[ANN] ManifoldsBase.jl 1.0

ManifoldsBase.jl is moving to a stable version 1.0

A few years back, we in JuliaManifolds decided, that it would be nice to have a general interface package for the manifolds defined in Manifolds.jl, since that allows

  • other packages to use manifolds purely based on a lightweight manifolds API
  • other packages to define their own manifolds

Besides Manifolds.jl itself defining manifolds, from the start Manopt.jl was using abstract manifolds to define its optimisation algorithms.
By now, there are more packages defining their own manifolds, for example the ExponentialFamilyManifolds.jl
The API has grown into a very stable state with only one minor change to the exponential map and retractions in the version 1.0. This is mainly in preparation for a next forthcoming package, where we do require a slightly different signature as well. See our changelog for a full explanation of the breaking changes.

We will still work on providing a broader interface for more fatures on manifolds covered in the API; still, the current state is considered so stable now, that we are happy to call this version ManifoldsBase.jl 1.0!

22 Likes

This is very cool! I took a course on differential geometry once but I didn’t think it would quickly become relevant for me again when I took the computational science specialisation afterwards.

What sort of applications is this currently being used for? I suppose General Relativity?

1 Like

Actually manifolds are a very neat way to describe any structure of continuous data, especially constraints. We can then do optimization and statistics on such data. As a more concrete example, it’s frequently used in robotics and anything that deals with modelling orientations (special orthogonal and special Euclidean groups). Also, subspaces of vector spaces form neat manifolds (Stiefel, Grassman and flag). They are very relevant in statistical modelling.

We don’t do general relativity right now. We are trying to do as much as possible without coordinates, which doesn’t work for GR but is great in other applications. For me working with manifolds is just respecting structure of your data in a very broad sense and not necessarily dealing with curvature (though it’s one of the problems the approach addresses).

5 Likes

My personal main interest is developing optimization algorithm on arbitrary manifolds (see Manopt.jl), which might be beneficial whenever you have a problem naturally defined on a manifold, often a Sphere, a Stiefel or Grassmann manifold. for these works I do not necessarily directly have an application in mind.
A main focus I do my research on is the case where the objective you aim to minimise is non smooth, so we do not have a gradient but maybe a sub gradient.

Of course with Hyperbloic space anything related to general relativity is also possible, the main areas I have seen (though not yet worked un much) are Model order reduction, especially Hamiltonian systems and the symplectic Stiefel manifold, but also Density Function theory (cf. DFTK.jl) as problems on Stiefel manifolds,… but to be honest I personally am maybe not the most applied person for that.

If you have applications we are surely interested to help how manifolds can help – for example defining Bézier curves on manifolds is one of our examples, cf. 🚀 Get Started with Manifolds.jl · Manifolds.jl, which I came to when then even optimising its control points (with respect to a certain objective)

2 Likes

@kellertuer @mateuszbaran thank you both for your elaborate replies :slight_smile: I personally am interested in splines and related geometric objects (see my recent package SplineGrids.jl), so if you see possibilities for collaboration there I would love to hear it

2 Likes

Nice! I worked a bit with Bézier curves on manifolds, but also before with (Euclidean) splines in my masters.

As in the linkes example – one can easily generalise e.g. Casteljau’s algorithm to arbitrary manifolds and hence smoothly interpolate data in Hyperbolic space. I know a few works that even try that for multivariate data on grids, but then the tangent spaces and requirements become a bit tricky (cf. https://epubs.siam.org/doi/10.1137/16M1057978). But sure, one could in theory combine manifolds and Splines/Bézier curves.

2 Likes

Applications wise, I thought about the application of estimating the Fundamental / Homography matrix.

The problem is given by:

\arg \min_{\boldsymbol{F}} \sum_{i} \boldsymbol{x}_{i}^{T} \boldsymbol{F} \boldsymbol{y}_{i} \; \text{ subject to } \operatorname{rank} \left( \boldsymbol{F} \right) = 2

Where \boldsymbol{F} \in \mathbb{R}^{3 \times 3}.

I wonder if there is a manifold which forces the rank.
Could one solve using the current state of the packages?

1 Like

Sure! (Not only with the interface for sure but) We do have the

in Manifolds.jl. There are even retractions and inverse retractions available as well as gradient and hessian conversions. So (not having tried it but)

Note. The fixed rand manifold does not have exp/log defined, but the retractions are first-order approximations to that. Similarly parallel_transport would be the exact mathematical way required for QN, but there is a vector transport that should be taken as an approximation of that automatically instead.

If you get stuck somewhere, maybe open a new topic here, or an issue in one of the packages :slight_smile:

And if you know more about your matrix, for example that it is an orthonormal basis of a 2D subspace (a 3x2 matrix, excluding the one zero eigenspace basically, ±ONB) you can use the Stiefel · Manifolds.jl even, which would be lower in dimension (if that is of interest).

1 Like

We just released

ManifoldsBase.jl 2.0

this week. The major change is, that we reworked the internal traits system, which was previously a bit technical and lead to error messages that were hard to read (even for the developers).

So at JuliaCon 2025s hackathon we decided to rework this. We now have a simpler system of traits, that are used to indicate that a manifold is embedded or which metric it has.

In practice, if you are using manifolds, e.g. like Manopt.jl does, there should be no breaking changes.
If you define your own manifolds, the overall scheme is now more consistent between metrics, embeddings, maybe even with different embedding types, connections as decorators of a manifold. The one breaking change is possibly to remove the definition of the active_traits function.

4 Likes

I’ve tried just removing active_traits from my manifolds but then I get lots of undefined or ambiguous function errors. I really do not want to reimplement, when they have worked before? Is there a way to get the same functionality back?

project! does not seem to exist anymore for PowerManifold?

Hi,
the active traits were a complicated trait system causing long complicated error messages. That system has been replaced by a simpler system of just a single trait (instead of a whole list).

Usually, all old functionality should be still available completely the same way as before. So I would need a concrete example where and when which kind of code is failing. Note also that a few manifolds have changed their order of parameters, since we unified that to have F first everywhere now.
But there should not be any regression in funcvtionality. You can also check what we changed in Manifolds.jl when moving from ManifoldsBase 1.0 to 2.0, usually only a few lines.

No, that should still exist, we did not remove it as far as I am aware. Please be more precise where and when what fails, otherwise it is super hard to help when I have to guess what yo might be trying to do?
Are you implementing a manifold? are you using one?

For the update to ManifoldsBase.jl 2.0 for example this PR is just one manifold where you see differences that were done

Many thanks. This is my test case. Now that I look at it even zero_vector is missing.

using ManifoldsBase, Manifolds

struct SkewFunction{m,k,𝔽} <: AbstractDecoratorManifold{𝔽}
    manifold::Euclidean
end

function SkewFunction(m::Int, k::Int; field::AbstractNumbers=ℝ)
    manifold = Euclidean(m, k, field=field)
    return SkewFunction{m,k,field}(manifold)
end

ManifoldsBase.decorated_manifold(M::SkewFunction) = M.manifold
# ManifoldsBase.active_traits(f, ::SkewFunction, args...) = ManifoldsBase.IsExplicitDecorator()

embed(::SkewFunction, p) = p
embed(::SkewFunction, p, X) = X

function get_embedding(M::SkewFunction)
    return M.manifold
end

function ManifoldsBase.get_embedding_type(::SkewFunction)
    return ManifoldsBase.SimpleForwardingType()
end

function Base.zero(M::Euclidean{T,𝔽}) where {T,𝔽}
    if 𝔽 == ℂ
        return zeros(ComplexF64, representation_size(M)...)
    else
        return zeros(representation_size(M)...)
    end
end

function Base.zero(M::SkewFunction)
    return zero(M.manifold)
end

MM = SkewFunction(5, 12)
pp = zero(MM)
XX = zero_vector(MM, pp)
YY = zero_vector(MM, pp)

project!(MM, YY, pp, XX)

Made it work. Your example really helped.

using ManifoldsBase, Manifolds

struct SkewFunction{m,k,𝔽} <: AbstractDecoratorManifold{𝔽}
    manifold::Euclidean
end

function SkewFunction(m::Int, k::Int; field::AbstractNumbers=ℝ)
    manifold = Euclidean(m, k, field=field)
    return SkewFunction{m,k,field}(manifold)
end

@inline ManifoldsBase.decorated_manifold(M::SkewFunction) = M.manifold

# Forward all functions by default to the decorated base manifold
@inline ManifoldsBase.get_forwarding_type(::SkewFunction, ::Any) = ManifoldsBase.SimpleForwardingType()
@inline ManifoldsBase.get_forwarding_type(::SkewFunction, ::Any, _) = ManifoldsBase.SimpleForwardingType()

function Base.zero(M::Euclidean{T,𝔽}) where {T,𝔽}
    if 𝔽 == ℂ
        return zeros(ComplexF64, representation_size(M)...)
    else
        return zeros(representation_size(M)...)
    end
end

function Base.zero(M::SkewFunction)
    return zero(M.manifold)
end

MM = SkewFunction(5, 12)
pp = zero(MM)
XX = zero_vector(MM, pp)
YY = zero_vector(MM, pp)

project!(MM, YY, pp, XX)
1 Like

Nice. Yes the active traits (since now there is only one trait) is discontinues and we decide “where to forward” things to based on the new function get_forwarding_type

I am a bit surprised by your zero function, especially it is heavy type piracy if you define that for Eclidean. I would kindly ask you to not do that. If so, overwrite zero_vector (or zero_vector!) for you manifold.

1 Like

Sort of agree about zero function. In general, manifolds have no zero element, and I believe that is why you do not define one. I however need a default ‘zero’ to initialise the optimisation. I suppose, I should have used a different name, such as default_point. This is different from a zero tangent vector, for which there is zero_vector. Will think about it…

1 Like

Well, type piracy (you neither “own” zero nor Euclidean) should not be committed. You could use the TranslationGroup from LieGroups.jl (which is nothing else that Euclidean with plus) and use its identity_element) - or come up with your own names of course.

1 Like