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

Code before:

    @skip_xp_backends(np_only=True, reason='list inputs are numpy-specific')
    def test_array_like(self, xp):
        hilbert2([[1, 2, 3], [4, 5, 6]])

    def test_bad_args(self, xp):
        # x must be real.
        x = xp.asarray([[1.0 + 0.0j]])
        assert_raises(ValueError, hilbert2, x)

        # Bad value for N.
        x = xp.reshape(xp.arange(16), (4, 4))
        assert_raises(ValueError, hilbert2, x, N=0)
        assert_raises(ValueError, hilbert2, x, N=(2, 0))
        assert_raises(ValueError, hilbert2, x, N=(0, 0))
        assert_raises(ValueError, hilbert2, x, N=(2,))

    @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)
        assert xp.real(signal.hilbert2(in_typed)).dtype == dtype
    
    @pytest.mark.parametrize('shape', [(4, 5), (5, 4), (4, 4), (5, 5)])
    @pytest.mark.parametrize('axes', [(-2, -1), (0, 1)])
    def test_hilbert2_old_implementation(self, xp, shape, axes):

        def _hilbert2(x, N=None):
            """
            Compute the '2-D' analytic signal of `x`

            Parameters
            ----------
            x : array_like
                2-D signal data.
            N : int or tuple of two ints, optional
                Number of Fourier components. Default is ``x.shape``

            Returns
            -------
            xa : ndarray
                Analytic signal of `x` taken along axes (0,1).

            References
            ----------
            .. [1] Wikipedia, "Analytic signal",
                https://en.wikipedia.org/wiki/Analytic_signal

            """
            xp = array_namespace(x)
            x = xpx.atleast_nd(xp.asarray(x), ndim=2, xp=xp)

Review comment (suggestion):
I do not think that testing against the old implementation brings a lot of additional value. It is sufficient to test that manually once. It's probably preferable to:

* Write tests for different shapes like in the example in previous [comment], where the result can be easily verified by hand.
* Additionally a test of a 3d signal, where the axes are permuted (with `np.moveaxis`), would be helpful.


 [comment]: https://github.com/scipy/scipy/pull/23247/files#r2278882880

Code after:
        """2d transform on equal arrays with moved axis are equal."""
        x0 = xp.reshape(xp.arange(5 * 7 * 9, dtype=xp.float64), (5, 7, 9))
        x0_as = hilbert2(x0)

        x1 = xp.moveaxis(x0, 0, 1)
        x1_as = hilbert2(x1, axes=(0, 2))
        x1_as = xp.moveaxis(x1_as, 1, 0)
        xp_assert_close(x0_as, x1_as)

        x2 = xp.moveaxis(x0, 0, 2)
        x2_as = hilbert2(x2, axes=(0, 1))
        x2_as = xp.moveaxis(x2_as, 2, 0)
        xp_assert_close(x0_as, x2_as)


class TestEnvelope:
    """Unit tests for function `._signaltools.envelope()`. """

    @staticmethod
    def assert_close(actual, desired, msg, xp):
        a_r_tol = ({'atol': 1e-12, 'rtol': 1e-12}
                   if xp_default_dtype(xp) == xp.float64
                   else {'atol': 1e-5, 'rtol': 1e-5}
        )

        """Little helper to compare to arrays with proper tolerances"""
        xp_assert_close(actual, desired, **a_r_tol, err_msg=msg)

    def test_envelope_invalid_parameters(self, xp):
        """For `envelope()` Raise all exceptions that are used to verify function
        parameters. """
        with pytest.raises(ValueError,
                           match=r"Invalid parameter axis=2 for z.shape=.*"):
            envelope(np.ones(3), axis=2)
        with pytest.raises(ValueError,
                           match=r"z.shape\[axis\] not > 0 for z.shape=.*"):
            envelope(xp.ones((3, 0)), axis=1)
        for bp_in in [(0, 1, 2), (0, 2.), (None, 2.)]:
            ts = ', '.join(map(str, bp_in))
            with pytest.raises(ValueError,
                               match=rf"bp_in=\({ts}\) isn't a 2-tuple of.*"):
                # noinspection PyTypeChecker
                envelope(xp.ones(4), bp_in=bp_in)
        with pytest.raises(ValueError,
                           match="n_out=10.0 is not a positive integer or.*"):
            # noinspection PyTypeChecker
            envelope(xp.ones(4), n_out=10.)
        for bp_in in [(-1, 3), (1, 1), (0, 10)]:
            with pytest.raises(ValueError,
                               match=r"`-n//2 <= bp_in\[0\] < bp_in\[1\] <=.*"):
                envelope(xp.ones(4), bp_in=bp_in)