|
| 1 | +""" |
| 2 | +```julia |
| 3 | +TrustRegion(max_trust_radius::Number; chunk_size = Val{0}(), |
| 4 | + autodiff = Val{true}(), diff_type = Val{:forward}) |
| 5 | +``` |
| 6 | +
|
| 7 | +A low-overhead implementation of a |
| 8 | +[trust-region](https://optimization.mccormick.northwestern.edu/index.php/Trust-region_methods) |
| 9 | +solver |
| 10 | +
|
| 11 | +
|
| 12 | +### Keyword Arguments |
| 13 | +- `max_trust_radius`: the maximum radius of the trust region. The step size in the algorithm |
| 14 | + will change dynamically. However, it will never be greater than the `max_trust_radius`. |
| 15 | +
|
| 16 | +### Keyword Arguments |
| 17 | +
|
| 18 | +- `chunk_size`: the chunk size used by the internal ForwardDiff.jl automatic differentiation |
| 19 | + system. This allows for multiple derivative columns to be computed simultaneously, |
| 20 | + improving performance. Defaults to `0`, which is equivalent to using ForwardDiff.jl's |
| 21 | + default chunk size mechanism. For more details, see the documentation for |
| 22 | + [ForwardDiff.jl](https://juliadiff.org/ForwardDiff.jl/stable/). |
| 23 | +- `autodiff`: whether to use forward-mode automatic differentiation for the Jacobian. |
| 24 | + Note that this argument is ignored if an analytical Jacobian is passed; as that will be |
| 25 | + used instead. Defaults to `Val{true}`, which means ForwardDiff.jl is used by default. |
| 26 | + If `Val{false}`, then FiniteDiff.jl is used for finite differencing. |
| 27 | +- `diff_type`: the type of finite differencing used if `autodiff = false`. Defaults to |
| 28 | + `Val{:forward}` for forward finite differences. For more details on the choices, see the |
| 29 | + [FiniteDiff.jl](https://github.com/JuliaDiff/FiniteDiff.jl) documentation. |
| 30 | +""" |
| 31 | +struct TrustRegion{CS, AD, FDT} <: AbstractNewtonAlgorithm{CS, AD, FDT} |
| 32 | + max_trust_radius::Number |
| 33 | + function TrustRegion(max_turst_radius::Number; chunk_size = Val{0}(), |
| 34 | + autodiff = Val{true}(), |
| 35 | + diff_type = Val{:forward}) |
| 36 | + new{SciMLBase._unwrap_val(chunk_size), SciMLBase._unwrap_val(autodiff), |
| 37 | + SciMLBase._unwrap_val(diff_type)}(max_trust_radius) |
| 38 | + end |
| 39 | +end |
| 40 | + |
| 41 | +function SciMLBase.solve(prob::NonlinearProblem, |
| 42 | + alg::TrustRegion, args...; abstol = nothing, |
| 43 | + reltol = nothing, |
| 44 | + maxiters = 1000, kwargs...) |
| 45 | + f = Base.Fix2(prob.f, prob.p) |
| 46 | + x = float(prob.u0) |
| 47 | + T = typeof(x) |
| 48 | + Δₘₐₓ = float(alg.max_trust_radius) # The maximum trust region radius. |
| 49 | + Δ = Δₘₐₓ / 5 # Initial trust region radius. |
| 50 | + η₁ = 0.1 # Threshold for taking a step. |
| 51 | + η₂ = 0.25 # Threshold for shrinking the trust region. |
| 52 | + η₃ = 0.75 # Threshold for expanding the trust region. |
| 53 | + t₁ = 0.25 # Factor to shrink the trust region with. |
| 54 | + t₂ = 2.0 # Factor to expand the trust region with. |
| 55 | + |
| 56 | + if SciMLBase.isinplace(prob) |
| 57 | + error("TrustRegion currently only supports out-of-place nonlinear problems") |
| 58 | + end |
| 59 | + |
| 60 | + atol = abstol !== nothing ? abstol : |
| 61 | + real(oneunit(eltype(T))) * (eps(real(one(eltype(T)))))^(4 // 5) |
| 62 | + rtol = reltol !== nothing ? reltol : eps(real(one(eltype(T))))^(4 // 5) |
| 63 | + |
| 64 | + if alg_autodiff(alg) |
| 65 | + F, ∇f = value_derivative(f, x) |
| 66 | + elseif x isa AbstractArray |
| 67 | + F = f(x) |
| 68 | + ∇f = FiniteDiff.finite_difference_jacobian(f, x, diff_type(alg), eltype(x), F) |
| 69 | + else |
| 70 | + F = f(x) |
| 71 | + ∇f = FiniteDiff.finite_difference_derivative(f, x, diff_type(alg), eltype(x), F) |
| 72 | + end |
| 73 | + |
| 74 | + fₖ = 0.5 * norm(F)^2 |
| 75 | + H = ∇f * ∇f |
| 76 | + g = ∇f * F |
| 77 | + |
| 78 | + for k in 1:maxiters |
| 79 | + # Solve the trust region subproblem. |
| 80 | + δ = dogleg_method(H, g, Δ) |
| 81 | + xₖ₊₁ = x + δ |
| 82 | + Fₖ₊₁ = f(xₖ₊₁) |
| 83 | + fₖ₊₁ = 0.5 * norm(Fₖ₊₁)^2 |
| 84 | + |
| 85 | + # Compute the ratio of the actual to predicted reduction. |
| 86 | + model = -(δ' * g + 0.5 * δ' * H * δ) |
| 87 | + r = (fₖ - fₖ₊₁) / model |
| 88 | + |
| 89 | + # Update the trust region radius. |
| 90 | + if r < η₂ |
| 91 | + Δ *= t₁ |
| 92 | + if r > η₁ |
| 93 | + if isapprox(x̂, x, atol = atol, rtol = rtol) |
| 94 | + return SciMLBase.build_solution(prob, alg, x, F; |
| 95 | + retcode = ReturnCode.Success) |
| 96 | + end |
| 97 | + |
| 98 | + x = xₖ₊₁ |
| 99 | + F = Fₖ₊₁ |
| 100 | + if alg_autodiff(alg) |
| 101 | + F, ∇f = value_derivative(f, x) |
| 102 | + elseif x isa AbstractArray |
| 103 | + ∇f = FiniteDiff.finite_difference_jacobian(f, x, diff_type(alg), eltype(x), |
| 104 | + F) |
| 105 | + else |
| 106 | + ∇f = FiniteDiff.finite_difference_derivative(f, x, diff_type(alg), |
| 107 | + eltype(x), |
| 108 | + F) |
| 109 | + end |
| 110 | + |
| 111 | + iszero(F) && |
| 112 | + return SciMLBase.build_solution(prob, alg, x, F; |
| 113 | + retcode = ReturnCode.Success) |
| 114 | + # Update the trust region radius. |
| 115 | + if r > η₃ && norm(δ) ≈ Δ |
| 116 | + Δ = min(t₂ * Δ, Δₘₐₓ) |
| 117 | + end |
| 118 | + fₖ = f̂ |
| 119 | + H = ∇f * ∇f |
| 120 | + g = ∇f * F |
| 121 | + end |
| 122 | + end |
| 123 | + |
| 124 | + return SciMLBase.build_solution(prob, alg, x, F; retcode = ReturnCode.MaxIters) |
| 125 | +end |
0 commit comments