Skip to content

Commit

Permalink
Refactor 'Manifold' base class
Browse files Browse the repository at this point in the history
This commit separates methods in the 'Manifold' base class into abstract
methods, which must be implemented by valid subclasses, and those which
are only required by certain solvers. If a subclass does not provide one
of those optional methods, we now raise a generic but informative
NotImplementedError.

The class constructor now also requires a 'name' and 'dimension'
argument, which are used in the generic implementations of the classes
'__str__' method and 'dim' property so that subclasses don't have to
provide this method/property manually.

Embedded submanifolds of Euclidean spaces offer particularly convenient
ways to convert Euclidean gradients and Hessian-vector products to their
Riemannian counterparts by means of tangent space projectors and
Weingarten maps, respectively. To ease implementations, we now provide a
'EuclideanEmbeddedSubmanifold' class, which, in addition to the required
abstract 'Manifold' methods, only requires descendants to implement the
'weingarten' method to support 'ehess2rhess'.

Signed-off-by: Niklas Koep <niklas.koep@gmail.com>
  • Loading branch information
nkoep committed Jan 25, 2020
1 parent 53b096a commit ec4e8fd
Showing 1 changed file with 119 additions and 69 deletions.
188 changes: 119 additions & 69 deletions pymanopt/manifolds/manifold.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import abc
import functools

import numpy as np


class Manifold:
class Manifold(metaclass=abc.ABCMeta):
"""
Abstract base class setting out a template for manifold classes. If you
would like to extend Pymanopt with a new manifold, then your manifold
Expand All @@ -24,116 +23,167 @@ class Manifold:
methods in this class have equivalents in Manopt with the same name).
"""

__metaclass__ = abc.ABCMeta
def __init__(self, name, dimension):
self._name = name
self._dimension = dimension

@abc.abstractmethod
def __str__(self):
"""
Name of the manifold
"""
"""Returns a string representation of the particular manifold."""
return self._name

@abc.abstractproperty
def _get_class_name(self):
return self.__class__.__name__

@property
def dim(self):
"""
Dimension of the manifold
"""
"""The dimension of the manifold"""
return self._dimension

@abc.abstractproperty
# Manifold properties that subclasses can define

@property
def typicaldist(self):
"""
Returns the "scale" of the manifold. This is used by the
trust-regions solver, to determine default initial and maximal
"""Returns the "scale" of the manifold. This is used by the
trust-regions solver to determine default initial and maximal
trust-region radii.
"""
raise NotImplementedError(
"Manifold class '{:s}' does not provide a 'typicaldist'".format(
self._get_class_name()))

# Abstract methods that subclasses must implement

@abc.abstractmethod
def dist(self, X, Y):
"""
Geodesic distance on the manifold
"""
"""Returns the geodesic distance between two points `X` and `Y` on the
manifold."""

@abc.abstractmethod
def inner(self, X, G, H):
"""
Inner product (Riemannian metric) on the tangent space
"""Returns the inner product (i.e., the Riemannian metric) between two
tangent vectors `G` and `H` in the tangent space at `X`.
"""

@abc.abstractmethod
def proj(self, X, G):
"""
Project into the tangent space. Usually the same as egrad2rgrad
"""Projects a vector `G` in the ambient space on the tangent space at
`X`.
"""

def egrad2rgrad(self, X, G):
"""
A mapping from the Euclidean gradient G into the tangent space
to the manifold at X. For embedded manifolds, this is simply the
projection of G on the tangent space at X.
"""
return self.proj(X, G)

@abc.abstractmethod
def ehess2rhess(self, X, Hess):
"""
Convert Euclidean into Riemannian Hessian.
def norm(self, X, G):
"""Computes the norm of a tangent vector `G` in the tangent space at
`X`.
"""

@abc.abstractmethod
def retr(self, X, G):
"""
A retraction mapping from the tangent space at X to the manifold.
See Absil for definition of retraction.
"""
def rand(self):
"""Returns a random point on the manifold."""

@abc.abstractmethod
def norm(self, X, G):
"""
Compute the norm of a tangent vector G, which is tangent to the
manifold at X.
def randvec(self, X):
"""Returns a random vector in the tangent space at `X`. This does not
follow a specific distribution.
"""

@abc.abstractmethod
def rand(self):
"""
A function which returns a random point on the manifold.
"""
def zerovec(self, X):
"""Returns the zero vector in the tangent space at X."""

@abc.abstractmethod
def randvec(self, X):
# Methods which are only required by certain solvers

def _raise_not_implemented_error(method):
"""Method decorator which raises a NotImplementedError with some meta
information about the manifold and method if a decorated method is
called.
"""
Returns a random, unit norm vector in the tangent space at X.
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
raise NotImplementedError(
"Manifold class '{:s}' provides no implementation for "
"'{:s}'".format(self._get_class_name(), method.__name__))
return wrapper

@_raise_not_implemented_error
def egrad2rgrad(self, X, G):
"""Maps the Euclidean gradient `G` in the ambient space on the tangent
space of the manifold at `X`. For embedded submanifolds, this is simply
the projection of `G` on the tangent space at `X`.
"""

@abc.abstractmethod
def transp(self, x1, x2, d):
@_raise_not_implemented_error
def ehess2rhess(self, X, G, H, U):
"""Converts the Euclidean gradient `G` and Hessian `H` of a function at
a point `X` along a tangent vector `U` to the Riemannian Hessian of `X`
along `U` on the manifold.
"""
Transports d, which is a tangent vector at x1, into the tangent
space at x2.

@_raise_not_implemented_error
def retr(self, X, G):
"""Computes a retraction mapping a vector `G` in the tangent space at
`X` to the manifold.
"""

@abc.abstractmethod
@_raise_not_implemented_error
def exp(self, X, U):
"""
The exponential (in the sense of Lie group theory) of a tangent
vector U at X.
"""Computes the Lie-theoretic exponential map of a tangent vector `U`
at `X`.
"""

@abc.abstractmethod
@_raise_not_implemented_error
def log(self, X, Y):
"""Computes the Lie-theoretic logarithm of `Y`. This is the inverse of
`exp`.
"""
The logarithm (in the sense of Lie group theory) of Y. This is the
inverse of exp.

@_raise_not_implemented_error
def transp(self, X1, X2, G):
"""Computes a vector transport which transports a vector `G` in the
tangent space at `X1` to the tangent space at `X2`.
"""

@abc.abstractmethod
@_raise_not_implemented_error
def pairmean(self, X, Y):
"""
Computes the intrinsic mean of X and Y, that is, a point that lies
mid-way between X and Y on the geodesic arc joining them.
"""
"""Returns the intrinsic mean of two points `X` and `Y` on the
manifold, i.e., a point that lies mid-way between `X` and `Y` on the
geodesic arc joining them.
"""


class EuclideanEmbeddedSubmanifold(Manifold, metaclass=abc.ABCMeta):
"""A class to model embedded submanifolds of a Euclidean space. It provides
a generic way to project Euclidean gradients to their Riemannian
counterparts via the `egrad2rgrad` method. Similarly, if the Weingarten map
(also known as shape operator) is provided via implementing the
'weingarten' method, the class provides a generic implementation of the
'ehess2rhess' method required by second-order solvers to translate
Euclidean Hessian-vector products to their Riemannian counterparts.
Notes
-----
Refer to [1]_ for the exact definition of the Weingarten map.
References
----------
.. [1] Absil, P-A., Robert Mahony, and Jochen Trumpf. "An extrinsic look at
the Riemannian Hessian." International Conference on Geometric Science
of Information. Springer, Berlin, Heidelberg, 2013.
"""

def zerovec(self, X):
def egrad2rgrad(self, X, G):
return self.proj(X, G)

def ehess2rhess(self, X, G, H, U):
"""Converts the Euclidean gradient `G` and Hessian `H` of a function at
a point `X` along a tangent vector `U` to the Riemannian Hessian of `X`
along `U` on the manifold. This uses the Weingarten map
"""
Returns the zero tangent vector at X.
normal_gradient = G - self.proj(X, G)
return self.proj(X, H) + self.weingarten(X, U, normal_gradient)

@Manifold._raise_not_implemented_error
def weingarten(self, X, U, V):
"""Evaluates the Weingarten map of the manifold. This map takes a
vector `U` in the tangent space at `X` and a vector `V` in the
normal space at `X` to produce another tangent vector.
"""
return np.zeros(np.shape(X))

0 comments on commit ec4e8fd

Please sign in to comment.