diff --git a/overlays/patches/scipy/README.md b/overlays/patches/scipy/README.md new file mode 100644 index 0000000..dab2d2b --- /dev/null +++ b/overlays/patches/scipy/README.md @@ -0,0 +1,70 @@ +# SciPy STFT test tolerances + +The x86-64-v3 build can produce small floating-point residuals in inverse-STFT +comparisons and scaling round trips. The original bounds reject these results, +including residuals around `4e-17` where a round trip expects zero for a signal +with amplitude 2. + +## Scope and behavior + +`stft-test-tolerances.patch` changes only the signal tests: + +- The inverse-STFT comparison in `_scipy_spectral_test_shim.py` uses + `max(1e-7, 2 * np.finfo(x.dtype).eps)` as its relative tolerance. Float64 + keeps the original bound, and the existing i686 override remains. +- Three scaling round trips in `test_spectral.py` gain an absolute tolerance + of one epsilon for the input dtype, allowing small residuals near zero. + +The tests remain enabled, and the production STFT implementation is unchanged. + +## Reproduction and focused checks + +From this directory, apply the patch to a disposable SciPy 1.18.0 checkout: + +```sh +patch --fuzz=0 -d /path/to/scipy -p1 < stft-test-tolerances.patch +``` + +Build and install that tree with SciPy's test dependencies. From outside the +source directory, run the installed tests: + +```sh +python -m pytest --pyargs scipy.signal.tests.test_spectral \ + -k 'roundtrip_float32 or roundtrip_scaling' -q +``` + +Use the same compiler flags and numerical libraries for before/after runs. +The earlier reproduction called `TestSTFT.test_roundtrip_float32` and +`TestSTFT.test_roundtrip_scaling` against the x86-64-v3 libraries, then loaded +patched copies of the test modules. Both failed with the original bounds +and passed with the adjusted bounds. + +## Upstream status + +[SciPy issue #25488](https://github.com/scipy/scipy/issues/25488) records +related test failures with architecture-specific compiler flags. It is +context for the local tolerance repair; this exact patch has not been +submitted upstream during this work. + +## Local NixOS integration and build results + +[`../default.nix`](../default.nix) loads [`default.nix`](default.nix) through +`pythonPackagesExtensions`, preserving the package's existing patches. +The override also covers SciPy used to test other Python dependencies, +including pgvector in portal's shared Python environment. + +From the repository root: + +```sh +nix build --no-link -L .#nixosConfigurations.portal-1.pkgs.python314Packages.scipy +``` + +The original remote build of patched SciPy 1.18.0 passed 87,723 tests, with +8,342 skips, 300 expected failures, and 22 unexpected passes. The patch and +override have been restored byte-for-byte from commit `24cbf74f`; those counts +describe the earlier full build. + +Restoration checks confirmed that the patch applies to the pinned source +without fuzz, portal's evaluated SciPy retains its existing patch and install +checks, and pgvector uses the patched SciPy. A full package or system rebuild +was not repeated for this restoration. diff --git a/overlays/patches/scipy/default.nix b/overlays/patches/scipy/default.nix new file mode 100644 index 0000000..a6a3b5d --- /dev/null +++ b/overlays/patches/scipy/default.nix @@ -0,0 +1,5 @@ +{ scipy }: +scipy.overridePythonAttrs (old: { + # Keep the STFT tests enabled with tolerances for x86-64-v3 rounding. + patches = (old.patches or [ ]) ++ [ ./stft-test-tolerances.patch ]; +}) diff --git a/overlays/patches/scipy/stft-test-tolerances.patch b/overlays/patches/scipy/stft-test-tolerances.patch new file mode 100644 index 0000000..016bf32 --- /dev/null +++ b/overlays/patches/scipy/stft-test-tolerances.patch @@ -0,0 +1,51 @@ +Subject: [PATCH] signal: allow floating-point rounding in STFT tests + +Keep the STFT tests enabled for x86-64-v3 builds. Allow two float32 +epsilons of relative error when comparing inverse-STFT implementations; +float64 and the existing i686 override remain unchanged. Allow one +float64 epsilon of absolute error in all three scaling round trips, +which otherwise require exact zeros (observed residual: 4e-17 for a +signal with amplitude 2). + +Upstream issue: https://github.com/scipy/scipy/issues/25488 + +--- a/scipy/signal/tests/_scipy_spectral_test_shim.py ++++ b/scipy/signal/tests/_scipy_spectral_test_shim.py +@@ -294,7 +294,7 @@ + + # Adapted tolerances to account for resolution loss: + atol = np.finfo(x.dtype).resolution*2 # instead of default atol = 0 +- rtol = 1e-7 # default for np.allclose() ++ rtol = max(1e-7, 2 * np.finfo(x.dtype).eps) + + # Relax atol on 32-Bit platforms a bit to pass CI tests. + # - Not clear why there are discrepancies (in the FFT maybe?) +--- a/scipy/signal/tests/test_spectral.py ++++ b/scipy/signal/tests/test_spectral.py +@@ -2044,7 +2044,7 @@ + + # Test round trip: + x1 = istft(Zs, boundary=True, scaling='spectrum')[1] +- assert_allclose(x1, x) ++ assert_allclose(x1, x, atol=np.finfo(x.dtype).eps) + + # For a Hann-windowed 256 sample length FFT, we expect a peak at + # frequency 64 (since it is 1/4 the length of X) with a height of 1 +@@ -2074,7 +2074,7 @@ + + # Test round trip: + x1 = istft(Zp, input_onesided=False, boundary=True, scaling='psd')[1] +- assert_allclose(x1, x) ++ assert_allclose(x1, x, atol=np.finfo(x.dtype).eps) + + # The power of the one-sided psd-scaled STFT can be determined + # analogously (note that the two sides are not of equal shape): +@@ -2094,7 +2094,7 @@ + + # Test round trip: + x1 = istft(Zp0, input_onesided=True, boundary=True, scaling='psd')[1] +- assert_allclose(x1, x) ++ assert_allclose(x1, x, atol=np.finfo(x.dtype).eps) + + + class TestSampledSpectralRepresentations: