A Decorator for manifolds

Several properties of a manifold are often implicitly assumed, for example the choice of the (Riemannian) metric, the group structure or the embedding. The latter shall serve as an example how to either implicitly or explicitly specify the embedding to avoid re-implementations and/or distinguish different embeddings.

The abstract decorator

When first implementing a manifold, it might be beneficial to dispatch certain computations to already existing manifolds. For an embedded manifold that is isometrically embedded this might be the inner the manifold inherits in each tangent space from its embedding.

This means we would like to dispatch the default implementation of a function to some other manifold. We refer to this as implicit decoration, since one can not “see” explicitly that a certain manifold inherits this property. As an example consider the Sphere. At each point the tangent space can be identified with a subspace of the tangent space in the embedding, the Euclidean manifold which the unit vectors of the sphere belong to. Thus every tangent space inherits its metric from the embedding. Since in the default implementation in Manifolds.jl points are represented by unit vectors and tangent vectors at a point as vectors orthogonal to that point, we can just dispatch the inner product to the embedding without having to re-implement this. The manifold using such an implicit dispatch just has to be a subtype of AbstractDecoratorManifold.

Traits with an inheritance hierarchy

The properties mentioned above might form a hierarchy. For embedded manifolds, again, we might have just a manifold whose points are represented in some embedding. If the manifold is even isometrically embedded, it is embedded but also inherits the Riemannian metric by restricting the metric from the embedding to the corresponding tangent space under consideration. But it also inherits the functions defined for the plain embedding, for example checking some conditions for the validity of points and vectors. If it is even a submanifold, also further functions are inherited like the shortest_geodesic.

We use a variation of Tim Holy's Traits Trick (THTT) which takes into account this nestedness of traits.

ManifoldsBase.AbstractDecoratorManifoldType
AbstractDecoratorManifold{𝔽} <: AbstractManifold{𝔽}

Declare a manifold to be an abstract decorator. A manifold which is a subtype of is a decorated manifold, i.e. has

  • certain additional properties or
  • delegates certain properties to other manifolds.

Most prominently, a manifold might be an embedded manifold, i.e. points on a manifold $\mathcal M$ are represented by (some, maybe not all) points on another manifold $\mathcal N$. Depending on the type of embedding, several functions are dedicated to the embedding. For example if the embedding is isometric, then the inner does not have to be implemented for $\mathcal M$ but can be automatically implemented by deligation to $\mathcal N$.

This is modelled by the AbstractDecoratorManifold and traits. These are mapped to functions, which determine the types of transparencies.

source
ManifoldsBase.IsExplicitDecoratorType
IsExplicitDecorator <: AbstractTrait

Specify that a certain type should dispatch per default to its decorated_manifold.

Note

Any decorator behind this decorator might not have any effect, since the function dispatch is moved to its field at this point. Therefore this decorator should always be last in the TraitList.

source
ManifoldsBase.TraitListType
TraitList <: AbstractTrait

Combine two traits into a combined trait. Note that this introduces a preceedence. the first of the traits takes preceedence if a trait is implemented for both functions.

Constructor

TraitList(head::AbstractTrait, tail::AbstractTrait)
source
ManifoldsBase.@next_trait_functionMacro
next_trait_function(trait_type, sig)

Define a special trait-handling method for function indicated by sig. It does not change the result but the presence of such additional methods may prevent method recursion limits in Julia's inference from being triggered. Some functions may work faster after adding methods generated by next_trait_function.

See the "Trait recursion breaking" section at the bottom of src/decorator_trait.jl file for an example of intended usage.

source
ManifoldsBase.active_traitsMethod
active_traits(f, args...)

Return the list of traits applicable to the given call of function f`. This function should be overloaded for specific function calls.

source
ManifoldsBase.merge_traitsMethod
merge_traits(t1, t2, trest...)

Merge two traits into a nested list of traits. Note that this takes trait preceedence into account, i.e. t1 takes preceedence over t2 is any operations. It always returns either ab EmptyTrait or a TraitList.

This means that for

  • one argument it just returns the trait itself if it is list-like, or wraps the trait in a single-element list otherwise,
  • two arguments that are list-like, it merges them,
  • two arguments of which only the first one is list-like and the second one is not, it appends the second argument to the list,
  • two arguments of which only the second one is list-like, it prepends the first one to the list,
  • two arguments of which none is list-like, it creates a two-element list.
  • more than two arguments it recursively performs a left-assiciative recursive reduction on arguments, that is for example merge_traits(t1, t2, t3) is equivalent to merge_traits(merge_traits(t1, t2), t3)
source
ManifoldsBase.next_traitMethod
next_trait(t::AbstractTrait)

Return the next trait to consider, which by default is no following trait (i.e. EmptyTrait).

Expecially for a a TraitList this function returns the (remaining) tail of the remaining traits.

source
ManifoldsBase.parent_traitMethod
parent_trait(t::AbstractTrait)

Return the parent trait for trait t, that is the more general trait whose behaviour it inherits as a fallback.

source

The key part of the trait system is that it forms a list of traits, from the most specific one to the least specific one, and tries to find a specific implementation of a function for a trait in the least. This ensures that there are, by design, no ambiguities (caused by traits) in the method selection process. Trait resolution is driven by Julia's method dispatch and the compiler is sufficiently clever to quite reliably constant-propagate traits and inline method calls.

The list of traits is browsed from the most specific one for implementation of a given function for that trait. If one is found, the implementation is called and it may internally call completely different function, breaking the trait dispatch chain. When no implementation for a trait is found, the next trait on the list is checked, until EmptyTrait is reached, which is conventionally the last trait to be considered, expected to have the most generic default implementation of a function If you want to continue with the following traits afterwards, use s =next_trait(t) of a TraitList t to continue working on the next trait in the list by calling the function with s as first argument.

The Manifold decorator

Based on the generic TraitList the following types, functions, and macros introduce the decorator trait which allows to decorate an arbitrary <:AbstractDecoratorManifold with further features.

ManifoldsBase.decorated_manifoldMethod
decorated_manifold(M::AbstractDecoratorManifold)

For a manifold M that is decorated with some properties, this function returns the manifold without that manifold, i.e. the manifold that was decorated.

source
ManifoldsBase.get_embeddingMethod
get_embedding(M::AbstractDecoratorManifold)
get_embedding(M::AbstractDecoratorManifold, p)

Specify the embedding of a manifold that has abstract decorators. the embedding might depend on a point representation, where different point representations are distinguished as subtypes of AbstractManifoldPoint. A unique or default representation might also just be an AbstractArray.

source

For an example see the (implicit) embedded manifold.