def __init__(self, k_landmarks, m_ambient): super(PreShapeMetric, self).__init__(dim=m_ambient * (k_landmarks - 1) - 1, default_point_type="matrix") self.embedding_metric = MatricesMetric(k_landmarks, m_ambient) self.sphere_metric = Hypersphere(m_ambient * k_landmarks - 1).metric self.k_landmarks = k_landmarks self.m_ambient = m_ambient
def __init__(self, m, n, **kwargs): if "dim" not in kwargs.keys(): kwargs["dim"] = m * n super(FullRankMatrices, self).__init__(ambient_space=Matrices(m, n), metric=MatricesMetric(m, n), **kwargs) self.rank = min(m, n)
def __init__(self, n): super(BuresWassersteinBundle, self).__init__( n=n, base=SPDMatrices(n), group=SpecialOrthogonal(n), ambient_metric=MatricesMetric(n, n), )
def setUp(self): gs.random.seed(0) n = 3 self.base = SPDMatrices(n) self.base_metric = SPDMetricBuresWasserstein(n) self.group = SpecialOrthogonal(n) self.bundle = FiberBundle(GeneralLinear(n), base=self.base, group=self.group) self.quotient_metric = QuotientMetric(self.bundle, ambient_metric=MatricesMetric( n, n)) def submersion(point): return GeneralLinear.mul(point, GeneralLinear.transpose(point)) def tangent_submersion(tangent_vec, base_point): product = GeneralLinear.mul(base_point, GeneralLinear.transpose(tangent_vec)) return 2 * GeneralLinear.to_symmetric(product) def horizontal_lift(tangent_vec, point, base_point=None): if base_point is None: base_point = submersion(point) sylvester = gs.linalg.solve_sylvester(base_point, base_point, tangent_vec) return GeneralLinear.mul(sylvester, point) self.bundle.submersion = submersion self.bundle.tangent_submersion = tangent_submersion self.bundle.horizontal_lift = horizontal_lift self.bundle.lift = gs.linalg.cholesky
def __init__(self, n, k): super(BuresWassersteinBundle, self).__init__( n=n, k=k, group=SpecialOrthogonal(k), ambient_metric=MatricesMetric(n, k), )
def __init__(self, n, k, **kwargs): kwargs.setdefault("dim", n * k) kwargs.setdefault("metric", MatricesMetric(n, k)) super(FullRankMatrices, self).__init__(ambient_space=Matrices(n, k), **kwargs) self.rank = min(n, k) self.n = n self.k = k
def __init__(self, n, **kwargs): super(SymmetricMatrices, self).__init__( dim=int(n * (n + 1) / 2), shape=(n, n), metric=MatricesMetric(n, n), default_point_type="matrix", ) self.n = n
def __init__(self, n, **kwargs): kwargs.setdefault("metric", MatricesMetric(n, n)) super(LowerTriangularMatrices, self).__init__(dim=int(n * (n + 1) / 2), shape=(n, n), default_point_type="matrix", **kwargs) self.n = n
def test_mean_matrices_shape(self): m, n = (2, 2) point = gs.array([[1., 4.], [2., 3.]]) metric = MatricesMetric(m, n) mean = FrechetMean(metric=metric, point_type='matrix') points = [point, point, point] mean.fit(points) result = mean.estimate_ self.assertAllClose(gs.shape(result), (m, n))
def test_mean_matrices(self): m, n = (2, 2) point = gs.array([[1.0, 4.0], [2.0, 3.0]]) metric = MatricesMetric(m, n) mean = FrechetMean(metric=metric, point_type="matrix") points = [point, point, point] mean.fit(points) result = mean.estimate_ expected = point self.assertAllClose(result, expected)
def test_power_euclidean_inner_product(self): """Test of SPDMetricEuclidean.inner_product method.""" base_point = gs.array([[1., 0., 0.], [0., 2.5, 1.5], [0., 1.5, 2.5]]) tangent_vec = gs.array([[2., 1., 1.], [1., .5, .5], [1., .5, .5]]) metric = SPDMetricEuclidean(3, power_euclidean=.5) result = metric.inner_product(tangent_vec, tangent_vec, base_point) expected = 3472 / 576 self.assertAllClose(result, expected) result = self.metric_euclidean.inner_product(tangent_vec, tangent_vec, base_point) expected = MatricesMetric(3, 3).inner_product(tangent_vec, tangent_vec) self.assertAllClose(result, expected)
def dist(self, point_a, point_b): """Compute log euclidean distance. Parameters ---------- point_a : array-like, shape=[..., dim] Point. point_b : array-like, shape=[..., dim] Point. Returns ------- dist : array-like, shape=[...,] Distance. """ log_a = SPDMatrices.logm(point_a) log_b = SPDMatrices.logm(point_b) return MatricesMetric.norm(log_a - log_b)
class PreShapeMetric(RiemannianMetric): """Procrustes metric on the pre-shape space. Parameters ---------- k_landmarks : int Number of landmarks m_ambient : int Number of coordinates of each landmark. """ def __init__(self, k_landmarks, m_ambient): super(PreShapeMetric, self).__init__(dim=m_ambient * (k_landmarks - 1) - 1, default_point_type="matrix") self.embedding_metric = MatricesMetric(k_landmarks, m_ambient) self.sphere_metric = Hypersphere(m_ambient * k_landmarks - 1).metric self.k_landmarks = k_landmarks self.m_ambient = m_ambient def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None): """Compute the inner-product of two tangent vectors at a base point. Parameters ---------- tangent_vec_a : array-like, shape=[..., k_landmarks, m_ambient] First tangent vector at base point. tangent_vec_b : array-like, shape=[..., k_landmarks, m_ambient] Second tangent vector at base point. base_point : array-like, shape=[..., dk_landmarks, m_ambient] Point on the pre-shape space. Returns ------- inner_prod : array-like, shape=[...,] Inner-product of the two tangent vectors. """ inner_prod = self.embedding_metric.inner_product( tangent_vec_a, tangent_vec_b, base_point) return inner_prod def exp(self, tangent_vec, base_point, **kwargs): """Compute the Riemannian exponential of a tangent vector. Parameters ---------- tangent_vec : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at a base point. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space. Returns ------- exp : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space equal to the Riemannian exponential of tangent_vec at the base point. """ flat_bp = gs.reshape(base_point, (-1, self.sphere_metric.dim + 1)) flat_tan = gs.reshape(tangent_vec, (-1, self.sphere_metric.dim + 1)) flat_exp = self.sphere_metric.exp(flat_tan, flat_bp) return gs.reshape(flat_exp, tangent_vec.shape) def log(self, point, base_point, **kwargs): """Compute the Riemannian logarithm of a point. Parameters ---------- point : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space. Returns ------- log : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at the base point equal to the Riemannian logarithm of point at the base point. """ flat_bp = gs.reshape(base_point, (-1, self.sphere_metric.dim + 1)) flat_pt = gs.reshape(point, (-1, self.sphere_metric.dim + 1)) flat_log = self.sphere_metric.log(flat_pt, flat_bp) try: log = gs.reshape(flat_log, base_point.shape) except (RuntimeError, check_tf_error(ValueError, "InvalidArgumentError")): log = gs.reshape(flat_log, point.shape) return log def curvature(self, tangent_vec_a, tangent_vec_b, tangent_vec_c, base_point): r"""Compute the curvature. For three tangent vectors at a base point :math:`x,y,z`, the curvature is defined by :math:`R(X, Y)Z = \nabla_{[X,Y]}Z - \nabla_X\nabla_Y Z + - \nabla_Y\nabla_X Z`, where :math:`\nabla` is the Levi-Civita connection. In the case of the hypersphere, we have the closed formula :math:`R(X,Y)Z = \langle X, Z \rangle Y - \langle Y,Z \rangle X`. Parameters ---------- tangent_vec_a : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. tangent_vec_b : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. tangent_vec_c : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the group. Optional, default is the identity. Returns ------- curvature : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. """ max_shape = base_point.shape for arg in [tangent_vec_a, tangent_vec_b, tangent_vec_c]: if arg.ndim >= 3: max_shape = arg.shape flat_shape = (-1, self.sphere_metric.dim + 1) flat_a = gs.reshape(tangent_vec_a, flat_shape) flat_b = gs.reshape(tangent_vec_b, flat_shape) flat_c = gs.reshape(tangent_vec_c, flat_shape) flat_bp = gs.reshape(base_point, flat_shape) curvature = self.sphere_metric.curvature(flat_a, flat_b, flat_c, flat_bp) curvature = gs.reshape(curvature, max_shape) return curvature def curvature_derivative( self, tangent_vec_a, tangent_vec_b=None, tangent_vec_c=None, tangent_vec_d=None, base_point=None, ): r"""Compute the covariant derivative of the curvature. For four vectors fields :math:`H|_P = tangent\_vec\_a, X|_P = tangent\_vec\_b, Y|_P = tangent\_vec\_c, Z|_P = tangent\_vec\_d` with tangent vector value specified in argument at the base point `P`, the covariant derivative of the curvature :math:`(\nabla_H R)(X, Y) Z |_P` is computed at the base point P. Since the sphere is a constant curvature space this vanishes identically. Parameters ---------- tangent_vec_a : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point` along which the curvature is derived. tangent_vec_b : array-like, shape=[..., k_landmarks, m_ambient] Unused tangent vector at `base_point` (since curvature derivative vanishes). tangent_vec_c : array-like, shape=[..., k_landmarks, m_ambient] Unused tangent vector at `base_point` (since curvature derivative vanishes). tangent_vec_d : array-like, shape=[..., k_landmarks, m_ambient] Unused tangent vector at `base_point` (since curvature derivative vanishes). base_point : array-like, shape=[..., k_landmarks, m_ambient] Unused point on the group. Returns ------- curvature_derivative : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at base point. """ return gs.zeros_like(tangent_vec_a) def parallel_transport(self, tangent_vec, base_point, direction=None, end_point=None): """Compute the Riemannian parallel transport of a tangent vector. Parameters ---------- tangent_vec : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at a base point. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space. direction : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at a base point. Optional, default : None. end_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space, to transport to. Unused if `tangent_vec_b` is given. Optional, default : None. Returns ------- transported : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space equal to the Riemannian exponential of tangent_vec at the base point. """ if direction is None: if end_point is not None: direction = self.log(end_point, base_point) else: raise ValueError( "Either an end_point or a tangent_vec_b must be given to define the" " geodesic along which to transport.") max_shape = tangent_vec.shape if tangent_vec.ndim == 3 else direction.shape flat_bp = gs.reshape(base_point, (-1, self.sphere_metric.dim + 1)) flat_tan_a = gs.reshape(tangent_vec, (-1, self.sphere_metric.dim + 1)) flat_tan_b = gs.reshape(direction, (-1, self.sphere_metric.dim + 1)) flat_transport = self.sphere_metric.parallel_transport( flat_tan_a, flat_bp, flat_tan_b) return gs.reshape(flat_transport, max_shape) def injectivity_radius(self, base_point): """Compute the radius of the injectivity domain. This is is the supremum of radii r for which the exponential map is a diffeomorphism from the open ball of radius r centered at the base point onto its image. In the case of the sphere, it does not depend on the base point and is Pi everywhere. Parameters ---------- base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the manifold. Returns ------- radius : float Injectivity radius. """ return gs.pi
class ProcrustesMetric(RiemannianMetric): """Procrustes metric on the pre-shape space. Parameters ---------- k_landmarks : int Number of landmarks m_ambient : int Number of coordinates of each landmark. """ def __init__(self, k_landmarks, m_ambient): super(ProcrustesMetric, self).__init__( dim=m_ambient * (k_landmarks - 1) - 1, default_point_type='matrix') self.embedding_metric = MatricesMetric(k_landmarks, m_ambient) self.sphere_metric = Hypersphere(m_ambient * k_landmarks - 1).metric self.k_landmarks = k_landmarks self.m_ambient = m_ambient def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None): """Compute the inner-product of two tangent vectors at a base point. Parameters ---------- tangent_vec_a : array-like, shape=[..., k_landmarks, m_ambient] First tangent vector at base point. tangent_vec_b : array-like, shape=[..., k_landmarks, m_ambient] Second tangent vector at base point. base_point : array-like, shape=[..., dk_landmarks, m_ambient] Point on the pre-shape space. Returns ------- inner_prod : array-like, shape=[...,] Inner-product of the two tangent vectors. """ inner_prod = self.embedding_metric.inner_product( tangent_vec_a, tangent_vec_b, base_point) return inner_prod def exp(self, tangent_vec, base_point): """Compute the Riemannian exponential of a tangent vector. Parameters ---------- tangent_vec : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at a base point. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space. Returns ------- exp : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space equal to the Riemannian exponential of tangent_vec at the base point. """ flat_bp = gs.reshape(base_point, (-1, self.sphere_metric.dim + 1)) flat_tan = gs.reshape(tangent_vec, (-1, self.sphere_metric.dim + 1)) flat_exp = self.sphere_metric.exp(flat_tan, flat_bp) return gs.reshape(flat_exp, tangent_vec.shape) def log(self, point, base_point): """Compute the Riemannian logarithm of a point. Parameters ---------- point : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space. Returns ------- log : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at the base point equal to the Riemannian logarithm of point at the base point. """ flat_bp = gs.reshape(base_point, (-1, self.sphere_metric.dim + 1)) flat_pt = gs.reshape(point, (-1, self.sphere_metric.dim + 1)) flat_log = self.sphere_metric.log(flat_pt, flat_bp) try: log = gs.reshape(flat_log, base_point.shape) except (RuntimeError, check_tf_error(ValueError, 'InvalidArgumentError')): log = gs.reshape(flat_log, point.shape) return log
class PreShapeMetric(RiemannianMetric): """Procrustes metric on the pre-shape space. Parameters ---------- k_landmarks : int Number of landmarks m_ambient : int Number of coordinates of each landmark. """ def __init__(self, k_landmarks, m_ambient): super(PreShapeMetric, self).__init__(dim=m_ambient * (k_landmarks - 1) - 1, default_point_type='matrix') self.embedding_metric = MatricesMetric(k_landmarks, m_ambient) self.sphere_metric = Hypersphere(m_ambient * k_landmarks - 1).metric self.k_landmarks = k_landmarks self.m_ambient = m_ambient def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None): """Compute the inner-product of two tangent vectors at a base point. Parameters ---------- tangent_vec_a : array-like, shape=[..., k_landmarks, m_ambient] First tangent vector at base point. tangent_vec_b : array-like, shape=[..., k_landmarks, m_ambient] Second tangent vector at base point. base_point : array-like, shape=[..., dk_landmarks, m_ambient] Point on the pre-shape space. Returns ------- inner_prod : array-like, shape=[...,] Inner-product of the two tangent vectors. """ inner_prod = self.embedding_metric.inner_product( tangent_vec_a, tangent_vec_b, base_point) return inner_prod def exp(self, tangent_vec, base_point): """Compute the Riemannian exponential of a tangent vector. Parameters ---------- tangent_vec : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at a base point. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space. Returns ------- exp : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space equal to the Riemannian exponential of tangent_vec at the base point. """ flat_bp = gs.reshape(base_point, (-1, self.sphere_metric.dim + 1)) flat_tan = gs.reshape(tangent_vec, (-1, self.sphere_metric.dim + 1)) flat_exp = self.sphere_metric.exp(flat_tan, flat_bp) return gs.reshape(flat_exp, tangent_vec.shape) def log(self, point, base_point): """Compute the Riemannian logarithm of a point. Parameters ---------- point : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space. Returns ------- log : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at the base point equal to the Riemannian logarithm of point at the base point. """ flat_bp = gs.reshape(base_point, (-1, self.sphere_metric.dim + 1)) flat_pt = gs.reshape(point, (-1, self.sphere_metric.dim + 1)) flat_log = self.sphere_metric.log(flat_pt, flat_bp) try: log = gs.reshape(flat_log, base_point.shape) except (RuntimeError, check_tf_error(ValueError, 'InvalidArgumentError')): log = gs.reshape(flat_log, point.shape) return log def curvature(self, tangent_vec_a, tangent_vec_b, tangent_vec_c, base_point): r"""Compute the curvature. For three tangent vectors at a base point :math: `x,y,z`, the curvature is defined by :math: `R(X, Y)Z = \nabla_{[X,Y]}Z - \nabla_X\nabla_Y Z + - \nabla_Y\nabla_X Z`, where :math: `\nabla` is the Levi-Civita connection. In the case of the hypersphere, we have the closed formula :math: `R(X,Y)Z = \langle X, Z \rangle Y - \langle Y,Z \rangle X`. Parameters ---------- tangent_vec_a : array-like, shape=[..., n, n] Tangent vector at `base_point`. tangent_vec_b : array-like, shape=[..., n, n] Tangent vector at `base_point`. tangent_vec_c : array-like, shape=[..., n, n] Tangent vector at `base_point`. base_point : array-like, shape=[..., n, n] Point on the group. Optional, default is the identity. Returns ------- curvature : array-like, shape=[..., n, n] Tangent vector at `base_point`. """ max_shape = base_point.shape for arg in [tangent_vec_a, tangent_vec_b, tangent_vec_c]: if arg.ndim >= 3: max_shape = arg.shape flat_shape = (-1, self.sphere_metric.dim + 1) flat_a = gs.reshape(tangent_vec_a, flat_shape) flat_b = gs.reshape(tangent_vec_b, flat_shape) flat_c = gs.reshape(tangent_vec_c, flat_shape) flat_bp = gs.reshape(base_point, flat_shape) curvature = self.sphere_metric.curvature(flat_a, flat_b, flat_c, flat_bp) curvature = gs.reshape(curvature, max_shape) return curvature
def __init__(self, nodes): self.total_space_metric = MatricesMetric(nodes, nodes) self.nodes = nodes self.space = _GraphSpace(nodes)
class GraphSpaceMetric: """Quotient metric on the graph space. Parameters ---------- nodes : int Number of nodes """ def __init__(self, nodes): self.total_space_metric = MatricesMetric(nodes, nodes) self.nodes = nodes self.space = _GraphSpace(nodes) def dist(self, base_graph, graph_to_permute, matcher="ID"): """Compute distance between two equivalence classes. Compute the distance between two equivalence classes of adjacency matrices [Jain2009]_. Parameters ---------- base_graph : array-like, shape=[..., n, n] First graph. graph_to_permute : array-like, shape=[..., n, n] Second graph to align to the first graph. matcher : selecting which matcher to use 'FAQ': [Vogelstein2015]_ Fast Quadratic Assignment note: use Frobenius metric in background. Returns ------- distance : array-like, shape=[...,] distance between equivalence classes. References ---------- ..[Jain2009] Jain, B., Obermayer, K. "Structure Spaces." Journal of Machine Learning Research 10.11 (2009). https://www.jmlr.org/papers/v10/jain09a.html. ..[Vogelstein2015] Vogelstein JT, Conroy JM, Lyzinski V, Podrazik LJ, Kratzer SG, Harley ET, Fishkind DE, Vogelstein RJ, Priebe CE. “Fast approximate quadratic programming for graph matching.“ PLoS One. 2015 Apr 17; doi: 10.1371/journal.pone.0121002. """ if matcher == "FAQ": perm = self.faq_matching(base_graph, graph_to_permute) if matcher == "ID": perm = self.id_matching(base_graph, graph_to_permute) return self.total_space_metric.dist( base_graph, self.space.permute(graph_to_permute=graph_to_permute, permutation=perm), ) @staticmethod def faq_matching(base_graph, graph_to_permute): """Fast Quadratic Assignment for graph matching. Parameters ---------- base_graph : array-like, shape=[..., n, n] First graph. graph_to_permute : array-like, shape=[..., n, n] Second graph to align. Returns ------- permutation : array-like, shape=[...,n] node permutation indexes of the second graph. References ---------- ..[Vogelstein2015] Vogelstein JT, Conroy JM, Lyzinski V, Podrazik LJ, Kratzer SG, Harley ET, Fishkind DE, Vogelstein RJ, Priebe CE. “Fast approximate quadratic programming for graph matching.“ PLoS One. 2015 Apr 17; doi: 10.1371/journal.pone.0121002. """ l_base = len(base_graph.shape) l_obj = len(graph_to_permute.shape) if l_base == l_obj == 3: return [ gs.linalg.quadratic_assignment(x, y, options={"maximize": True}) for x, y in zip(base_graph, graph_to_permute) ] if l_base == l_obj == 2: return gs.linalg.quadratic_assignment(base_graph, graph_to_permute, options={"maximize": True}) if l_base < l_obj: return [ gs.linalg.quadratic_assignment(x, y, options={"maximize": True}) for x, y in zip( gs.stack([base_graph] * graph_to_permute.shape[0]), graph_to_permute) ] raise ValueError( "The method can align a set of graphs to one graphs," "but the single graphs should be passed as base_graph") def id_matching(self, base_graph, graph_to_permute): """Identity matching. Parameters ---------- base_graph : array-like, shape=[..., n, n] First graph. graph_to_permute : array-like, shape=[..., n, n] Second graph to align. Returns ------- permutation : array-like, shape=[...,n] node permutation indexes of the second graph. """ l_base = len(base_graph.shape) l_obj = len(graph_to_permute.shape) if l_base == l_obj == 3 or l_base < l_obj: return [list(range(self.nodes))] * len(graph_to_permute) if l_base == l_obj == 2: return list(range(self.nodes)) raise (ValueError( "The method can align a set of graphs to one graphs," "but the single graphs should be passed as base_graph"))