Repository: scipy/scipy
File: scipy/signal/tests/test_signaltools.py (Python)

Code before:
        out = xp.real(signal.hilbert2(in_typed))
        assert out.dtype == dtype

    def test_parameter_N(self, xp):
        """Compare passing tuple to single int. """
        x = xp.zeros((5, 5))
        x0_a = hilbert2(x, N=4)
        x1_a = hilbert2(x, N=(4, 4))
        xp_assert_equal(x1_a, x0_a)

    @pytest.mark.parametrize('shape', [(4, 5), (5, 4), (4, 4), (5, 5)])
    def test_quadrant_values(self, shape, xp):
        """Compare desired and calculated values in Fourier space. """
        x_f = xp.ones(shape, dtype=xp.complex128)  # FFT of input signal
        x_f[0 , 0] += 7
        x = xp.real(sp_fft.ifft2(x_f))  # x.imag is zero

        x_as = hilbert2(x)
        x_as_f = sp_fft.fft2(x_as)

        # Create slices for bins with purely positive and purely negative frequencies
        # (can be verified with `sp_fft.fftfreq()`):
        f0_pos, f0_neg = slice(1, (shape[0] + 1) // 2), slice((shape[0] + 1) // 2, None)
        f1_pos, f1_neg = slice(1, (shape[1] + 1) // 2), slice((shape[1] + 1) // 2, None)
        # Verify all values:
        atol = np.finfo(x_as_f.dtype).resolution
        xp_assert_close(x_as_f[f0_pos, f1_pos], x_f[f0_pos, f1_pos] * 4, atol=atol)
        xp_assert_close(x_as_f[0, f1_pos], x_f[0, f1_pos] * 2, atol=atol)
        xp_assert_close(x_as_f[f0_pos, 0], x_f[f0_pos, 0] * 2, atol=atol)
        xp_assert_close(x_as_f[0, 0], x_f[0, 0], atol=atol)
        zz_as_f = x_as_f[f0_neg, f1_neg]  # check for zeroed orthants
        xp_assert_close(zz_as_f, xp.zeros_like(zz_as_f), atol=atol)

    @pytest.mark.parametrize('shape', [(4, 5), (5, 4), (4, 4), (5, 5)])
    def test_zero_analytic_signal(self, shape, xp):
        """Test that a real signal with Z[-p,-q] == np.conj(Z[p,q])
        produces a zero analytic signal."""
        c0 = shape[0] // 2
        c1 = shape[1] // 2
        x_f = xp.zeros(shape)
        x_f[c0 - 1, c1 + 1] = 1.0
        x_f[c0 + 1, c1 - 1] = 1.0
        x_f = sp_fft.ifftshift(x_f)
        x = xp.real(sp_fft.ifft2(x_f))
        assert xp.sum(abs(x)) > 0.0
        x_as = hilbert2(x)
        xp_assert_close(x_as, xp.zeros_like(x_as), atol=xp.finfo(x_as.dtype).resolution)

    @pytest.mark.parametrize('sh0', [4, 5])
    @pytest.mark.parametrize('sh1', [6, 7])
    @pytest.mark.parametrize('sh2', [8, 9])

Review comment (suggestion):
Unfortunately the `resolution` isn't in the array api standard, maybe something like sqrt(eps) would work?
https://data-apis.org/array-api/latest/API_specification/generated/array_api.finfo.html#finfo 

Code after:
            hilbert2(xp.asarray([[1.0 + 0.0j]]))
        with pytest.raises(ValueError, match="^axes must be a tuple of length 2"):
            hilbert2(x, axes=(0, 1, 2))
        with pytest.raises(ValueError, match="^axes must contain 2 distinct axes"):
            hilbert2(x, axes=(0, 0))
        with pytest.raises(ValueError, match="^N must be positive."):
            hilbert2(x, N=-1)
        with pytest.raises(ValueError, match="^When given as a tuple, N must hold"):
            hilbert2(x, N=(1, 1, 1))
        with pytest.raises(ValueError, match="^When given as a tuple, N must hold"):
            hilbert2(x, N=(0, 1))

    @pytest.mark.parametrize('dtype', ['float32', 'float64'])
    def test_hilbert2_types(self, dtype, xp):
        dtype = getattr(xp, dtype)
        in_typed = xp.zeros((2, 32), dtype=dtype)
        out = xp.real(signal.hilbert2(in_typed))
        assert out.dtype == dtype

    def test_1d_input(self, xp):
        """Needed for 100% coverage """
        x = xp.asarray([0., 1., 1., 0., -1., -1.])
        x0a = signal.hilbert2(xp.reshape(x, (6, 1)))
        x1a = signal.hilbert2(xp.reshape(x, (1, 6)))
        xp_assert_close(x0a, x1a.T)

    def test_parameter_N(self, xp):
        """Compare passing tuple to single int. """
        x = xp.zeros((5, 5))
        x0_a = hilbert2(x, N=4)
        x1_a = hilbert2(x, N=(4, 4))
        xp_assert_equal(x1_a, x0_a)

    @pytest.mark.parametrize('shape', [(4, 5), (5, 4), (4, 4), (5, 5)])
    @skip_xp_backends("cupy", reason="Bug in cupy implementation, see cupy#9396")
    def test_quadrant_values(self, shape, xp):
        """Compare desired and calculated values in Fourier space. """
        x_f = xp.ones(shape, dtype=xp.complex128)  # FFT of input signal
        x_f[0 , 0] += 7
        x = xp.real(sp_fft.ifft2(x_f))  # x.imag is zero

        x_as = hilbert2(x)
        x_as_f = sp_fft.fft2(x_as)

        # Create slices for bins with purely positive and purely negative frequencies
        # (can be verified with `sp_fft.fftfreq()`):
        f0_pos, f0_neg = slice(1, (shape[0] + 1) // 2), slice((shape[0] + 1) // 2, None)
        f1_pos, f1_neg = slice(1, (shape[1] + 1) // 2), slice((shape[1] + 1) // 2, None)
        # Verify all values:
        atol = 1e-12  # for x of dtype complex128
        xp_assert_close(x_as_f[f0_pos, f1_pos], x_f[f0_pos, f1_pos] * 4, atol=atol)