Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/src/optimization_packages/manopt.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Manopt.jl

[Manopt.jl](https://github.com/JuliaManifolds/Manopt.jl) is a package with implementations of a variety of optimization solvers on manifolds supported by
[Manifolds](https://github.com/JuliaManifolds/Manifolds.jl).
[Manopt.jl](https://github.com/JuliaManifolds/Manopt.jl) is a package providing solvers
for optimization problems defined on Riemannian manifolds.
The implementation is based on [ManifoldsBase.jl](https://github.com/JuliaManifolds/ManifoldsBase.jl) interface and can hence be used for all maniolds defined in
[Manifolds](https://github.com/JuliaManifolds/Manifolds.jl) or any other manifold implemented using the interface.

## Installation: OptimizationManopt.jl

Expand Down Expand Up @@ -29,7 +31,7 @@ The common kwargs `maxiters`, `maxtime` and `abstol` are supported by all the op
function or `OptimizationProblem`.

!!! note

The `OptimizationProblem` has to be passed the manifold as the `manifold` keyword argument.

## Examples
Expand Down
8 changes: 4 additions & 4 deletions lib/OptimizationManopt/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69"

[compat]
LinearAlgebra = "1.10"
ManifoldDiff = "0.3.10"
Manifolds = "0.9.18"
ManifoldsBase = "0.15.10"
Manopt = "0.4.63"
ManifoldDiff = "0.4"
Manifolds = "0.10"
ManifoldsBase = "1"
Manopt = "0.5"
Optimization = "4.4"
Reexport = "1.2"
julia = "1.10"
Expand Down
180 changes: 44 additions & 136 deletions lib/OptimizationManopt/src/OptimizationManopt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,15 @@ function call_manopt_optimizer(
loss,
gradF,
x0;
stopping_criterion::Union{Manopt.StoppingCriterion, Manopt.StoppingCriterionSet},
evaluation::AbstractEvaluationType = Manopt.AllocatingEvaluation(),
stepsize::Stepsize = ArmijoLinesearch(M),
hessF=nothing, # ignore that keyword for this solver
kwargs...)
opts = gradient_descent(M,
opts = Manopt.gradient_descent(M,
loss,
gradF,
x0;
return_state = true,
evaluation,
stepsize,
stopping_criterion,
kwargs...)
# we unwrap DebugOptions here
return_state = true, # return the (full, decorated) solver state
kwargs...
)
minimizer = Manopt.get_solver_result(opts)
return (; minimizer = minimizer, minimum = loss(M, minimizer), options = opts)
end
Expand All @@ -90,13 +85,9 @@ function call_manopt_optimizer(M::ManifoldsBase.AbstractManifold, opt::NelderMea
loss,
gradF,
x0;
stopping_criterion::Union{Manopt.StoppingCriterion, Manopt.StoppingCriterionSet},
kwargs...)
opts = NelderMead(M,
loss;
return_state = true,
stopping_criterion,
hessF=nothing, # ignore that keyword for this solver
kwargs...)
opts = NelderMead(M, loss; return_state = true, kwargs...)
minimizer = Manopt.get_solver_result(opts)
return (; minimizer = minimizer, minimum = loss(M, minimizer), options = opts)
end
Expand All @@ -109,20 +100,15 @@ function call_manopt_optimizer(M::ManifoldsBase.AbstractManifold,
loss,
gradF,
x0;
stopping_criterion::Union{Manopt.StoppingCriterion, Manopt.StoppingCriterionSet},
evaluation::AbstractEvaluationType = InplaceEvaluation(),
stepsize::Stepsize = ArmijoLinesearch(M),
hessF=nothing, # ignore that keyword for this solver
kwargs...)
opts = conjugate_gradient_descent(M,
opts = Manopt.conjugate_gradient_descent(M,
loss,
gradF,
x0;
return_state = true,
evaluation,
stepsize,
stopping_criterion,
kwargs...)
# we unwrap DebugOptions here
kwargs...
)
minimizer = Manopt.get_solver_result(opts)
return (; minimizer = minimizer, minimum = loss(M, minimizer), options = opts)
end
Expand All @@ -135,25 +121,11 @@ function call_manopt_optimizer(M::ManifoldsBase.AbstractManifold,
loss,
gradF,
x0;
stopping_criterion::Union{Manopt.StoppingCriterion, Manopt.StoppingCriterionSet},
evaluation::AbstractEvaluationType = InplaceEvaluation(),
hessF=nothing, # ignore that keyword for this solver
population_size::Int = 100,
retraction_method::AbstractRetractionMethod = default_retraction_method(M),
inverse_retraction_method::AbstractInverseRetractionMethod = default_inverse_retraction_method(M),
vector_transport_method::AbstractVectorTransportMethod = default_vector_transport_method(M),
kwargs...)
initial_population = vcat([x0], [rand(M) for _ in 1:(population_size - 1)])
opts = particle_swarm(M,
loss;
x0 = initial_population,
n = population_size,
return_state = true,
retraction_method,
inverse_retraction_method,
vector_transport_method,
stopping_criterion,
kwargs...)
# we unwrap DebugOptions here
swarm = [x0, [rand(M) for _ in 1:(population_size - 1)]...]
opts = particle_swarm(M, loss, swarm; return_state = true, kwargs...)
minimizer = Manopt.get_solver_result(opts)
return (; minimizer = minimizer, minimum = loss(M, minimizer), options = opts)
end
Expand All @@ -167,28 +139,10 @@ function call_manopt_optimizer(M::Manopt.AbstractManifold,
loss,
gradF,
x0;
stopping_criterion::Union{Manopt.StoppingCriterion, Manopt.StoppingCriterionSet},
evaluation::AbstractEvaluationType = InplaceEvaluation(),
retraction_method::AbstractRetractionMethod = default_retraction_method(M),
vector_transport_method::AbstractVectorTransportMethod = default_vector_transport_method(M),
stepsize = WolfePowellLinesearch(M;
retraction_method = retraction_method,
vector_transport_method = vector_transport_method,
linesearch_stopsize = 1e-12),
hessF=nothing, # ignore that keyword for this solver
kwargs...
)
opts = quasi_Newton(M,
loss,
gradF,
x0;
return_state = true,
evaluation,
retraction_method,
vector_transport_method,
stepsize,
stopping_criterion,
kwargs...)
# we unwrap DebugOptions here
opts = quasi_Newton(M, loss, gradF, x0; return_state = true, kwargs...)
minimizer = Manopt.get_solver_result(opts)
return (; minimizer = minimizer, minimum = loss(M, minimizer), options = opts)
end
Expand All @@ -200,19 +154,9 @@ function call_manopt_optimizer(M::ManifoldsBase.AbstractManifold,
loss,
gradF,
x0;
stopping_criterion::Union{Manopt.StoppingCriterion, Manopt.StoppingCriterionSet},
evaluation::AbstractEvaluationType = InplaceEvaluation(),
retraction_method::AbstractRetractionMethod = default_retraction_method(M),
vector_transport_method::AbstractVectorTransportMethod = default_vector_transport_method(M),
basis = Manopt.DefaultOrthonormalBasis(),
hessF=nothing, # ignore that keyword for this solver
kwargs...)
opt = cma_es(M,
loss,
x0;
return_state = true,
stopping_criterion,
kwargs...)
# we unwrap DebugOptions here
opt = cma_es(M, loss, x0; return_state = true, kwargs...)
minimizer = Manopt.get_solver_result(opt)
return (; minimizer = minimizer, minimum = loss(M, minimizer), options = opt)
end
Expand All @@ -224,22 +168,9 @@ function call_manopt_optimizer(M::ManifoldsBase.AbstractManifold,
loss,
gradF,
x0;
stopping_criterion::Union{Manopt.StoppingCriterion, Manopt.StoppingCriterionSet},
evaluation::AbstractEvaluationType = InplaceEvaluation(),
retraction_method::AbstractRetractionMethod = default_retraction_method(M),
vector_transport_method::AbstractVectorTransportMethod = default_vector_transport_method(M),
hessF=nothing, # ignore that keyword for this solver
kwargs...)
opt = convex_bundle_method!(M,
loss,
gradF,
x0;
return_state = true,
evaluation,
retraction_method,
vector_transport_method,
stopping_criterion,
kwargs...)
# we unwrap DebugOptions here
opt = convex_bundle_method(M, loss, gradF, x0; return_state = true, kwargs...)
minimizer = Manopt.get_solver_result(opt)
return (; minimizer = minimizer, minimum = loss(M, minimizer), options = opt)
end
Expand All @@ -252,21 +183,13 @@ function call_manopt_optimizer(M::ManifoldsBase.AbstractManifold,
gradF,
x0;
hessF = nothing,
stopping_criterion::Union{Manopt.StoppingCriterion, Manopt.StoppingCriterionSet},
evaluation::AbstractEvaluationType = InplaceEvaluation(),
retraction_method::AbstractRetractionMethod = default_retraction_method(M),
kwargs...)
opt = adaptive_regularization_with_cubics(M,
loss,
gradF,
hessF,
x0;
return_state = true,
evaluation,
retraction_method,
stopping_criterion,
kwargs...)
# we unwrap DebugOptions here

opt = if isnothing(hessF)
adaptive_regularization_with_cubics(M, loss, gradF, x0; return_state = true, kwargs...)
else
adaptive_regularization_with_cubics(M, loss, gradF, hessF, x0; return_state = true, kwargs...)
end
minimizer = Manopt.get_solver_result(opt)
return (; minimizer = minimizer, minimum = loss(M, minimizer), options = opt)
end
Expand All @@ -279,21 +202,12 @@ function call_manopt_optimizer(M::ManifoldsBase.AbstractManifold,
gradF,
x0;
hessF = nothing,
stopping_criterion::Union{Manopt.StoppingCriterion, Manopt.StoppingCriterionSet},
evaluation::AbstractEvaluationType = InplaceEvaluation(),
retraction_method::AbstractRetractionMethod = default_retraction_method(M),
kwargs...)
opt = trust_regions(M,
loss,
gradF,
hessF,
x0;
return_state = true,
evaluation,
retraction = retraction_method,
stopping_criterion,
kwargs...)
# we unwrap DebugOptions here
opt = if isnothing(hessF)
trust_regions(M, loss, gradF, x0; return_state = true, kwargs...)
else
trust_regions(M, loss, gradF, hessF, x0; return_state = true, kwargs...)
end
minimizer = Manopt.get_solver_result(opt)
return (; minimizer = minimizer, minimum = loss(M, minimizer), options = opt)
end
Expand All @@ -305,22 +219,9 @@ function call_manopt_optimizer(M::ManifoldsBase.AbstractManifold,
loss,
gradF,
x0;
stopping_criterion::Union{Manopt.StoppingCriterion, Manopt.StoppingCriterionSet},
evaluation::AbstractEvaluationType = InplaceEvaluation(),
retraction_method::AbstractRetractionMethod = default_retraction_method(M),
stepsize::Stepsize = DecreasingStepsize(; length = 2.0, shift = 2),
kwargs...)
opt = Frank_Wolfe_method(M,
loss,
gradF,
x0;
return_state = true,
evaluation,
retraction_method,
stopping_criterion,
stepsize,
hessF=nothing, # ignore that keyword for this solver
kwargs...)
# we unwrap DebugOptions here
opt = Frank_Wolfe_method(M, loss, gradF, x0; return_state = true, kwargs...)
minimizer = Manopt.get_solver_result(opt)
return (; minimizer = minimizer, minimum = loss(M, minimizer), options = opt)
end
Expand All @@ -332,20 +233,25 @@ function SciMLBase.requiresgradient(opt::Union{
AdaptiveRegularizationCubicOptimizer, TrustRegionsOptimizer})
true
end
# TODO: WHY? they both still accept not passing it
function SciMLBase.requireshessian(opt::Union{
AdaptiveRegularizationCubicOptimizer, TrustRegionsOptimizer})
true
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this function defined and what is it for?

The current definition here is not correct, both ARC and TR can perform their own (actually quite good) approximation of the hessian – similar to what QN does.
So they do not need a Hessian, but the exact one of course performs a bit better than the approximate one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a trait for checking whether a solver requires that the Hessian function is required in order to use the solver. For example, if your solver uses prob.f.hess then this should be true, so that way you can fail if a second order AD method is not given.

if it's not required then this should be false. What this will do is, if true, turn on an error message that says "prob.f.hess is not defined and therefore you cannot use this method" (not exactly, but high level that's pretty much what it's for, for higher level error messages and reporting)

function build_loss(f::OptimizationFunction, prob, cb)
function (::AbstractManifold, θ)
# TODO: I do not understand this. Why is the manifold not used?
# Either this is an Euclidean cost, then we should probably still call `embed`,
# or it is not, then we need M.
return function (::AbstractManifold, θ)
Comment on lines +247 to +250
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we should check what best to do, the current one works in some cases, but not all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This I don't know. Do you need to know the manifold to know how to calculate the loss? I guess to know the mapping for some parameter values in some representations?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature of cost/grad/hess always has the first parameter as the manifold, since it allows to implement several costs for arbitrary manifolds, e..g. the Karcher mean to minimise the distances squared.

My main problem is that I do not understand which cost that is

  1. on a manifold it would contradict what the gradient function next does
  2. in the embedding we would have to call embed(M, θ) before passing it to the function f that is defined in the embedding.

as long as embed is the identity, like for SPDs and the sphere the current code works. But for fixed rank it for example would not work.

x = f.f(θ, prob.p)
cb(x, θ)
__x = first(x)
return prob.sense === Optimization.MaxSense ? -__x : __x
end
end

#TODO: What does the “true” mean here?
function build_gradF(f::OptimizationFunction{true})
function g(M::AbstractManifold, G, θ)
f.grad(G, θ)
Expand All @@ -356,6 +262,7 @@ function build_gradF(f::OptimizationFunction{true})
f.grad(G, θ)
return riemannian_gradient(M, θ, G)
end
return g
end

function build_hessF(f::OptimizationFunction{true})
Expand All @@ -373,6 +280,7 @@ function build_hessF(f::OptimizationFunction{true})
f.grad(G, θ)
return riemannian_Hessian(M, θ, G, H, X)
end
return h
end

function SciMLBase.__solve(cache::OptimizationCache{
Expand All @@ -395,8 +303,7 @@ function SciMLBase.__solve(cache::OptimizationCache{
LC,
UC,
S,
O <:
AbstractManoptOptimizer,
O <: AbstractManoptOptimizer,
D,
P,
C
Expand All @@ -418,6 +325,7 @@ function SciMLBase.__solve(cache::OptimizationCache{
u = θ,
p = cache.p,
objective = x[1])
#TODO: What is this callback for?
cb_call = cache.callback(opt_state, x...)
if !(cb_call isa Bool)
error("The callback should return a boolean `halt` for whether to stop the optimization process.")
Expand Down Expand Up @@ -452,7 +360,7 @@ function SciMLBase.__solve(cache::OptimizationCache{
solver_kwarg..., stopping_criterion = stopping_criterion, hessF)

asc = get_stopping_criterion(opt_res.options)
opt_ret = Manopt.indicates_convergence(asc) ? ReturnCode.Success : ReturnCode.Failure
opt_ret = Manopt.has_converged(asc) ? ReturnCode.Success : ReturnCode.Failure

return SciMLBase.build_solution(cache,
cache.opt,
Expand Down
Loading