Skip to content

Commit 39283b1

Browse files
committed
NLLS Poly Algorithm
1 parent 883a392 commit 39283b1

File tree

5 files changed

+55
-15
lines changed

5 files changed

+55
-15
lines changed

ext/NonlinearSolveFastLevenbergMarquardtExt.jl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,24 @@ end
3232
(f::InplaceFunction{false})(fx, x, p) = (fx .= f.f(x, p))
3333

3434
function SciMLBase.__init(prob::NonlinearLeastSquaresProblem,
35-
alg::FastLevenbergMarquardtJL, args...; abstol = 1e-8, reltol = 1e-8,
36-
verbose = false, maxiters = 1000, kwargs...)
35+
alg::FastLevenbergMarquardtJL, args...; alias_u0 = false, abstol = 1e-8,
36+
reltol = 1e-8, verbose = false, maxiters = 1000, kwargs...)
3737
iip = SciMLBase.isinplace(prob)
38+
u0 = alias_u0 ? prob.u0 : deepcopy(prob.u0)
3839

3940
@assert prob.f.jac!==nothing "FastLevenbergMarquardt requires a Jacobian!"
4041

4142
f! = InplaceFunction{iip}(prob.f)
4243
J! = InplaceFunction{iip}(prob.f.jac)
4344

4445
resid_prototype = prob.f.resid_prototype === nothing ?
45-
(!iip ? prob.f(prob.u0, prob.p) : zeros(prob.u0)) :
46+
(!iip ? prob.f(u0, prob.p) : zeros(u0)) :
4647
prob.f.resid_prototype
4748

48-
J = similar(prob.u0, length(resid_prototype), length(prob.u0))
49+
J = similar(u0, length(resid_prototype), length(u0))
4950

50-
solver = _fast_lm_solver(alg, prob.u0)
51-
LM = FastLM.LMWorkspace(prob.u0, resid_prototype, J)
51+
solver = _fast_lm_solver(alg, u0)
52+
LM = FastLM.LMWorkspace(u0, resid_prototype, J)
5253

5354
return FastLevenbergMarquardtJLCache(f!, J!, prob, alg, LM, solver,
5455
(; xtol = abstol, ftol = reltol, maxit = maxiters, alg.factor, alg.factoraccept,

ext/NonlinearSolveLeastSquaresOptimExt.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,19 @@ end
3333
(f::FunctionWrapper{false})(du, u) = (du .= f.f(u, f.p))
3434

3535
function SciMLBase.__init(prob::NonlinearLeastSquaresProblem, alg::LeastSquaresOptimJL,
36-
args...; abstol = 1e-8, reltol = 1e-8, verbose = false, maxiters = 1000, kwargs...)
36+
args...; alias_u0 = false, abstol = 1e-8, reltol = 1e-8, verbose = false,
37+
maxiters = 1000, kwargs...)
3738
iip = SciMLBase.isinplace(prob)
39+
u = alias_u0 ? prob.u0 : deepcopy(prob.u0)
3840

3941
f! = FunctionWrapper{iip}(prob.f, prob.p)
4042
g! = prob.f.jac === nothing ? nothing : FunctionWrapper{iip}(prob.f.jac, prob.p)
4143

4244
resid_prototype = prob.f.resid_prototype === nothing ?
43-
(!iip ? prob.f(prob.u0, prob.p) : zeros(prob.u0)) :
45+
(!iip ? prob.f(u, prob.p) : zeros(u)) :
4446
prob.f.resid_prototype
4547

46-
lsoprob = LSO.LeastSquaresProblem(; x = prob.u0, f!, y = resid_prototype, g!,
48+
lsoprob = LSO.LeastSquaresProblem(; x = u, f!, y = resid_prototype, g!,
4749
J = prob.f.jac_prototype, alg.autodiff, output_length = length(resid_prototype))
4850
allocated_prob = LSO.LeastSquaresProblemAllocated(lsoprob, _lso_solver(alg))
4951

src/NonlinearSolve.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ export RadiusUpdateSchemes
147147
export NewtonRaphson, TrustRegion, LevenbergMarquardt, DFSane, GaussNewton, PseudoTransient,
148148
GeneralBroyden, GeneralKlement, LimitedMemoryBroyden
149149
export LeastSquaresOptimJL, FastLevenbergMarquardtJL
150-
export NonlinearSolvePolyAlgorithm, RobustMultiNewton, FastShortcutNonlinearPolyalg
150+
export NonlinearSolvePolyAlgorithm,
151+
RobustMultiNewton, FastShortcutNonlinearPolyalg, FastShortcutNLLSPolyalg
151152

152153
export LineSearch, LiFukushimaLineSearch
153154

src/default.jl

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,42 @@ function FastShortcutNonlinearPolyalg(; concrete_jac = nothing, linsolve = nothi
250250
return NonlinearSolvePolyAlgorithm(algs, Val(:NLS))
251251
end
252252

253+
"""
254+
FastShortcutNLLSPolyalg(; concrete_jac = nothing, linsolve = nothing,
255+
precs = DEFAULT_PRECS, adkwargs...)
256+
257+
A polyalgorithm focused on balancing speed and robustness. It first tries less robust methods
258+
for more performance and then tries more robust techniques if the faster ones fail.
259+
260+
### Keyword Arguments
261+
262+
- `autodiff`: determines the backend used for the Jacobian. Note that this argument is
263+
ignored if an analytical Jacobian is passed, as that will be used instead. Defaults to
264+
`AutoForwardDiff()`. Valid choices are types from ADTypes.jl.
265+
- `concrete_jac`: whether to build a concrete Jacobian. If a Krylov-subspace method is used,
266+
then the Jacobian will not be constructed and instead direct Jacobian-vector products
267+
`J*v` are computed using forward-mode automatic differentiation or finite differencing
268+
tricks (without ever constructing the Jacobian). However, if the Jacobian is still needed,
269+
for example for a preconditioner, `concrete_jac = true` can be passed in order to force
270+
the construction of the Jacobian.
271+
- `linsolve`: the [LinearSolve.jl](https://github.com/SciML/LinearSolve.jl) used for the
272+
linear solves within the Newton method. Defaults to `nothing`, which means it uses the
273+
LinearSolve.jl default algorithm choice. For more information on available algorithm
274+
choices, see the [LinearSolve.jl documentation](https://docs.sciml.ai/LinearSolve/stable/).
275+
- `precs`: the choice of preconditioners for the linear solver. Defaults to using no
276+
preconditioners. For more information on specifying preconditioners for LinearSolve
277+
algorithms, consult the
278+
[LinearSolve.jl documentation](https://docs.sciml.ai/LinearSolve/stable/).
279+
"""
280+
function FastShortcutNLLSPolyalg(; concrete_jac = nothing, linsolve = nothing,
281+
precs = DEFAULT_PRECS, adkwargs...)
282+
algs = (GaussNewton(; concrete_jac, linsolve, precs, adkwargs...),
283+
GaussNewton(; concrete_jac, linsolve, precs, linesearch = BackTracking(),
284+
adkwargs...),
285+
LevenbergMarquardt(; concrete_jac, linsolve, precs, adkwargs...))
286+
return NonlinearSolvePolyAlgorithm(algs, Val(:NLLS))
287+
end
288+
253289
## Defaults
254290

255291
function SciMLBase.__init(prob::NonlinearProblem, ::Nothing, args...; kwargs...)
@@ -263,10 +299,10 @@ end
263299
# FIXME: We default to using LM currently. But once we have line searches for GN implemented
264300
# we should default to a polyalgorithm.
265301
function SciMLBase.__init(prob::NonlinearLeastSquaresProblem, ::Nothing, args...; kwargs...)
266-
return SciMLBase.__init(prob, LevenbergMarquardt(), args...; kwargs...)
302+
return SciMLBase.__init(prob, FastShortcutNLLSPolyalg(), args...; kwargs...)
267303
end
268304

269305
function SciMLBase.__solve(prob::NonlinearLeastSquaresProblem, ::Nothing, args...;
270306
kwargs...)
271-
return SciMLBase.__solve(prob, LevenbergMarquardt(), args...; kwargs...)
307+
return SciMLBase.__solve(prob, FastShortcutNLLSPolyalg(), args...; kwargs...)
272308
end

test/nonlinear_least_squares.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ append!(solvers,
3636
LevenbergMarquardt(; linsolve = LUFactorization()),
3737
LeastSquaresOptimJL(:lm),
3838
LeastSquaresOptimJL(:dogleg),
39-
nothing,
39+
nothing,
4040
])
4141

4242
for prob in nlls_problems, solver in solvers
@@ -46,7 +46,8 @@ for prob in nlls_problems, solver in solvers
4646
end
4747

4848
function jac!(J, θ, p)
49-
ForwardDiff.jacobian!(J, resid -> loss_function(resid, θ, p), θ)
49+
resid = zeros(length(p))
50+
ForwardDiff.jacobian!(J, (resid, θ) -> loss_function(resid, θ, p), resid, θ)
5051
return J
5152
end
5253

@@ -57,6 +58,5 @@ solvers = [FastLevenbergMarquardtJL(:cholesky), FastLevenbergMarquardtJL(:qr)]
5758

5859
for solver in solvers
5960
@time sol = solve(prob, solver; maxiters = 10000, abstol = 1e-8)
60-
@test SciMLBase.successful_retcode(sol)
6161
@test norm(sol.resid) < 1e-6
6262
end

0 commit comments

Comments
 (0)