Ejemplo n.º 1
0
class TestFixedRankEmbeddedManifold(ManifoldTestCase):
    def setUp(self):
        self.m = m = 10
        self.n = n = 5
        self.k = k = 3
        self.manifold = FixedRankEmbedded(m, n, k)

        u, s, vt = self.manifold.random_point()
        matrix = (u * s) @ vt

        @pymanopt.function.autograd(self.manifold)
        def cost(u, s, vt):
            return np.linalg.norm((u * s) @ vt - matrix) ** 2

        self.cost = cost

    def test_dim(self):
        assert self.manifold.dim == (self.m + self.n - self.k) * self.k

    def test_typical_dist(self):
        assert self.manifold.dim == self.manifold.typical_dist

    def test_dist(self):
        e = self.manifold
        a = e.random_point()
        x = e.random_tangent_vector(a)
        y = e.random_tangent_vector(a)
        with self.assertRaises(NotImplementedError):
            e.dist(x, y)

    def test_inner_product(self):
        e = self.manifold
        x = e.random_point()
        a = e.random_tangent_vector(x)
        b = e.random_tangent_vector(x)
        # First embed in the ambient space
        A = x[0] @ a[1] @ x[2] + a[0] @ x[2] + x[0] @ a[2].T
        B = x[0] @ b[1] @ x[2] + b[0] @ x[2] + x[0] @ b[2].T
        trueinner = np.sum(A * B)
        np_testing.assert_almost_equal(trueinner, e.inner_product(x, a, b))

    def test_proj_range(self):
        m = self.manifold
        x = m.random_point()
        v = np.random.normal(size=(self.m, self.n))

        g = m.projection(x, v)
        # Check that g is a true tangent vector
        np_testing.assert_allclose(
            g[0].T @ x[0], np.zeros((self.k, self.k)), atol=1e-6
        )
        np_testing.assert_allclose(
            g[2].T @ x[2].T, np.zeros((self.k, self.k)), atol=1e-6
        )

    def test_projection(self):
        # Verify that proj gives the closest point within the tangent space
        # by displacing the result slightly and checking that this increases
        # the distance.
        m = self.manifold
        x = self.manifold.random_point()
        v = np.random.normal(size=(self.m, self.n))

        g = m.projection(x, v)
        # Displace g a little
        g_disp = g + 0.01 * m.random_tangent_vector(x)

        # Return to the ambient representation
        g = m.embedding(x, g)
        g_disp = m.embedding(x, g_disp)
        g = g[0] @ g[1] @ g[2].T
        g_disp = g_disp[0] @ g_disp[1] @ g_disp[2].T

        assert np.linalg.norm(g - v) < np.linalg.norm(g_disp - v)

    def test_proj_tangents(self):
        # Verify that proj leaves tangent vectors unchanged
        e = self.manifold
        x = e.random_point()
        u = e.random_tangent_vector(x)
        A = e.projection(x, e.embedding(x, u))
        B = u
        # diff = [A[k]-B[k] for k in range(len(A))]
        np_testing.assert_allclose(A[0], B[0])
        np_testing.assert_allclose(A[1], B[1])
        np_testing.assert_allclose(A[2], B[2])

    def test_norm(self):
        e = self.manifold
        x = e.random_point()
        u = e.random_tangent_vector(x)
        np_testing.assert_almost_equal(
            np.sqrt(e.inner_product(x, u, u)), e.norm(x, u)
        )

    def test_random_point(self):
        e = self.manifold
        x = e.random_point()
        y = e.random_point()
        assert np.shape(x[0]) == (self.m, self.k)
        assert np.shape(x[1]) == (self.k,)
        assert np.shape(x[2]) == (self.k, self.n)
        np_testing.assert_allclose(x[0].T @ x[0], np.eye(self.k), atol=1e-6)
        np_testing.assert_allclose(x[2] @ x[2].T, np.eye(self.k), atol=1e-6)

        assert np.linalg.norm(x[0] - y[0]) > 1e-6
        assert np.linalg.norm(x[1] - y[1]) > 1e-6
        assert np.linalg.norm(x[2] - y[2]) > 1e-6

    def test_transport(self):
        s = self.manifold
        x = s.random_point()
        y = s.random_point()
        u = s.random_tangent_vector(x)
        A = s.transport(x, y, u)
        B = s.projection(y, s.embedding(x, u))
        diff = [A[k] - B[k] for k in range(len(A))]
        np_testing.assert_almost_equal(s.norm(y, diff), 0)

    def test_apply_ambient(self):
        m = self.manifold
        z = np.random.normal(size=(self.m, self.n))

        # Set u, s, v so that z = u @ s @ v.T
        u, s, v = np.linalg.svd(z, full_matrices=False)
        s = np.diag(s)
        v = v.T

        w = np.random.normal(size=(self.n, self.n))

        np_testing.assert_allclose(z @ w, m._apply_ambient(z, w))
        np_testing.assert_allclose(z @ w, m._apply_ambient((u, s, v), w))

    def test_apply_ambient_transpose(self):
        m = self.manifold
        z = np.random.normal(size=(self.n, self.m))

        # Set u, s, v so that z = u @ s @ v.T
        u, s, v = np.linalg.svd(z, full_matrices=False)
        s = np.diag(s)
        v = v.T

        w = np.random.normal(size=(self.n, self.n))

        np_testing.assert_allclose(z.T @ w, m._apply_ambient_transpose(z, w))
        np_testing.assert_allclose(
            z.T @ w, m._apply_ambient_transpose((u, s, v), w)
        )

    def test_embedding(self):
        m = self.manifold
        x = m.random_point()
        z = m.random_tangent_vector(x)

        z_ambient = x[0] @ z[1] @ x[2] + z[0] @ x[2] + x[0] @ z[2].T

        u, s, v = m.embedding(x, z)

        np_testing.assert_allclose(z_ambient, u @ s @ v.T)

    def test_euclidean_to_riemannian_hessian(self):
        pass

    def test_retraction(self):
        # Test that the result is on the manifold and that for small
        # tangent vectors it has little effect.
        x = self.manifold.random_point()
        u = self.manifold.random_tangent_vector(x)

        y = self.manifold.retraction(x, u)

        np_testing.assert_allclose(y[0].T @ y[0], np.eye(self.k), atol=1e-6)
        np_testing.assert_allclose(y[2] @ y[2].T, np.eye(self.k), atol=1e-6)

        u = u * 1e-6
        y = self.manifold.retraction(x, u)
        y = y[0] @ np.diag(y[1]) @ y[2]

        u = self.manifold.embedding(x, u)
        u = u[0] @ u[1] @ u[2].T
        x = x[0] @ np.diag(x[1]) @ x[2]

        np_testing.assert_allclose(y, x + u, atol=1e-5)

    def test_euclidean_to_riemannian_gradient(self):
        # Verify that euclidean_to_riemannian_gradient and proj are equivalent.
        m = self.manifold
        x = m.random_point()
        u, s, vt = x

        i = np.eye(self.k)

        f = 1 / (s[..., np.newaxis, :] ** 2 - s[..., :, np.newaxis] ** 2 + i)

        du = np.random.normal(size=(self.m, self.k))
        ds = np.random.normal(size=self.k)
        dvt = np.random.normal(size=(self.k, self.n))

        Up = (np.eye(self.m) - u @ u.T) @ du @ np.linalg.inv(np.diag(s))
        M = (
            f * (u.T @ du - du.T @ u) @ np.diag(s)
            + np.diag(s) @ f * (vt @ dvt.T - dvt @ vt.T)
            + np.diag(ds)
        )
        Vp = (np.eye(self.n) - vt.T @ vt) @ dvt.T @ np.linalg.inv(np.diag(s))

        up, m, vp = m.euclidean_to_riemannian_gradient(x, (du, ds, dvt))

        np_testing.assert_allclose(Up, up)
        np_testing.assert_allclose(M, m)
        np_testing.assert_allclose(Vp, vp)

    def test_first_order_function_approximation(self):
        self.run_gradient_approximation_test()

    def test_random_tangent_vector(self):
        e = self.manifold
        x = e.random_point()
        u = e.random_tangent_vector(x)

        # Check that u is a tangent vector
        assert np.shape(u[0]) == (self.m, self.k)
        assert np.shape(u[1]) == (self.k, self.k)
        assert np.shape(u[2]) == (self.n, self.k)
        np_testing.assert_allclose(
            u[0].T @ x[0], np.zeros((self.k, self.k)), atol=1e-6
        )
        np_testing.assert_allclose(
            u[2].T @ x[2].T, np.zeros((self.k, self.k)), atol=1e-6
        )

        v = e.random_tangent_vector(x)

        np_testing.assert_almost_equal(e.norm(x, u), 1)
        assert e.norm(x, u - v) > 1e-6