Skip to content

Commit

Permalink
Towards a cleaner and more maintainable internals of NonlinearSolve.jl
Browse files Browse the repository at this point in the history
  • Loading branch information
avik-pal committed Sep 7, 2023
1 parent bf3a132 commit 8869f80
Show file tree
Hide file tree
Showing 9 changed files with 836 additions and 1,207 deletions.
3 changes: 2 additions & 1 deletion .JuliaFormatter.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
style = "sciml"
format_markdown = true
format_markdown = true
annotate_untyped_fields_with_any = false
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
name = "NonlinearSolve"
uuid = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
authors = ["SciML"]
version = "1.10.0"
version = "1.11.0"

[deps]
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
ConcreteStructs = "2569d6c7-a4a2-43d3-a901-331e8e4be471"
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
EnumX = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
FiniteDiff = "6a86dc24-6348-571c-b903-95158fe2bd41"
Expand Down
85 changes: 44 additions & 41 deletions src/NonlinearSolve.jl
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
module NonlinearSolve
if isdefined(Base, :Experimental) &&
isdefined(Base.Experimental, Symbol("@max_methods"))

if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@max_methods"))
@eval Base.Experimental.@max_methods 1
end
using Reexport
using UnPack: @unpack
using FiniteDiff, ForwardDiff
using ForwardDiff: Dual
using LinearAlgebra
using StaticArraysCore
using RecursiveArrayTools
import EnumX
import ArrayInterface
import LinearSolve
using DiffEqBase
using SparseDiffTools

@reexport using SciMLBase
using SciMLBase: NLStats
@reexport using SimpleNonlinearSolve

import SciMLBase: _unwrap_val

abstract type AbstractNonlinearSolveAlgorithm <: SciMLBase.AbstractNonlinearAlgorithm end
abstract type AbstractNewtonAlgorithm{CS, AD, FDT, ST, CJ} <:
AbstractNonlinearSolveAlgorithm end

function SciMLBase.__solve(prob::NonlinearProblem,
alg::AbstractNonlinearSolveAlgorithm, args...;
kwargs...)

using DiffEqBase, LinearAlgebra, LinearSolve, SparseDiffTools
import ForwardDiff

import ADTypes: AbstractFiniteDifferencesMode
import ArrayInterface: undefmatrix
import ConcreteStructs: @concrete
import EnumX: @enumx
import ForwardDiff: Dual
import LinearSolve: ComposePreconditioner, InvPreconditioner, needs_concrete_A
import RecursiveArrayTools: AbstractVectorOfArray, recursivecopy!, recursivefill!
import Reexport: @reexport
import SciMLBase: AbstractNonlinearAlgorithm, NLStats, _unwrap_val, has_jac, isinplace
import SparseDiffTools: __init_𝒥
import StaticArraysCore: StaticArray, SVector
import UnPack: @unpack

@reexport using ADTypes, SciMLBase, SimpleNonlinearSolve

const AbstractSparseADType = Union{ADTypes.AbstractSparseFiniteDifferences,
ADTypes.AbstractSparseForwardMode, ADTypes.AbstractSparseReverseMode}

abstract type AbstractNonlinearSolveAlgorithm <: AbstractNonlinearAlgorithm end
abstract type AbstractNewtonAlgorithm{CJ, AD} <: AbstractNonlinearSolveAlgorithm end

function SciMLBase.__solve(prob::NonlinearProblem, alg::AbstractNonlinearSolveAlgorithm,
args...; kwargs...)
cache = init(prob, alg, args...; kwargs...)
sol = solve!(cache)
return solve!(cache)
end

# FIXME: Scalar Case is Completely Broken

include("utils.jl")
include("raphson.jl")
include("trustRegion.jl")
Expand All @@ -44,23 +47,23 @@ import PrecompileTools

PrecompileTools.@compile_workload begin
for T in (Float32, Float64)
prob = NonlinearProblem{false}((u, p) -> u .* u .- p, T(0.1), T(2))
# prob = NonlinearProblem{false}((u, p) -> u .* u .- p, T(0.1), T(2))

precompile_algs = if VERSION >= v"1.7"
(NewtonRaphson(), TrustRegion(), LevenbergMarquardt())
else
(NewtonRaphson(),)
end
# precompile_algs = if VERSION v"1.7"
# (NewtonRaphson(), TrustRegion(), LevenbergMarquardt())
# else
# (NewtonRaphson(),)
# end

for alg in precompile_algs
solve(prob, alg, abstol = T(1e-2))
end
# for alg in precompile_algs
# solve(prob, alg, abstol = T(1e-2))
# end

prob = NonlinearProblem{true}((du, u, p) -> du[1] = u[1] * u[1] - p[1], T[0.1],
T[2])
for alg in precompile_algs
solve(prob, alg, abstol = T(1e-2))
end
# for alg in precompile_algs
# solve(prob, alg, abstol = T(1e-2))
# end
end
end

Expand Down
19 changes: 7 additions & 12 deletions src/ad.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,17 @@ function scalar_nlsolve_ad(prob, alg, args...; kwargs...)
return sol, partials
end

function SciMLBase.solve(prob::NonlinearProblem{<:Union{Number, StaticArraysCore.SVector},
iip,
<:Dual{T, V, P}},
alg::AbstractNewtonAlgorithm,
args...; kwargs...) where {iip, T, V, P}
function SciMLBase.solve(prob::NonlinearProblem{<:Union{Number, SVector}, iip,
<:Dual{T, V, P}}, alg::AbstractNewtonAlgorithm, args...;
kwargs...) where {iip, T, V, P}
sol, partials = scalar_nlsolve_ad(prob, alg, args...; kwargs...)
return SciMLBase.build_solution(prob, alg, Dual{T, V, P}(sol.u, partials), sol.resid;
retcode = sol.retcode)
sol.retcode)
end
function SciMLBase.solve(prob::NonlinearProblem{<:Union{Number, StaticArraysCore.SVector},
iip,
<:AbstractArray{<:Dual{T, V, P}}},
alg::AbstractNewtonAlgorithm,
args...;
function SciMLBase.solve(prob::NonlinearProblem{<:Union{Number, SVector}, iip,
<:AbstractArray{<:Dual{T, V, P}}}, alg::AbstractNewtonAlgorithm, args...;
kwargs...) where {iip, T, V, P}
sol, partials = scalar_nlsolve_ad(prob, alg, args...; kwargs...)
return SciMLBase.build_solution(prob, alg, Dual{T, V, P}(sol.u, partials), sol.resid;
retcode = sol.retcode)
sol.retcode)
end
191 changes: 42 additions & 149 deletions src/jacobian.jl
Original file line number Diff line number Diff line change
@@ -1,185 +1,78 @@
struct JacobianWrapper{fType, pType}
f::fType
p::pType
@concrete struct JacobianWrapper
f
p
end

(uf::JacobianWrapper)(u) = uf.f(u, uf.p)
(uf::JacobianWrapper)(res, u) = uf.f(res, u, uf.p)

struct NonlinearSolveTag end

function sparsity_colorvec(f, x)
sparsity = f.sparsity
colorvec = DiffEqBase.has_colorvec(f) ? f.colorvec :
(isnothing(sparsity) ? (1:length(x)) : matrix_colors(sparsity))
sparsity, colorvec
end

function jacobian_finitediff_forward!(J, f, x, jac_config, forwardcache, cache)
(FiniteDiff.finite_difference_jacobian!(J, f, x, jac_config, forwardcache);
maximum(jac_config.colorvec))
end
function jacobian_finitediff!(J, f, x, jac_config, cache)
(FiniteDiff.finite_difference_jacobian!(J, f, x, jac_config);
2 * maximum(jac_config.colorvec))
end
# function sparsity_colorvec(f, x)
# sparsity = f.sparsity
# colorvec = DiffEqBase.has_colorvec(f) ? f.colorvec :
# (isnothing(sparsity) ? (1:length(x)) : matrix_colors(sparsity))
# sparsity, colorvec
# end

# NoOp for Jacobian if it is not a Abstract Array -- For eg, JacVec Operator
jacobian!(J, cache) = J
function jacobian!(J::AbstractMatrix{<:Number}, cache)
f = cache.f
uf = cache.uf
x = cache.u
fx = cache.fu
jac_config = cache.jac_config
alg = cache.alg

if SciMLBase.has_jac(f)
f.jac(J, x, cache.p)
elseif alg_autodiff(alg)
forwarddiff_color_jacobian!(J, uf, x, jac_config)
#cache.destats.nf += 1
jacobian!!(J, _) = J
# `!!` notation is from BangBang.jl since J might be jacobian in case of oop `f.jac`
# and we don't want wasteful `copyto!`
function jacobian!!(J::Union{AbstractMatrix{<:Number}, Nothing}, cache)
@unpack f, uf, u, p, jac_cache, alg, fu2 = cache
iip = isinplace(cache)
if iip
has_jac(f) ? f.jac(J, u, p) : sparse_jacobian!(J, alg.ad, jac_cache, uf, fu2, u)
else
isforward = alg_difftype(alg) === Val{:forward}
if isforward
uf(fx, x)
#cache.destats.nf += 1
tmp = jacobian_finitediff_forward!(J, uf, x, jac_config, fx,
cache)
else # not forward difference
tmp = jacobian_finitediff!(J, uf, x, jac_config, cache)
end
#cache.destats.nf += tmp
return has_jac(f) ? f.jac(u, p) : sparse_jacobian!(J, alg.ad, jac_cache, uf, u)
end
nothing
return nothing
end

function build_jac_and_jac_config(alg, f::F1, uf::F2, du1, u, tmp, du2) where {F1, F2}
# Build Jacobian Caches
function jacobian_caches(alg::AbstractNonlinearSolveAlgorithm, f, u, p,
::Val{iip}) where {iip}
uf = JacobianWrapper(f, p)

haslinsolve = hasfield(typeof(alg), :linsolve)

has_analytic_jac = SciMLBase.has_jac(f)
has_analytic_jac = has_jac(f)
linsolve_needs_jac = (concrete_jac(alg) === nothing &&
(!haslinsolve || (haslinsolve && (alg.linsolve === nothing ||
LinearSolve.needs_concrete_A(alg.linsolve)))))
alg_wants_jac = (concrete_jac(alg) !== nothing && concrete_jac(alg))
needs_concrete_A(alg.linsolve)))))
alg_wants_jac = (concrete_jac(alg) === nothing && concrete_jac(alg))

fu = zero(u) # TODO: Use Prototype
if !has_analytic_jac && (linsolve_needs_jac || alg_wants_jac)
sparsity, colorvec = sparsity_colorvec(f, u)

if alg_autodiff(alg)
_chunksize = get_chunksize(alg) === Val(0) ? nothing : get_chunksize(alg) # SparseDiffEq uses different convection...

T = if standardtag(alg)
typeof(ForwardDiff.Tag(NonlinearSolveTag(), eltype(u)))
else
typeof(ForwardDiff.Tag(uf, eltype(u)))
end
jac_config = ForwardColorJacCache(uf, u, _chunksize; colorvec, sparsity,
tag = T)
else
if alg_difftype(alg) !== Val{:complex}
jac_config = FiniteDiff.JacobianCache(tmp, du1, du2, alg_difftype(alg);
colorvec, sparsity)
else
jac_config = FiniteDiff.JacobianCache(Complex{eltype(tmp)}.(tmp),
Complex{eltype(du1)}.(du1), nothing, alg_difftype(alg), eltype(u);
colorvec, sparsity)
end
end
# TODO: We need an Upstream Mode to allow using known sparsity and colorvec
# TODO: We can use the jacobian prototype here
sd = typeof(alg.ad) <: AbstractSparseADType ? SymbolicsSparsityDetection() :
NoSparsityDetection()
jac_cache = iip ? sparse_jacobian_cache(alg.ad, sd, uf, fu, u) :
sparse_jacobian_cache(alg.ad, sd, uf, u; fx=fu)
else
jac_config = nothing
jac_cache = nothing
end

J = if !linsolve_needs_jac
# We don't need to construct the Jacobian
JacVec(uf, u; autodiff = alg_autodiff(alg) ? AutoForwardDiff() : AutoFiniteDiff())
JacVec(uf, u; autodiff = alg.ad)
else
if f.jac_prototype === nothing
ArrayInterface.undefmatrix(u)
if has_analytic_jac
iip ? undefmatrix(u) : nothing
else
f.jac_prototype
f.jac_prototype === nothing ? __init_𝒥(jac_cache) : f.jac_prototype
end
end

return J, jac_config
end

# Build Jacobian Caches
function jacobian_caches(alg::Union{NewtonRaphson, LevenbergMarquardt, TrustRegion}, f, u,
p, ::Val{true})
uf = JacobianWrapper(f, p)

du1 = zero(u)
du2 = zero(u)
tmp = zero(u)
J, jac_config = build_jac_and_jac_config(alg, f, uf, du1, u, tmp, du2)

# FIXME: Assumes same sized `u` and `fu` -- Incorrect Assumption for Levenberg
linprob = LinearProblem(J, _vec(zero(u)); u0 = _vec(zero(u)))

weight = similar(u)
recursivefill!(weight, true)

Pl, Pr = wrapprecs(alg.precs(J, nothing, u, p, nothing, nothing, nothing, nothing,
nothing)..., weight)
linsolve = init(linprob, alg.linsolve; alias_A = true, alias_b = true, Pl, Pr)

uf, linsolve, J, du1, jac_config
end

function get_chunksize(jac_config::ForwardDiff.JacobianConfig{
T,
V,
N,
D,
}) where {T, V, N, D
}
Val(N)
end # don't degrade compile time information to runtime information

function jacobian_finitediff(f, x, ::Type{diff_type}, dir, colorvec, sparsity,
jac_prototype) where {diff_type}
(FiniteDiff.finite_difference_derivative(f, x, diff_type, eltype(x), dir = dir), 2)
end
function jacobian_finitediff(f, x::AbstractArray, ::Type{diff_type}, dir, colorvec,
sparsity, jac_prototype) where {diff_type}
f_in = diff_type === Val{:forward} ? f(x) : similar(x)
ret_eltype = eltype(f_in)
J = FiniteDiff.finite_difference_jacobian(f, x, diff_type, ret_eltype, f_in,
dir = dir, colorvec = colorvec,
sparsity = sparsity,
jac_prototype = jac_prototype)
return J, _nfcount(maximum(colorvec), diff_type)
end
function jacobian(cache, f::F) where {F}
x = cache.u
alg = cache.alg
uf = cache.uf
local tmp

if DiffEqBase.has_jac(cache.f)
J = f.jac(cache.u, cache.p)
elseif alg_autodiff(alg)
J, tmp = jacobian_autodiff(uf, x, cache.f, alg)
else
jac_prototype = cache.f.jac_prototype
sparsity, colorvec = sparsity_colorvec(cache.f, x)
dir = true
J, tmp = jacobian_finitediff(uf, x, alg_difftype(alg), dir, colorvec, sparsity,
jac_prototype)
end
J
end

jacobian_autodiff(f, x, nonlinfun, alg) = (ForwardDiff.derivative(f, x), 1, alg)
function jacobian_autodiff(f, x::AbstractArray, nonlinfun, alg)
jac_prototype = nonlinfun.jac_prototype
sparsity, colorvec = sparsity_colorvec(nonlinfun, x)
maxcolor = maximum(colorvec)
chunk_size = get_chunksize(alg) === Val(0) ? nothing : get_chunksize(alg)
num_of_chunks = chunk_size === nothing ?
Int(ceil(maxcolor /
SparseDiffTools.getsize(ForwardDiff.pickchunksize(maxcolor)))) :
Int(ceil(maxcolor / _unwrap_val(chunk_size)))
(forwarddiff_color_jacobian(f, x, colorvec = colorvec, sparsity = sparsity,
jac_prototype = jac_prototype, chunksize = chunk_size),
num_of_chunks)
return uf, linsolve, J, fu, jac_cache
end
Loading

0 comments on commit 8869f80

Please sign in to comment.