Manopt.jl
Manopt.jl is a package providing solvers for optimization problems defined on Riemannian manifolds. The implementation is based on ManifoldsBase.jl interface and can hence be used for all maniolds defined in Manifolds or any other manifold implemented using the interface.
Installation: OptimizationManopt.jl
To use the Optimization.jl interface to Manopt, install the OptimizationManopt package:
import Pkg;
Pkg.add("OptimizationManopt");Methods
The following methods are available for the OptimizationManopt package:
GradientDescentOptimizer: Corresponds to thegradient_descentmethod in Manopt.NelderMeadOptimizer: Corresponds to theNelderMeadmethod in Manopt.ConjugateGradientDescentOptimizer: Corresponds to theconjugate_gradient_descentmethod in Manopt.ParticleSwarmOptimizer: Corresponds to theparticle_swarmmethod in Manopt.QuasiNewtonOptimizer: Corresponds to thequasi_Newtonmethod in Manopt.CMAESOptimizer: Corresponds to thecma_esmethod in Manopt.ConvexBundleOptimizer: Corresponds to theconvex_bundle_methodmethod in Manopt.FrankWolfeOptimizer: Corresponds to theFrankWolfemethod in Manopt.AdaptiveRegularizationCubicOptimizer: Corresponds to theadaptive_regularization_with_cubicsmethod in Manopt.TrustRegionsOptimizer: Corresponds to thetrust_regionsmethod in Manopt.
OptimizationManopt.AbstractManoptOptimizer — Type
abstract type AbstractManoptOptimizer endA Manopt solver without things specified by a call to solve (stopping criteria) and internal state.
OptimizationManopt.GradientDescentOptimizer — Type
GradientDescentOptimizer()Manopt gradient descent optimizer for manifold-valued Optimization.jl problems.
OptimizationManopt.NelderMeadOptimizer — Type
NelderMeadOptimizer()Manopt Nelder-Mead optimizer for derivative-free manifold optimization.
OptimizationManopt.ConjugateGradientDescentOptimizer — Type
ConjugateGradientDescentOptimizer()Manopt conjugate gradient descent optimizer for manifold-valued problems.
OptimizationManopt.ParticleSwarmOptimizer — Type
ParticleSwarmOptimizer()Manopt particle swarm optimizer for derivative-free manifold optimization.
OptimizationManopt.QuasiNewtonOptimizer — Type
QuasiNewtonOptimizer()Manopt quasi-Newton optimizer for manifold-valued problems.
OptimizationManopt.CMAESOptimizer — Type
CMAESOptimizer()Manopt covariance matrix adaptation evolution strategy optimizer.
OptimizationManopt.ConvexBundleOptimizer — Type
ConvexBundleOptimizer()Manopt convex bundle method optimizer for manifold-valued problems.
OptimizationManopt.FrankWolfeOptimizer — Type
FrankWolfeOptimizer()Manopt Frank-Wolfe optimizer for constrained manifold-valued problems.
OptimizationManopt.AdaptiveRegularizationCubicOptimizer — Type
AdaptiveRegularizationCubicOptimizer()Manopt adaptive regularization with cubics optimizer. Uses the Hessian when the OptimizationFunction supplies one.
OptimizationManopt.TrustRegionsOptimizer — Type
TrustRegionsOptimizer()Manopt Riemannian trust-regions optimizer. Uses the Hessian when the OptimizationFunction supplies one.
The common kwargs maxiters, maxtime and abstol are supported by all the optimizers. Solver specific kwargs from Manopt can be passed to the solve function or OptimizationProblem.
Passing maxiters/maxtime bounds the run while keeping the convergence part of the solver's own Manopt default stopping criterion active, so a run that converges before hitting the bound reports retcode = Success rather than MaxIters/MaxTime. For the derivative-free solvers (NelderMeadOptimizer, ParticleSwarmOptimizer, CMAESOptimizer) and ConvexBundleOptimizer, Manopt's own stopping criteria never indicate convergence, so a run bounded only by maxiters/maxtime reports MaxIters/MaxTime. A Manopt stopping_criterion passed as a solver kwarg is combined with the requested maxiters/maxtime bounds and replaces the default convergence criterion.
Reexported Manopt.jl API
The optimizers listed above are defined by OptimizationManopt itself; they are thin wrappers whose names this package owns. From Manopt.jl only the Manopt module binding is re-exported, so that using OptimizationManopt is enough to write Manopt.ArmijoLinesearch(M) as the examples below do.
Manopt's solver options are passed to solve and keep that qualified spelling:
- Stepsizes, e.g.
Manopt.ArmijoLinesearch,Manopt.WolfePowellLinesearch - Stopping criteria, e.g.
Manopt.StopAfterIteration,Manopt.StopWhenGradientNormLess - Quasi-Newton update rules, e.g.
Manopt.InverseBFGS,Manopt.SR1 - Debug and record actions, e.g.
Manopt.DebugCost,Manopt.RecordIterate
Manopt's own surface is roughly 480 names and includes solve!, BFGS, NelderMead and getindex, so it is deliberately not blanket-reexported: doing so shadowed the SciML solve! and collided with Optim.jl's optimizer names.
Anything else from Manopt.jl must be reached through the Manopt module or imported from Manopt directly.
Examples
The Rosenbrock function on the Euclidean manifold can be optimized using the GradientDescentOptimizer as follows:
using OptimizationBase, OptimizationManopt, Manifolds, Manopt, LinearAlgebra, ADTypes, Zygote
rosenbrock(x, p) = (p[1] - x[1])^2 + p[2] * (x[2] - x[1]^2)^2
x0 = zeros(2)
p = [1.0, 100.0]
R2 = Euclidean(2)
stepsize = Manopt.ArmijoLinesearch(R2)
opt = OptimizationManopt.GradientDescentOptimizer()
optf = OptimizationFunction(rosenbrock, ADTypes.AutoZygote())
prob = OptimizationProblem(
optf, x0, p; manifold = R2, stepsize = stepsize, maxiters = 25000)
sol = OptimizationBase.solve(prob, opt)retcode: Success
u: 2-element Vector{Float64}:
0.9999999960724049
0.9999999921503789Plain gradient descent zig-zags slowly down Rosenbrock's narrow curved valley, so it needs tens of thousands of iterations (a few seconds here) to bring the gradient norm below the default tolerance and report retcode = Success.
The box-constrained Karcher mean problem on the SPD manifold with the Frank-Wolfe algorithm can be solved as follows:
M = SymmetricPositiveDefinite(5)
m = 100
σ = 0.005
q = Matrix{Float64}(I, 5, 5) .+ 2.0
data2 = [exp(M, q, σ * rand(M; vector_at = q)) for i in 1:m]
f(x, p = nothing) = sum(distance(M, x, data2[i])^2 for i in 1:m)
function closed_form_solution!(M::SymmetricPositiveDefinite, q, L, U, p, X)
# extract p^1/2 and p^{-1/2}
(p_sqrt_inv, p_sqrt) = Manifolds.spd_sqrt_and_sqrt_inv(p)
# Compute D & Q
e2 = eigen(p_sqrt_inv * X * p_sqrt_inv) # decompose Sk = QDQ'
D = Diagonal(1.0 .* (e2.values .< 0))
Q = e2.vectors
Uprime = Q' * p_sqrt_inv * U * p_sqrt_inv * Q
Lprime = Q' * p_sqrt_inv * L * p_sqrt_inv * Q
P = cholesky(Hermitian(Uprime - Lprime))
z = P.U' * D * P.U + Lprime
copyto!(M, q, p_sqrt * Q * z * Q' * p_sqrt)
return q
end
N = m
U = mean(data2)
L = inv(sum(1 / N * inv(matrix) for matrix in data2))
optf = OptimizationFunction(f, ADTypes.AutoZygote())
prob = OptimizationProblem(optf, U; manifold = M, maxiters = 5000)
opt = OptimizationManopt.FrankWolfeOptimizer()
sol = OptimizationBase.solve(
prob, opt, sub_problem = (M, q, p, X) -> closed_form_solution!(M, q, L, U, p, X),
evaluation = Manopt.InplaceEvaluation())retcode: Success
u: 5×5 Matrix{Float64}:
3.0012 2.00113 2.00145 2.00115 2.00124
2.00113 3.001 2.00129 2.00109 2.00118
2.00145 2.00129 3.00165 2.00131 2.00143
2.00115 2.00109 2.00131 3.00105 2.00118
2.00124 2.00118 2.00143 2.00118 3.00125This example is based on the example in the Manopt and Weber and Sra'22.
The following example is adapted from the Rayleigh Quotient example in ManoptExamples.jl. We solve the Rayleigh quotient problem on the Sphere manifold:
using OptimizationBase, OptimizationManopt
using Manifolds, LinearAlgebra
using Manopt
n = 1000
A = Symmetric(randn(n, n) / n)
manifold = Sphere(n - 1)
cost(x, p = nothing) = -x' * A * x
egrad(G, x, p = nothing) = (G .= -2 * A * x)
optf = OptimizationFunction(cost, grad = egrad)
x0 = rand(manifold)
prob = OptimizationProblem(optf, x0, manifold = manifold, maxiters = 5000)
sol = solve(prob, GradientDescentOptimizer())retcode: Success
u: 1000-element Vector{Float64}:
-0.025071720443232977
-0.044498583041258595
0.06562110482257963
-0.06688777116434928
-0.012899040545583133
-0.010812757375816946
0.01935231113614613
0.03819056573772199
0.07748134819264248
-0.02340532464344354
⋮
-0.032021640044428756
-0.006158853362377555
-0.0653715843748964
-0.054007457102606
-0.005308799999218389
0.010946259076707118
-0.016730224769438317
-0.008024940454329953
-0.021834943093882907Let's check that this indeed corresponds to the minimum eigenvalue of the matrix A.
@show eigmin(A)
@show sol.objective-0.06240252665529942