Repository: scipy/scipy
File: scipy/stats/tests/test_multivariate.py (Python)

Code before:

    @pytest.mark.parametrize("df", [1, 10, 100])
    def test_entropy_1d(self, df):
        mvt_entropy = stats.multivariate_t.entropy(shape=1., df=df)
        t_entropy = stats.t.entropy(df=df)
        assert_allclose(mvt_entropy, t_entropy, rtol=1e-13)

    # entropy reference values were computed via numerical integration
    #
    # def integrand(x, y, mvt):
    #     vec = np.array([x, y])
    #     return mvt.logpdf(vec) * mvt.pdf(vec)

    # def multivariate_t_entropy_quad_2d(df, cov):
    #     dim = cov.shape[0]
    #     loc = np.zeros((dim, ))
    #     mvt = stats.multivariate_t(loc, cov, df)
    #     limit = 100
    #     return -integrate.dblquad(integrand, -limit, limit, -limit, limit,
    #                               args=(mvt, ))[0]

    @pytest.mark.parametrize('X_ndim', [3])
    @pytest.mark.parametrize('dimensions', [[1], [-1, 1]])
    @pytest.mark.parametrize('frozen', [True, False])
    @pytest.mark.parametrize('df', [0.1, 1, 2])
    def test_marginal_distribution(self, X_ndim, dimensions, frozen, df):
        rng = np.random.default_rng(413911473)
        loc = rng.standard_normal(X_ndim)
        A = rng.standard_normal((X_ndim, X_ndim))
        shape = A @ A.T

        # number of points at which to evaluate marginal PDF
        x = np.random.standard_normal((4, len(dimensions)))
        X = multivariate_t(loc, shape, df)
        
        if frozen:
            Y = X.marginal(dimensions)
            res = Y.pdf(x)
        else: 
            Y = multivariate_t.marginal(dimensions, loc, shape, df)
            res = Y.pdf(x)

        ref = marginal_pdf(X, X_ndim, dimensions, x)
        assert_allclose(ref, res)

    def test_marginal_input_validation(self):
        rng = np.random.default_rng(413911473)
        loc = rng.standard_normal(3)
        A = rng.standard_normal((3, 3))
        shape = A @ A.T
        df = rng.uniform(low=0, high=5)

Review comment (suggestion):
These look pretty similar to the multivariate normal tests. Could you factor those into a new class and `parametrize` them like `(dist, [stats.multivariate_normal, stats.multivariate_t])` to reuse that code? Alternatively, `TestMultivariateNormal` and `TestMultivariateT` class can inherit from that class, and you could set an instance variable to run the test with a different distribution.

Code after:
        res = stats.multivariate_t(shape=A).cdf([0] * dim, random_state=rng)
        ref = 1 / (dim + 1)
        assert_allclose(res, ref, rtol=5e-5)

    def test_entropy_inf_df(self):
        cov = np.eye(3, 3)
        df = np.inf
        mvt_entropy = stats.multivariate_t.entropy(shape=cov, df=df)
        mvn_entropy = stats.multivariate_normal.entropy(None, cov)
        assert mvt_entropy == mvn_entropy

    @pytest.mark.parametrize("df", [1, 10, 100])
    def test_entropy_1d(self, df):
        mvt_entropy = stats.multivariate_t.entropy(shape=1., df=df)
        t_entropy = stats.t.entropy(df=df)
        assert_allclose(mvt_entropy, t_entropy, rtol=1e-13)

    # entropy reference values were computed via numerical integration
    #
    # def integrand(x, y, mvt):
    #     vec = np.array([x, y])
    #     return mvt.logpdf(vec) * mvt.pdf(vec)

    # def multivariate_t_entropy_quad_2d(df, cov):
    #     dim = cov.shape[0]
    #     loc = np.zeros((dim, ))
    #     mvt = stats.multivariate_t(loc, cov, df)
    #     limit = 100
    #     return -integrate.dblquad(integrand, -limit, limit, -limit, limit,
    #                               args=(mvt, ))[0]

    @pytest.mark.parametrize("df, cov, ref, tol",
                             [(10, np.eye(2, 2), 3.0378770664093313, 1e-14),
                              (100, np.array([[0.5, 1], [1, 10]]),
                               3.55102424550609, 1e-8)])
    def test_entropy_vs_numerical_integration(self, df, cov, ref, tol):
        loc = np.zeros((2, ))
        mvt = stats.multivariate_t(loc, cov, df)
        assert_allclose(mvt.entropy(), ref, rtol=tol)

    @pytest.mark.parametrize(
        "df, dim, ref, tol",
        [
            (10, 1, 1.5212624929756808, 1e-15),
            (100, 1, 1.4289633653182439, 1e-13),
            (500, 1, 1.420939531869349, 1e-14),
            (1e20, 1, 1.4189385332046727, 1e-15),
            (1e100, 1, 1.4189385332046727, 1e-15),
            (10, 10, 15.069150450832911, 1e-15),
            (1000, 10, 14.19936546446673, 1e-13),
            (1e20, 10, 14.189385332046728, 1e-15),