Optimizers
An Optimizer
stores an OptimizationAlgorithm
, a MultivariateObjective
, the SimpleSolvers.OptimizerResult
and a SimpleSolvers.NonlinearMethod
. Its purposes are:
using SimpleSolvers
using LinearAlgebra: norm
x = rand(3)
obj = MultivariateObjective(x -> sum((x - [0., 0., 1.]) .^ 2), x)
bt = Backtracking()
alg = Newton()
opt = Optimizer(x, obj; algorithm = alg, linesearch = bt)
* Algorithm: Newton
* Linesearch: Backtracking with α₀ = 1.0, ϵ = 0.0001and p = 0.5.
* Iterations
n = 0
* Convergence measures
|x - x'| = NaN ≰ 4.4e-16
|x - x'|/|x'| = NaN ≰ 4.4e-16
|f(x) - f(x')| = NaN ≰ 0.0e+00
|f(x) - f(x')|/|f(x')| = NaN ≰ 4.4e-16
|g(x)| = NaN ≰ 1.5e-08
* Candidate solution
Final solution value: [NaN, NaN, NaN]
Final objective value: NaN
Optimizer Constructor
Internally the constructor for Optimizer
calls SimpleSolvers.OptimizerResult
and SimpleSolvers.NewtonOptimizerState
and Hessian
. We can also allocate these objects manually and then call a different constructor for Optimizer
:
using SimpleSolvers: NewtonOptimizerState, OptimizerResult, initialize!
result = OptimizerResult(x, value!(obj, x))
initialize!(result, x)
state = NewtonOptimizerState(x; linesearch = bt)
hes = Hessian(alg, obj, x)
opt₂ = Optimizer(alg, obj, hes, result, state)
* Algorithm: Newton
* Linesearch: Backtracking with α₀ = 1.0, ϵ = 0.0001and p = 0.5.
* Iterations
n = 0
* Convergence measures
|x - x'| = NaN ≰ 4.4e-16
|x - x'|/|x'| = NaN ≰ 4.4e-16
|f(x) - f(x')| = NaN ≰ 0.0e+00
|f(x) - f(x')|/|f(x')| = NaN ≰ 4.4e-16
|g(x)| = NaN ≰ 1.5e-08
* Candidate solution
Final solution value: [NaN, NaN, NaN]
Final objective value: NaN
If we want to solve the problem, we can call solve!
on the Optimizer
instance:
x₀ = copy(x)
solve!(opt, x₀)
3-element Vector{Float64}:
0.0
0.0
1.0
Internally SimpleSolvers.solve!
repeatedly calls SimpleSolvers.solver_step!
until SimpleSolvers.meets_stopping_criteria
is satisfied.
using SimpleSolvers: solver_step!
x = rand(3)
solver_step!(opt, x)
3-element Vector{Float64}:
0.0
0.0
1.0
The function SimpleSolvers.solver_step!
in turn does the following:
# update objective, hessian, state and result
update!(opt, x)
# solve H δx = - ∇f
ldiv!(direction(opt), hessian(opt), rhs(opt))
# apply line search
α = linesearch(state(opt))(linesearch_objective(objective(opt), cache(opt)))
# compute new minimizer
x .= compute_new_iterate(x, α, direction(opt))
Solving the Line Search Problem with Backtracking
Calling an instance of SimpleSolvers.LinesearchState
(in this case SimpleSolvers.BacktrackingState
) on an SimpleSolvers.AbstractUnivariateObjective
in turn does:
α *= ls.p
as long as the SimpleSolvers.SufficientDecreaseCondition
isn't satisfied. This condition checks the following:
fₖ₊₁ ≤ sdc.fₖ + sdc.c₁ * αₖ * sdc.pₖ' * sdc.gradₖ
sdc
is first allocated as:
ls = linesearch(opt)
α = ls.α₀
x₀ = zero(α)
lso = linesearch_objective(objective(opt), cache(opt))
y₀ = value!(lso, x₀)
d₀ = derivative!(lso, x₀)
sdc = SufficientDecreaseCondition(ls.ϵ, x₀, y₀, d₀, d₀, obj)
SimpleSolvers.SufficientDecreaseCondition{Float64, Float64, Float64, MultivariateObjective{Float64, Vector{Float64}, Main.var"#1#2", GradientAutodiff{Float64, Main.var"#1#2", ForwardDiff.GradientConfig{ForwardDiff.Tag{Main.var"#1#2", Float64}, Float64, 3, Vector{ForwardDiff.Dual{ForwardDiff.Tag{Main.var"#1#2", Float64}, Float64, 3}}}}, Float64, Vector{Float64}}}(0.0001, 0.0, 0.6841489676563611, -1.3682979353127223, -1.3682979353127223, MultivariateObjective (for vector-valued quantities only the first component is printed):
f(x) = 6.84e-01
g(x)₁ = 3.82e-01
x_f₁ = 1.91e-01
x_g₁ = 1.91e-01
number of f calls = 5
number of g calls = 5
)