/
NelderMead.jl
148 lines (139 loc) · 5.23 KB
/
NelderMead.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
@doc raw"""
NelderMead(M, F [, p])
perform a nelder mead minimization problem for the cost function `F` on the
manifold `M`. If the initial population `p` is not given, a random set of
points is chosen.
This algorithm is adapted from the Euclidean Nelder-Mead method, see
[https://en.wikipedia.org/wiki/Nelder–Mead_method](https://en.wikipedia.org/wiki/Nelder–Mead_method)
and
[http://www.optimization-online.org/DB_FILE/2007/08/1742.pdf](http://www.optimization-online.org/DB_FILE/2007/08/1742.pdf).
# Input
* `M` – a manifold ``\mathcal M``
* `F` – a cost function ``F:\mathcal M→ℝ`` to minimize
* `population` – (n+1 `random_point(M)`) an initial population of ``n+1`` points, where ``n``
is the dimension of the manifold `M`.
# Optional
* `stopping_criterion` – ([`StopAfterIteration`](@ref)`(2000)`) a [`StoppingCriterion`](@ref)
* `α` – (`1.`) reflection parameter (``α > 0``)
* `γ` – (`2.`) expansion parameter (``γ``)
* `ρ` – (`1/2`) contraction parameter, ``0 < ρ ≤ \frac{1}{2}``,
* `σ` – (`1/2`) shrink coefficient, ``0 < σ ≤ 1``
* `retraction_method` – (`default_retraction_method(M)`) the rectraction to use
* `inverse_retraction_method` - (`default_inverse_retraction_method(M)`) an inverse retraction to use.
and the ones that are passed to [`decorate_options`](@ref) for decorators.
!!! note
The manifold `M` used here has to either provide a `mean(M, pts)` or you have to
load `Manifolds.jl` to use its statistics part.
# Output
* either `x` the last iterate or the complete options depending on the optional
keyword `return_options`, which is false by default (hence then only `x` is
returned).
"""
function NelderMead(
M::AbstractManifold,
F::TF,
population=[random_point(M) for i in 1:(manifold_dimension(M) + 1)];
kwargs...,
) where {TF}
res_population = copy.(Ref(M), population)
return NelderMead!(M, F, res_population; kwargs...)
end
@doc raw"""
NelderMead(M, F [, p])
perform a Nelder Mead minimization problem for the cost function `F` on the
manifold `M`. If the initial population `p` is not given, a random set of
points is chosen. If it is given, the computation is done in place of `p`.
For more options see [`NelderMead`](@ref).
"""
function NelderMead!(
M::AbstractManifold,
F::TF,
population=[random_point(M) for i in 1:(manifold_dimension(M) + 1)];
stopping_criterion::StoppingCriterion=StopAfterIteration(200000),
α=1.0,
γ=2.0,
ρ=1 / 2,
σ=1 / 2,
retraction_method::AbstractRetractionMethod=default_retraction_method(M),
inverse_retraction_method::AbstractInverseRetractionMethod=default_inverse_retraction_method(
M
),
return_options=false,
kwargs..., #collect rest
) where {TF}
p = CostProblem(M, F)
o = NelderMeadOptions(
M,
population;
stopping_criterion=stopping_criterion,
α=α,
γ=γ,
ρ=ρ,
σ=σ,
retraction_method=retraction_method,
inverse_retraction_method=inverse_retraction_method,
)
o = decorate_options(o; kwargs...)
resultO = solve(p, o)
if return_options
return resultO
else
return get_solver_result(resultO)
end
end
#
# Solver functions
#
function initialize_solver!(p::CostProblem, o::NelderMeadOptions)
# init cost and x
o.costs = get_cost.(Ref(p), o.population)
return o.x = o.population[argmin(o.costs)] # select min
end
function step_solver!(p::CostProblem, o::NelderMeadOptions, ::Any)
m = mean(p.M, o.population)
ind = sortperm(o.costs) # reordering for cost and p, i.e. minimizer is at ind[1]
ξ = inverse_retract(p.M, m, o.population[last(ind)], o.inverse_retraction_method)
# reflect last
xr = retract(p.M, m, -o.α * ξ, o.retraction_method)
Costr = get_cost(p, xr)
# is it better than the worst but not better than the best?
if Costr >= o.costs[first(ind)] && Costr < o.costs[last(ind)]
# store as last
o.population[last(ind)] = xr
o.costs[last(ind)] = Costr
end
# --- Expansion ---
if Costr < o.costs[first(ind)] # reflected is better than fist -> expand
xe = retract(p.M, m, -o.γ * o.α * ξ, o.retraction_method)
Coste = get_cost(p, xe)
# successful? use the expanded, otherwise still use xr
o.population[last(ind)] .= Coste < Costr ? xe : xr
o.costs[last(ind)] = min(Coste, Costr)
end
# --- Contraction ---
if Costr > o.costs[ind[end - 1]] # even worse than second worst
s = (Costr < o.costs[last(ind)] ? -o.ρ : o.ρ)
xc = retract(p.M, m, s * ξ, o.retraction_method)
Costc = get_cost(p, xc)
if Costc < o.costs[last(ind)] # better than last ? -> store
o.population[last(ind)] = xc
o.costs[last(ind)] = Costc
end
end
# --- Shrink ---
for i in 2:length(ind)
o.population[ind[i]] = retract(
p.M,
o.population[ind[1]],
inverse_retract(
p.M, o.population[ind[1]], o.population[ind[i]], o.inverse_retraction_method
),
o.σ,
o.retraction_method,
)
# update cost
o.costs[ind[i]] = get_cost(p, o.population[ind[i]])
end
# store best
return o.x = o.population[argmin(o.costs)]
end