Skip to content

Commit

Permalink
Add Enlsip Extension
Browse files Browse the repository at this point in the history
  • Loading branch information
avik-pal committed May 24, 2024
1 parent 42e6e19 commit d719ee3
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 6 deletions.
67 changes: 67 additions & 0 deletions ext/NonlinearSolveEnlsipExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
module NonlinearSolveEnlsipExt

using FastClosures: @closure
using NonlinearSolve: NonlinearSolve, EnlsipJL
using SciMLBase: SciMLBase, NonlinearLeastSquaresProblem, ReturnCode
using Enlsip: Enlsip

function SciMLBase.__solve(prob::NonlinearLeastSquaresProblem, alg::EnlsipJL, args...;

Check warning on line 8 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L8

Added line #L8 was not covered by tests
abstol = nothing, reltol = nothing, maxiters = 1000,
alias_u0::Bool = false, maxtime = nothing, show_trace::Val{ST} = Val(false),
termination_condition = nothing, kwargs...) where {ST}
NonlinearSolve.__test_termination_condition(termination_condition, :EnlsipJL)

Check warning on line 12 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L12

Added line #L12 was not covered by tests

f, u0, resid = NonlinearSolve.__construct_extension_f(

Check warning on line 14 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L14

Added line #L14 was not covered by tests
prob; alias_u0, can_handle_oop = Val(true), force_oop = Val(true))

f_aug = @closure u -> begin
u_ = view(u, 1:(length(u) - 1))
r = f(u_)
return vcat(r, u[length(u)])

Check warning on line 20 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L17-L20

Added lines #L17 - L20 were not covered by tests
end

eq_cons = u -> [u[end]]

Check warning on line 23 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L23

Added line #L23 was not covered by tests

abstol = NonlinearSolve.DEFAULT_TOLERANCE(abstol, eltype(u0))
reltol = NonlinearSolve.DEFAULT_TOLERANCE(reltol, eltype(u0))

Check warning on line 26 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L25-L26

Added lines #L25 - L26 were not covered by tests

maxtime = maxtime === nothing ? typemax(eltype(u0)) : maxtime

Check warning on line 28 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L28

Added line #L28 was not covered by tests

jac_fn = NonlinearSolve.__construct_extension_jac(

Check warning on line 30 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L30

Added line #L30 was not covered by tests
prob, alg, u0, resid; alg.autodiff, can_handle_oop = Val(true))

n = length(u0) + 1
m = length(resid) + 1

Check warning on line 34 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L33-L34

Added lines #L33 - L34 were not covered by tests

jac_fn_aug = @closure u -> begin
u_ = view(u, 1:(length(u) - 1))
J = jac_fn(u_)
J_full = similar(u, (m, n))
J_full[1:(m - 1), 1:(n - 1)] .= J
fill!(J_full[1:(m - 1), n], false)
fill!(J_full[m, 1:(n - 1)], false)
return J_full

Check warning on line 43 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L36-L43

Added lines #L36 - L43 were not covered by tests
end

u0 = vcat(u0, 0.0)
u_low = [eltype(u0)(ifelse(i == 1, -Inf, 0)) for i in 1:length(u0)]
u_up = [eltype(u0)(ifelse(i == 1, Inf, 0)) for i in 1:length(u0)]

Check warning on line 48 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L46-L48

Added lines #L46 - L48 were not covered by tests

model = Enlsip.CnlsModel(

Check warning on line 50 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L50

Added line #L50 was not covered by tests
f_aug, n, m; starting_point = u0, jacobian_residuals = jac_fn_aug,
x_low = u_low, x_upp = u_up, nb_eqcons = 1, eq_constraints = eq_cons)
Enlsip.solve!(model; max_iter = maxiters, time_limit = maxtime, silent = !ST,

Check warning on line 53 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L53

Added line #L53 was not covered by tests
abs_tol = abstol, rel_tol = reltol, x_tol = reltol)

sol_u = Enlsip.solution(model)
resid = Enlsip.sum_sq_residuals(model)

Check warning on line 57 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L56-L57

Added lines #L56 - L57 were not covered by tests

status = Enlsip.status(model)
retcode = status === :found_first_order_stationary_point ? ReturnCode.Success :

Check warning on line 60 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L59-L60

Added lines #L59 - L60 were not covered by tests
status === :maximum_iterations_exceeded ? ReturnCode.MaxIters :
status === :time_limit_exceeded ? ReturnCode.MaxTime : ReturnCode.Failure

return SciMLBase.build_solution(prob, alg, sol_u, resid; retcode, original = model)

Check warning on line 64 in ext/NonlinearSolveEnlsipExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NonlinearSolveEnlsipExt.jl#L64

Added line #L64 was not covered by tests
end

end
4 changes: 2 additions & 2 deletions src/NonlinearSolve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ export NonlinearSolvePolyAlgorithm, RobustMultiNewton, FastShortcutNonlinearPoly
FastShortcutNLLSPolyalg

# Extension Algorithms
export LeastSquaresOptimJL, FastLevenbergMarquardtJL, CMINPACK, NLsolveJL, NLSolversJL,
FixedPointAccelerationJL, SpeedMappingJL, SIAMFANLEquationsJL
export LeastSquaresOptimJL, FastLevenbergMarquardtJL, CMINPACK, EnlsipJL, NLsolveJL,
NLSolversJL, FixedPointAccelerationJL, SpeedMappingJL, SIAMFANLEquationsJL

# Advanced Algorithms -- Without Bells and Whistles
export GeneralizedFirstOrderAlgorithm, ApproximateJacobianSolveAlgorithm, GeneralizedDFSane
Expand Down
33 changes: 33 additions & 0 deletions src/algorithms/extension_algs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -484,3 +484,36 @@ function SIAMFANLEquationsJL(; method = :newton, delta = 1e-3, linsolve = nothin
end
return SIAMFANLEquationsJL(method, delta, linsolve, m, beta, autodiff)
end

"""
EnlsipJL(; autodiff = nothing)
Wrapper over [Enlsip.jl](https://github.com/UncertainLab/Enlsip.jl) for solving Nonlinear
Least Squares Problems.
### Keyword Arguments
- `autodiff`: determines the backend used for the Jacobian. Note that this argument is
ignored if an analytical Jacobian is passed, as that will be used instead. Defaults to
`nothing` which means that a default is selected according to the problem specification!
!!! note
This algorithm is only available if `Enlsip.jl` is installed.
!!! warning
Enlsip is designed for constrained NLLS problems. However, since we don't support
constraints in NonlinearSolve.jl currently, we add a dummy constraint to the problem
before calling Enlsip.
"""
@concrete struct EnlsipJL <: AbstractNonlinearSolveExtensionAlgorithm
autodiff
end

function EnlsipJL(; autodiff = nothing)
if Base.get_extension(@__MODULE__, :NonlinearSolveEnlsipExt) === nothing
error("EnlsipJL requires Enlsip.jl to be loaded")

Check warning on line 516 in src/algorithms/extension_algs.jl

View check run for this annotation

Codecov / codecov/patch

src/algorithms/extension_algs.jl#L514-L516

Added lines #L514 - L516 were not covered by tests
end
return EnlsipJL(autodiff)

Check warning on line 518 in src/algorithms/extension_algs.jl

View check run for this annotation

Codecov / codecov/patch

src/algorithms/extension_algs.jl#L518

Added line #L518 was not covered by tests
end
2 changes: 1 addition & 1 deletion test/misc/qa_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ end

@testitem "Explicit Imports" tags=[:misc] begin
using NonlinearSolve, ADTypes, SimpleNonlinearSolve, SciMLBase
import BandedMatrices, FastLevenbergMarquardt, FixedPointAcceleration,
import BandedMatrices, Enlsip, FastLevenbergMarquardt, FixedPointAcceleration,
LeastSquaresOptim, MINPACK, NLsolve, NLSolvers, SIAMFANLEquations, SpeedMapping,
Symbolics, Zygote

Expand Down
10 changes: 7 additions & 3 deletions test/wrappers/nlls_tests.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@testsetup module WrapperNLLSSetup
using Reexport
@reexport using LinearAlgebra, StableRNGs, StaticArrays, Random, ForwardDiff, Zygote
import FastLevenbergMarquardt, LeastSquaresOptim, MINPACK
import FastLevenbergMarquardt, LeastSquaresOptim, MINPACK, Enlsip

true_function(x, θ) = @. θ[1] * exp(θ[2] * x) * cos(θ[3] * x + θ[4])
true_function(y, x, θ) = (@. y = θ[1] * exp(θ[2] * x) * cos(θ[3] * x + θ[4]))
Expand Down Expand Up @@ -46,7 +46,7 @@ end
end
end

@testitem "FastLevenbergMarquardt.jl + CMINPACK: Jacobian Provided" setup=[WrapperNLLSSetup] tags=[:wrappers] begin
@testitem "FastLevenbergMarquardt.jl + CMINPACK + Einsip: Jacobian Provided" setup=[WrapperNLLSSetup] tags=[:wrappers] begin
function jac!(J, θ, p)
resid = zeros(length(p))
ForwardDiff.jacobian!(J, (resid, θ) -> loss_function(resid, θ, p), resid, θ)
Expand All @@ -71,13 +71,14 @@ end

solvers = Any[FastLevenbergMarquardtJL(linsolve) for linsolve in (:cholesky, :qr)]
Sys.isapple() || push!(solvers, CMINPACK())
push!(solvers, EnlsipJL())
for solver in solvers, prob in probs
sol = solve(prob, solver; maxiters = 10000, abstol = 1e-8)
@test maximum(abs, sol.resid) < 1e-6
end
end

@testitem "FastLevenbergMarquardt.jl + CMINPACK: Jacobian Not Provided" setup=[WrapperNLLSSetup] tags=[:wrappers] begin
@testitem "FastLevenbergMarquardt.jl + CMINPACK + Einsip: Jacobian Not Provided" setup=[WrapperNLLSSetup] tags=[:wrappers] begin
probs = [
NonlinearLeastSquaresProblem(
NonlinearFunction{true}(loss_function; resid_prototype = zero(y_target)),
Expand All @@ -92,6 +93,9 @@ end
autodiff in (nothing, AutoForwardDiff(), AutoFiniteDiff())])
Sys.isapple() ||
append!(solvers, [CMINPACK(; method) for method in (:auto, :lm, :lmdif)])
append!(solvers,
[EnlsipJL(; autodiff)
for autodiff in (nothing, AutoForwardDiff(), AutoFiniteDiff())])

for solver in solvers, prob in probs
sol = solve(prob, solver; maxiters = 10000, abstol = 1e-8)
Expand Down

0 comments on commit d719ee3

Please sign in to comment.