Skip to content

Commit 38088d9

Browse files
committed
refactor: create Quasi Newton Algorithm subpackage
1 parent f662e3e commit 38088d9

28 files changed

+1427
-1148
lines changed

Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1919
LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
2020
MaybeInplace = "bb5d69b7-63fc-4a16-80bd-7e42200c7bdb"
2121
NonlinearSolveBase = "be0214bd-f91f-a760-ac4e-3421ce2b2da0"
22+
NonlinearSolveQuasiNewton = "9a2c21bd-3a47-402d-9113-8faf9a0ee114"
2223
NonlinearSolveSpectralMethods = "26075421-4e9a-44e1-8bd1-420ed7ad02b2"
2324
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
2425
Preferences = "21216c6a-2e73-6563-6e65-726566657250"

common/nlls_problem_workloads.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

docs/src/release_notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Breaking Changes in `NonlinearSolve.jl` v4
66

7+
- `ApproximateJacobianSolveAlgorithm` has been renamed to `QuasiNewtonAlgorithm`.
78
- See [common breaking changes](@ref common-breaking-changes-v4v2) below.
89

910
### Breaking Changes in `SimpleNonlinearSolve.jl` v2

lib/NonlinearSolveBase/ext/NonlinearSolveBaseSparseArraysExt.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ Utils.make_sparse(x) = sparse(x)
1515

1616
Utils.condition_number(J::AbstractSparseMatrix) = Utils.condition_number(Matrix(J))
1717

18-
Utils.maybe_pinv!!_workspace(A::AbstractSparseMatrix) = Matrix(A)
18+
function Utils.maybe_pinv!!_workspace(A::AbstractSparseMatrix)
19+
dense_A = Matrix(A)
20+
return dense_A, copy(dense_A)
21+
end
1922

2023
end

lib/NonlinearSolveBase/src/NonlinearSolveBase.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ using DifferentiationInterface: DifferentiationInterface, Constant
1111
using EnzymeCore: EnzymeCore
1212
using FastClosures: @closure
1313
using FunctionProperties: hasbranching
14-
using LinearAlgebra: LinearAlgebra, Diagonal, norm, ldiv!, diagind
14+
using LinearAlgebra: LinearAlgebra, Diagonal, norm, ldiv!, diagind, pinv
1515
using Markdown: @doc_str
1616
using MaybeInplace: @bb
1717
using Preferences: @load_preference

lib/NonlinearSolveBase/src/abstract_types.jl

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,11 @@ Abstract Type for all NonlinearSolveBase Caches.
226226
### Interface Functions
227227
228228
- `get_fu(cache)`: get the residual.
229+
229230
- `get_u(cache)`: get the current state.
230231
- `set_fu!(cache, fu)`: set the residual.
231232
- `has_time_limit(cache)`: whether or not the solver has a maximum time limit.
232233
- `not_terminated(cache)`: whether or not the solver has terminated.
233-
234234
- `SciMLBase.set_u!(cache, u)`: set the current state.
235235
- `SciMLBase.reinit!(cache, u0; kwargs...)`: reinitialize the cache with the initial state
236236
`u0` and any additional keyword arguments.
@@ -339,3 +339,170 @@ Abstract Type for all Jacobian Caches used in NonlinearSolveBase. Subtypes of th
339339
meant to be constructured via [`construct_jacobian_cache`](@ref).
340340
"""
341341
abstract type AbstractJacobianCache <: AbstractNonlinearSolveBaseAPI end
342+
343+
"""
344+
AbstractApproximateJacobianStructure
345+
346+
Abstract Type for all Approximate Jacobian Structures used in NonlinearSolve.jl.
347+
348+
### Interface Functions
349+
350+
- `stores_full_jacobian(alg)`: whether or not the algorithm stores the full Jacobian.
351+
Defaults to `false`.
352+
- `get_full_jacobian(cache, alg, J)`: get the full Jacobian. Defaults to throwing an
353+
error if `stores_full_jacobian(alg)` is `false`.
354+
"""
355+
abstract type AbstractApproximateJacobianStructure <: AbstractNonlinearSolveBaseAPI end
356+
357+
stores_full_jacobian(::AbstractApproximateJacobianStructure) = false
358+
function get_full_jacobian(cache, alg::AbstractApproximateJacobianStructure, J)
359+
stores_full_jacobian(alg) && return J
360+
error("This algorithm does not store the full Jacobian. Define `get_full_jacobian` for \
361+
this algorithm.")
362+
end
363+
364+
"""
365+
AbstractJacobianInitialization
366+
367+
Abstract Type for all Jacobian Initialization Algorithms used in NonlinearSolveBase.
368+
369+
### Interface Functions
370+
371+
- `jacobian_initialized_preinverted(alg)`: whether or not the Jacobian is initialized
372+
preinverted. Defaults to `false`.
373+
374+
### `InternalAPI.init` specification
375+
376+
```julia
377+
InternalAPI.init(
378+
prob::AbstractNonlinearProblem, alg::AbstractJacobianInitialization, solver,
379+
f, fu, u, p;
380+
linsolve = missing, internalnorm::IN = L2_NORM, kwargs...
381+
)::AbstractJacobianCache
382+
```
383+
384+
All subtypes need to define
385+
`(cache::AbstractJacobianCache)(alg::NewSubType, fu, u)` which reinitializes the Jacobian in
386+
`cache.J`.
387+
"""
388+
abstract type AbstractJacobianInitialization <: AbstractNonlinearSolveBaseAPI end
389+
390+
jacobian_initialized_preinverted(::AbstractJacobianInitialization) = false
391+
392+
"""
393+
AbstractApproximateJacobianUpdateRule
394+
395+
Abstract Type for all Approximate Jacobian Update Rules used in NonlinearSolveBase.
396+
397+
### Interface Functions
398+
399+
- `store_inverse_jacobian(alg)`: Return `alg.store_inverse_jacobian`
400+
401+
### `InternalAPI.init` specification
402+
403+
```julia
404+
InternalAPI.init(
405+
prob::AbstractNonlinearProblem, alg::AbstractApproximateJacobianUpdateRule, J, fu, u,
406+
du, args...; internalnorm = L2_NORM, kwargs...
407+
)::AbstractApproximateJacobianUpdateRuleCache
408+
```
409+
"""
410+
abstract type AbstractApproximateJacobianUpdateRule <: AbstractNonlinearSolveBaseAPI end
411+
412+
function store_inverse_jacobian(rule::AbstractApproximateJacobianUpdateRule)
413+
return rule.store_inverse_jacobian
414+
end
415+
416+
"""
417+
AbstractApproximateJacobianUpdateRuleCache
418+
419+
Abstract Type for all Approximate Jacobian Update Rule Caches used in NonlinearSolveBase.
420+
421+
### Interface Functions
422+
423+
- `store_inverse_jacobian(cache)`: Return `store_inverse_jacobian(cache.rule)`
424+
425+
### `InternalAPI.solve!` specification
426+
427+
```julia
428+
InternalAPI.solve!(
429+
cache::AbstractApproximateJacobianUpdateRuleCache, J, fu, u, du; kwargs...
430+
) --> J / J⁻¹
431+
```
432+
"""
433+
abstract type AbstractApproximateJacobianUpdateRuleCache <: AbstractNonlinearSolveBaseAPI end
434+
435+
function store_inverse_jacobian(cache::AbstractApproximateJacobianUpdateRuleCache)
436+
return store_inverse_jacobian(cache.rule)
437+
end
438+
439+
"""
440+
AbstractResetCondition
441+
442+
Condition for resetting the Jacobian in Quasi-Newton's methods.
443+
444+
### `InternalAPI.init` specification
445+
446+
```julia
447+
InternalAPI.init(
448+
alg::AbstractResetCondition, J, fu, u, du, args...; kwargs...
449+
)::AbstractResetConditionCache
450+
```
451+
"""
452+
abstract type AbstractResetCondition <: AbstractNonlinearSolveBaseAPI end
453+
454+
"""
455+
AbstractResetConditionCache
456+
457+
Abstract Type for all Reset Condition Caches used in NonlinearSolveBase.
458+
459+
### `InternalAPI.solve!` specification
460+
461+
```julia
462+
InternalAPI.solve!(
463+
cache::AbstractResetConditionCache, J, fu, u, du; kwargs...
464+
)::Bool
465+
```
466+
"""
467+
abstract type AbstractResetConditionCache <: AbstractNonlinearSolveBaseAPI end
468+
469+
"""
470+
AbstractTrustRegionMethod
471+
472+
Abstract Type for all Trust Region Methods used in NonlinearSolveBase.
473+
474+
### `InternalAPI.init` specification
475+
476+
```julia
477+
InternalAPI.init(
478+
prob::AbstractNonlinearProblem, alg::AbstractTrustRegionMethod, f, fu, u, p, args...;
479+
internalnorm = L2_NORM, kwargs...
480+
)::AbstractTrustRegionMethodCache
481+
```
482+
"""
483+
abstract type AbstractTrustRegionMethod <: AbstractNonlinearSolveBaseAPI end
484+
485+
"""
486+
AbstractTrustRegionMethodCache
487+
488+
Abstract Type for all Trust Region Method Caches used in NonlinearSolveBase.
489+
490+
### Interface Functions
491+
492+
- `last_step_accepted(cache)`: whether or not the last step was accepted. Defaults to
493+
`cache.last_step_accepted`. Should if overloaded if the field is not present.
494+
495+
### `InternalAPI.solve!` specification
496+
497+
```julia
498+
InternalAPI.solve!(
499+
cache::AbstractTrustRegionMethodCache, J, fu, u, δu, descent_stats; kwargs...
500+
)
501+
```
502+
503+
Returns `last_step_accepted`, updated `u_cache` and `fu_cache`. If the last step was
504+
accepted then these values should be copied into the toplevel cache.
505+
"""
506+
abstract type AbstractTrustRegionMethodCache <: AbstractNonlinearSolveBaseAPI end
507+
508+
last_step_accepted(cache::AbstractTrustRegionMethodCache) = cache.last_step_accepted

lib/NonlinearSolveBase/src/jacobian.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,7 @@ end
122122
di_extras
123123
end
124124

125-
function InternalAPI.reinit!(
126-
cache::JacobianCache, args...; p = cache.p, u0 = cache.u, kwargs...)
125+
function InternalAPI.reinit!(cache::JacobianCache; p = cache.p, u0 = cache.u, kwargs...)
127126
cache.u = u0
128127
cache.p = p
129128
end

lib/NonlinearSolveBase/src/tracing.jl

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ function update_trace!(
205205
return trace
206206
end
207207

208-
function update_trace!(cache, α = true)
208+
function update_trace!(cache, α = true; uses_jac_inverse = Val(false))
209209
trace = Utils.safe_getproperty(cache, Val(:trace))
210210
trace === missing && return nothing
211211

@@ -214,11 +214,8 @@ function update_trace!(cache, α = true)
214214
update_trace!(
215215
trace, cache.nsteps + 1, get_u(cache), get_fu(cache), nothing, cache.du, α
216216
)
217-
# XXX: Implement
218-
# elseif cache isa ApproximateJacobianSolveCache && store_inverse_jacobian(cache)
219-
# update_trace!(trace, cache.nsteps + 1, get_u(cache), get_fu(cache),
220-
# ApplyArray(__safe_inv, J), cache.du, α)
221217
else
218+
J = uses_jac_inverse isa Val{true} ? pinv(J) : J
222219
update_trace!(trace, cache.nsteps + 1, get_u(cache), get_fu(cache), J, cache.du, α)
223220
end
224221
end

lib/NonlinearSolveBase/src/utils.jl

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ module Utils
22

33
using ArrayInterface: ArrayInterface
44
using FastClosures: @closure
5-
using LinearAlgebra: LinearAlgebra, Diagonal, Symmetric, norm, dot, cond, diagind, pinv
5+
using LinearAlgebra: LinearAlgebra, Diagonal, Symmetric, norm, dot, cond, diagind, pinv, inv
66
using MaybeInplace: @bb
77
using RecursiveArrayTools: AbstractVectorOfArray, ArrayPartition
88
using SciMLOperators: AbstractSciMLOperator
99
using SciMLBase: SciMLBase, AbstractNonlinearProblem, NonlinearFunction
10-
using StaticArraysCore: StaticArray, SArray
10+
using StaticArraysCore: StaticArray, SArray, SMatrix
1111

1212
using ..NonlinearSolveBase: NonlinearSolveBase, L2_NORM, Linf_NORM
1313

@@ -180,7 +180,7 @@ condition_number(::Any) = -1
180180

181181
# XXX: Move to NonlinearSolveQuasiNewton
182182
# compute `pinv` if `inv` won't work
183-
maybe_pinv!!_workspace(A) = nothing
183+
maybe_pinv!!_workspace(A) = nothing, A
184184

185185
maybe_pinv!!(workspace, A::Union{Number, AbstractMatrix}) = pinv(A)
186186
function maybe_pinv!!(workspace, A::Diagonal)
@@ -193,12 +193,12 @@ function maybe_pinv!!(workspace, A::StridedMatrix)
193193
LinearAlgebra.checksquare(A)
194194
if LinearAlgebra.istriu(A)
195195
issingular = any(iszero, @view(A[diagind(A)]))
196-
A_ = UpperTriangular(A)
197-
!issingular && return triu!(parent(inv(A_)))
196+
A_ = LinearAlgebra.UpperTriangular(A)
197+
!issingular && return LinearAlgebra.triu!(parent(inv(A_)))
198198
elseif LinearAlgebra.istril(A)
199-
A_ = LowerTriangular(A)
199+
A_ = LinearAlgebra.LowerTriangular(A)
200200
issingular = any(iszero, @view(A_[diagind(A_)]))
201-
!issingular && return tril!(parent(inv(A_)))
201+
!issingular && return LinearAlgebra.tril!(parent(inv(A_)))
202202
else
203203
F = LinearAlgebra.lu(A; check = false)
204204
if issuccess(F)
@@ -209,4 +209,34 @@ function maybe_pinv!!(workspace, A::StridedMatrix)
209209
return pinv(A)
210210
end
211211

212+
function initial_jacobian_scaling_alpha(α, u, fu, ::Any)
213+
return convert(promote_type(eltype(u), eltype(fu)), α)
214+
end
215+
function initial_jacobian_scaling_alpha(::Nothing, u, fu, internalnorm::F) where {F}
216+
fu_norm = internalnorm(fu)
217+
fu_norm < 1e-5 && return initial_jacobian_scaling_alpha(true, u, fu, internalnorm)
218+
return (2 * fu_norm) / max(L2_NORM(u), true)
219+
end
220+
221+
make_identity!!(::T, α) where {T <: Number} = T(α)
222+
function make_identity!!(A::AbstractVector{T}, α) where {T}
223+
@bb @. A = T(α)
224+
return A
225+
end
226+
function make_identity!!(::SMatrix{S1, S2, T, L}, α) where {S1, S2, T, L}
227+
return SMatrix{S1, S2, T, L}(LinearAlgebra.I * α)
228+
end
229+
function make_identity!!(A::AbstractMatrix{T}, α) where {T}
230+
A = ArrayInterface.can_setindex(A) ? A : similar(A)
231+
fill!(A, false)
232+
if ArrayInterface.fast_scalar_indexing(A)
233+
@simd ivdep for i in axes(A, 1)
234+
@inbounds A[i, i] = α
235+
end
236+
else
237+
A[diagind(A)] .= α
238+
end
239+
return A
240+
end
241+
212242
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)