def test_inner(self): man = self.man k = self.k n = self.n x = man.rand() a, b = rnd.randn(2, k, n, n) np.testing.assert_almost_equal(np.tensordot(a, b, axes=a.ndim), man.inner(x, multiprod(x, a), multiprod(x, b)))
def dist(self, x, y): # Adapted from equation 6.13 of "Positive definite matrices". Chol # decomp gives the same result as matrix sqrt. There may be a more # efficient way to compute this! c = np.linalg.cholesky(x) c_inv = np.linalg.inv(c) l = multilog(multiprod(multiprod(c_inv, y), multitransp(c_inv)), pos_def=True) return la.norm(multiprod(multiprod(c, l), c_inv))
def log(self, X, Y): ytx = multiprod(multitransp(Y), X) At = multitransp(Y) - multiprod(ytx, multitransp(X)) Bt = np.linalg.solve(ytx, At) u, s, vt = svd(multitransp(Bt), full_matrices=False) arctan_s = np.expand_dims(np.arctan(s), -2) U = multiprod(u * arctan_s, vt) return U
def test_proj(self): # Construct a random point X on the manifold. X = self.man.rand() # Construct a vector H in the ambient space. H = rnd.randn(self.k, self.m, self.n) # Compare the projections. Hproj = H - multiprod(X, multiprod(multitransp(X), H)) np_testing.assert_allclose(Hproj, self.man.proj(X, H))
def test_rand(self): # Just make sure that things generated are on the manifold and that # if you generate two they are not equal. X = self.man.rand() np_testing.assert_allclose(multiprod(multitransp(X), X), multieye(self.k, self.n), atol=1e-10) Y = self.man.rand() assert np.linalg.norm(X - Y) > 1e-6
def test_multiprod_singlemat(self): # Two random matrices A (m x n) and B (n x p) A = rnd.randn(self.m, self.n) B = rnd.randn(self.n, self.p) # Compare the products. np_testing.assert_allclose(A.dot(B), multiprod(A, B))
def exp(self, X, U): u, s, vt = svd(U, full_matrices=False) cos_s = np.expand_dims(np.cos(s), -2) sin_s = np.expand_dims(np.sin(s), -2) Y = (multiprod(multiprod(X, multitransp(vt) * cos_s), vt) + multiprod(u * sin_s, vt)) # From numerical experiments, it seems necessary to # re-orthonormalize. This is overall quite expensive. if self._k == 1: Y, unused = np.linalg.qr(Y) return Y else: for i in range(self._k): Y[i], unused = np.linalg.qr(Y[i]) return Y
def test_exp(self): man = self.man x = man.rand() u = man.randvec(x) e = sp.linalg.expm(la.solve(x, u)) np_testing.assert_allclose(multiprod(x, e), man.exp(x, u)) u = u * 1e-6 np_testing.assert_allclose(man.exp(x, u), x + u)
def test_randvec(self): # Make sure things generated are in tangent space and if you generate # two then they are not equal. X = self.man.rand() U = self.man.randvec(X) np_testing.assert_allclose(multisym(multiprod(multitransp(X), U)), np.zeros((self.k, self.n, self.n)), atol=1e-10) V = self.man.randvec(X) assert la.norm(U - V) > 1e-6
def test_multiprod(self): # Two random arrays of matrices A (k x m x n) and B (k x n x p) A = rnd.randn(self.k, self.m, self.n) B = rnd.randn(self.k, self.n, self.p) C = np.zeros((self.k, self.m, self.p)) for i in range(self.k): C[i] = A[i].dot(B[i]) np_testing.assert_allclose(C, multiprod(A, B))
def exp(self, x, u): # TODO: Check which method is faster depending on n, k. x_inv_u = np.linalg.solve(x, u) if self._k > 1: e = np.zeros(np.shape(x)) for i in range(self._k): e[i] = sp.linalg.expm(x_inv_u[i]) else: e = sp.linalg.expm(x_inv_u) return multiprod(x, e)
def rand(self): # The way this is done is arbitrary. I think the space of p.d. # matrices would have infinite measure w.r.t. the Riemannian metric # (c.f. integral 0-inf [ln(x)] dx = inf) so impossible to have a # 'uniform' distribution. # Generate eigenvalues between 1 and 2 d = np.ones((self._k, self._n, 1)) + rnd.rand(self._k, self._n, 1) # Generate an orthogonal matrix. Annoyingly qr decomp isn't # vectorized so need to use a for loop. Could be done using # svd but this is slower for bigger matrices. u = np.zeros((self._k, self._n, self._n)) for i in range(self._k): u[i], r = la.qr(rnd.randn(self._n, self._n)) if self._k == 1: return multiprod(u, d * multitransp(u))[0] else: return multiprod(u, d * multitransp(u))
def retr(self, X, G): # Calculate 'thin' qr decomposition of X + G # XNew, r = np.linalg.qr(X + G) # We do not need to worry about flipping signs of columns here, # since only the column space is important, not the actual # columns. Compare this with the Stiefel manifold. # Compute the polar factorization of Y = X+G u, s, vt = svd(X + G, full_matrices=False) return multiprod(u, vt)
def test_exp(self): # Test against manopt implementation, test that for small vectors # exp(x, u) = x + u. man = self.man x = man.rand() u = man.randvec(x) e = np.zeros((self.k, self.n, self.n)) for i in range(self.k): e[i] = sp.linalg.expm(la.solve(x[i], u[i])) np_testing.assert_allclose(multiprod(x, e), man.exp(x, u)) u = u * 1e-6 np_testing.assert_allclose(man.exp(x, u), x + u)
def test_retr(self): # Test that the result is on the manifold and that for small # tangent vectors it has little effect. x = self.man.rand() u = self.man.randvec(x) xretru = self.man.retr(x, u) np_testing.assert_allclose(multiprod(multitransp(xretru), xretru), multieye(self.k, self.n), atol=1e-10) u = u * 1e-6 xretru = self.man.retr(x, u) np_testing.assert_allclose(xretru, x + u)
def test_exp(self): # Check that exp lies on the manifold and that exp of a small vector u # is close to x + u. s = self.man x = s.rand() u = s.randvec(x) xexpu = s.exp(x, u) np_testing.assert_allclose(multiprod(multitransp(xexpu), xexpu), multieye(self.k, self.n), atol=1e-10) u = u * 1e-6 xexpu = s.exp(x, u) np_testing.assert_allclose(xexpu, x + u)
def dist(self, X, Y): if self._k == 1: u, s, v = np.linalg.svd(np.dot(X.T, Y)) s[s > 1] = 1 s = np.arccos(s) return np.linalg.norm(s) else: XtY = multiprod(multitransp(X), Y) square_d = 0 for i in xrange(self._k): s = np.linalg.svd(XtY[i], compute_uv=False) # Ensure that -1 <= s <= 1 s = np.fmin(s, [1]) s = np.fmax(s, [-1]) square_d = square_d + np.linalg.norm(np.arccos(s))**2 return np.sqrt(square_d)
def test_ehess2rhess(self): # Use manopt's slow method man = self.man n = self.n k = self.k x = man.rand() egrad, ehess = rnd.randn(2, k, n, n) u = man.randvec(x) Hess = (multiprod(multiprod(x, multisym(ehess)), x) + 2*multisym(multiprod(multiprod(u, multisym(egrad)), x))) # Correction factor for the non-constant metric Hess = Hess - multisym(multiprod(multiprod(u, multisym(egrad)), x)) np_testing.assert_almost_equal(Hess, man.ehess2rhess(x, egrad, ehess, u))
def proj(self, X, U): return U - multiprod(X, multiprod(multitransp(X), U))
def ehess2rhess(self, X, egrad, ehess, H): # Convert Euclidean into Riemannian Hessian. PXehess = self.proj(X, ehess) XtG = multiprod(multitransp(X), egrad) HXtG = multiprod(H, XtG) return PXehess - HXtG
def dist(self, X, Y): u, s, v = svd(multiprod(multitransp(X), Y)) s[s > 1] = 1 s = np.arccos(s) return np.linalg.norm(s)
def test_egrad2rgrad(self): man = self.man x = man.rand() u = rnd.randn(self.n, self.n) + 1j * rnd.randn(self.n, self.n) np.testing.assert_allclose(man.egrad2rgrad(x, u), multiprod(multiprod(x, multiherm(u)), x))
def proj(self, X, U): UNew = U - multiprod( X, multiprod(multitransp(X), U) + multiprod(multitransp(U), X)) / 2 return UNew
def ehess2rhess(self, X, egrad, ehess, H): # Convert Euclidean into Riemannian Hessian. XtG = multiprod(multitransp(X), egrad) symXtG = multisym(XtG) HsymXtG = multiprod(H, symXtG) return self.proj(X, ehess - HsymXtG)
def test_egrad2rgrad(self): man = self.man x = man.rand() u = rnd.randn(self.k, self.n, self.n) np.testing.assert_allclose(man.egrad2rgrad(x, u), multiprod(multiprod(x, multisym(u)), x))
def ehess2rhess(self, x, egrad, ehess, u): # TODO: Check that this is correct return (multiprod(multiprod(x, multisym(ehess)), x) + multisym(multiprod(multiprod(u, multisym(egrad)), x)))
def egrad2rgrad(self, x, u): # TODO: Check that this is correct return multiprod(multiprod(x, multisym(u)), x)
def log(self, x, y): c = la.cholesky(x) c_inv = la.inv(c) l = multilog(multiprod(multiprod(c_inv, y), multitransp(c_inv)), pos_def=True) return multiprod(multiprod(c, l), multitransp(c))
def test_norm(self): man = self.man x = man.rand() a = np.random.randn(self.k, self.n, self.n) np_testing.assert_almost_equal(la.norm(a), man.norm(x, multiprod(x, a)))
def ehess2rhess(self, X, egrad, ehess, H): # Convert Euclidean hessian into Riemannian hessian. PXehess = self.proj(X, ehess) XtG = multiprod(multitransp(X), egrad) HXtG = multiprod(H, XtG) return PXehess - HXtG