Skip to content

Commit 498964c

Browse files
committed
refactor: move Descent Directions to NonlinearSolveBase
1 parent e805aef commit 498964c

21 files changed

+900
-803
lines changed

docs/src/devdocs/operators.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
# Custom SciML Operators
22

3-
## Abstract Operators
4-
5-
```@docs
6-
NonlinearSolve.AbstractNonlinearSolveOperator
7-
```
8-
93
## Low-Rank Jacobian Operators
104

115
```@docs

lib/NonlinearSolveBase/Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ FunctionProperties = "f62d2435-5019-4c03-9749-2d4c77af0cbc"
1717
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1818
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
1919
MaybeInplace = "bb5d69b7-63fc-4a16-80bd-7e42200c7bdb"
20+
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
2021
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
2122
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
2223
SciMLJacobianOperators = "19f34311-ddf3-4b8b-af20-060888a46c0e"
2324
SciMLOperators = "c0aeaf25-5076-4817-a8d5-81caf7dfa961"
2425
StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
26+
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
2527

2628
[weakdeps]
2729
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
@@ -57,6 +59,7 @@ LinearAlgebra = "1.10"
5759
LinearSolve = "2.36.1"
5860
Markdown = "1.10"
5961
MaybeInplace = "0.1.4"
62+
Preferences = "1.4"
6063
RecursiveArrayTools = "3"
6164
SciMLBase = "2.50"
6265
SciMLJacobianOperators = "0.1.1"
@@ -65,6 +68,7 @@ SparseArrays = "1.10"
6568
SparseMatrixColorings = "0.4.8"
6669
StaticArraysCore = "1.4"
6770
Test = "1.10"
71+
TimerOutputs = "0.5.23"
6872
julia = "1.10"
6973

7074
[extras]
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module NonlinearSolveBaseSparseArraysExt
22

3-
using NonlinearSolveBase: NonlinearSolveBase
3+
using NonlinearSolveBase: NonlinearSolveBase, Utils
44
using SparseArrays: AbstractSparseMatrix, AbstractSparseMatrixCSC, nonzeros
55

66
function NonlinearSolveBase.NAN_CHECK(x::AbstractSparseMatrixCSC)
@@ -9,4 +9,6 @@ end
99

1010
NonlinearSolveBase.sparse_or_structured_prototype(::AbstractSparseMatrix) = true
1111

12+
Utils.maybe_symmetric(x::AbstractSparseMatrix) = x
13+
1214
end

lib/NonlinearSolveBase/src/NonlinearSolveBase.jl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ using DifferentiationInterface: DifferentiationInterface, Constant
1111
using EnzymeCore: EnzymeCore
1212
using FastClosures: @closure
1313
using FunctionProperties: hasbranching
14-
using LinearAlgebra: Diagonal, norm, ldiv!
14+
using LinearAlgebra: LinearAlgebra, Diagonal, norm, ldiv!, diagind
1515
using Markdown: @doc_str
1616
using MaybeInplace: @bb
17+
using Preferences: @load_preference
1718
using RecursiveArrayTools: AbstractVectorOfArray, ArrayPartition
1819
using SciMLBase: SciMLBase, ReturnCode, AbstractODEIntegrator, AbstractNonlinearProblem,
1920
AbstractNonlinearAlgorithm, AbstractNonlinearFunction,
@@ -37,6 +38,14 @@ include("termination_conditions.jl")
3738
include("autodiff.jl")
3839
include("jacobian.jl")
3940
include("linear_solve.jl")
41+
include("timer_outputs.jl")
42+
43+
include("descent/common.jl")
44+
include("descent/newton.jl")
45+
include("descent/steepest.jl")
46+
include("descent/damped_newton.jl")
47+
include("descent/dogleg.jl")
48+
include("descent/geodesic_acceleration.jl")
4049

4150
# Unexported Public API
4251
@compat(public, (L2_NORM, Linf_NORM, NAN_CHECK, UNITLESS_ABS2, get_tolerance))
@@ -55,4 +64,7 @@ export RelTerminationMode, AbsTerminationMode,
5564
RelNormSafeTerminationMode, AbsNormSafeTerminationMode,
5665
RelNormSafeBestTerminationMode, AbsNormSafeBestTerminationMode
5766

67+
export DescentResult, SteepestDescent, NewtonDescent, DampedNewtonDescent, Dogleg,
68+
GeodesicAcceleration
69+
5870
end

lib/NonlinearSolveBase/src/abstract_types.jl

Lines changed: 177 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,65 @@ function reinit! end
66

77
end
88

9-
abstract type AbstractDescentDirection end
9+
abstract type AbstractNonlinearSolveBaseAPI end # Mostly used for pretty-printing
10+
11+
function Base.show(io::IO, ::MIME"text/plain", alg::AbstractNonlinearSolveBaseAPI)
12+
main_name = nameof(typeof(alg))
13+
modifiers = String[]
14+
for field in fieldnames(typeof(alg))
15+
val = getfield(alg, field)
16+
Utils.is_default_value(val, field, getfield(alg, field)) && continue
17+
push!(modifiers, "$(field) = $(val)")
18+
end
19+
print(io, "$(main_name)($(join(modifiers, ", ")))")
20+
return
21+
end
22+
23+
"""
24+
AbstractDescentDirection
25+
26+
Abstract Type for all Descent Directions used in NonlinearSolveBase. Given the Jacobian
27+
`J` and the residual `fu`, these algorithms compute the descent direction `δu`.
28+
29+
For non-square Jacobian problems, if we need to solve a linear solve problem, we use a
30+
least squares solver by default, unless the provided `linsolve` can't handle non-square
31+
matrices, in which case we use the normal form equations ``JᵀJ δu = Jᵀ fu``. Note that
32+
this factorization is often the faster choice, but it is not as numerically stable as
33+
the least squares solver.
34+
35+
### `InternalAPI.init` specification
36+
37+
```julia
38+
InternalAPI.init(
39+
prob::AbstractNonlinearProblem, alg::AbstractDescentDirection, J, fu, u;
40+
pre_inverted::Val = Val(false), linsolve_kwargs = (;),
41+
abstol = nothing, reltol = nothing, alias_J::Bool = true,
42+
shared::Val = Val(1), kwargs...
43+
)::AbstractDescentCache
44+
```
45+
46+
- `pre_inverted`: whether or not the Jacobian has been pre_inverted.
47+
- `linsolve_kwargs`: keyword arguments to pass to the linear solver.
48+
- `abstol`: absolute tolerance for the linear solver.
49+
- `reltol`: relative tolerance for the linear solver.
50+
- `alias_J`: whether or not to alias the Jacobian.
51+
- `shared`: Store multiple descent directions in the cache. Allows efficient and
52+
correct reuse of factorizations if needed.
53+
54+
Some of the algorithms also allow additional keyword arguments. See the documentation for
55+
the specific algorithm for more information.
56+
57+
### Interface Functions
58+
59+
- `supports_trust_region(alg)`: whether or not the algorithm supports trust region
60+
methods. Defaults to `false`.
61+
- `supports_line_search(alg)`: whether or not the algorithm supports line search
62+
methods. Defaults to `false`.
63+
64+
See also [`NewtonDescent`](@ref), [`Dogleg`](@ref), [`SteepestDescent`](@ref),
65+
[`DampedNewtonDescent`](@ref).
66+
"""
67+
abstract type AbstractDescentDirection <: AbstractNonlinearSolveBaseAPI end
1068

1169
supports_line_search(::AbstractDescentDirection) = false
1270
supports_trust_region(::AbstractDescentDirection) = false
@@ -15,7 +73,46 @@ function get_linear_solver(alg::AbstractDescentDirection)
1573
return Utils.safe_getproperty(alg, Val(:linsolve))
1674
end
1775

18-
abstract type AbstractDescentCache end
76+
"""
77+
AbstractDescentCache
78+
79+
Abstract Type for all Descent Caches.
80+
81+
### `InternalAPI.solve!` specification
82+
83+
```julia
84+
InternalAPI.solve!(
85+
cache::AbstractDescentCache, J, fu, u, idx::Val;
86+
skip_solve::Bool = false, new_jacobian::Bool = true, kwargs...
87+
)::DescentResult
88+
```
89+
90+
- `J`: Jacobian or Inverse Jacobian (if `pre_inverted = Val(true)`).
91+
- `fu`: residual.
92+
- `u`: current state.
93+
- `idx`: index of the descent problem to solve and return. Defaults to `Val(1)`.
94+
- `skip_solve`: Skip the direction computation and return the previous direction.
95+
Defaults to `false`. This is useful for Trust Region Methods where the previous
96+
direction was rejected and we want to try with a modified trust region.
97+
- `new_jacobian`: Whether the Jacobian has been updated. Defaults to `true`.
98+
- `kwargs`: keyword arguments to pass to the linear solver if there is one.
99+
100+
#### Returned values
101+
102+
- `descent_result`: Result in a [`DescentResult`](@ref).
103+
104+
### Interface Functions
105+
106+
- `get_du(cache)`: get the descent direction.
107+
- `get_du(cache, ::Val{N})`: get the `N`th descent direction.
108+
- `set_du!(cache, δu)`: set the descent direction.
109+
- `set_du!(cache, δu, ::Val{N})`: set the `N`th descent direction.
110+
- `last_step_accepted(cache)`: whether or not the last step was accepted. Checks if the
111+
cache has a `last_step_accepted` field and returns it if it does, else returns `true`.
112+
- `preinverted_jacobian(cache)`: whether or not the Jacobian has been preinverted.
113+
- `normal_form(cache)`: whether or not the linear solver uses normal form.
114+
"""
115+
abstract type AbstractDescentCache <: AbstractNonlinearSolveBaseAPI end
19116

20117
SciMLBase.get_du(cache::AbstractDescentCache) = cache.δu
21118
SciMLBase.get_du(cache::AbstractDescentCache, ::Val{1}) = SciMLBase.get_du(cache)
@@ -29,6 +126,79 @@ function last_step_accepted(cache::AbstractDescentCache)
29126
return true
30127
end
31128

129+
for fname in (:preinverted_jacobian, :normal_form)
130+
@eval function $(fname)(alg::AbstractDescentCache)
131+
res = Utils.unwrap_val(Utils.safe_getproperty(alg, Val($(QuoteNode(fname)))))
132+
res === missing && return false
133+
return res
134+
end
135+
end
136+
137+
"""
138+
AbstractDampingFunction
139+
140+
Abstract Type for Damping Functions in DampedNewton.
141+
142+
### `InternalAPI.init` specification
143+
144+
```julia
145+
InternalAPI.init(
146+
prob::AbstractNonlinearProblem, f::AbstractDampingFunction, initial_damping,
147+
J, fu, u, args...;
148+
internalnorm::F = L2_NORM, kwargs...
149+
)::AbstractDampingFunctionCache
150+
```
151+
152+
Returns a [`NonlinearSolveBase.AbstractDampingFunctionCache`](@ref).
153+
"""
154+
abstract type AbstractDampingFunction <: AbstractNonlinearAlgorithm end
155+
156+
"""
157+
AbstractDampingFunctionCache
158+
159+
Abstract Type for the Caches created by AbstractDampingFunctions
160+
161+
### Interface Functions
162+
163+
- `requires_normal_form_jacobian(alg)`: whether or not the Jacobian is needed in normal
164+
form. No default.
165+
- `requires_normal_form_rhs(alg)`: whether or not the residual is needed in normal form.
166+
No default.
167+
- `returns_norm_form_damping(alg)`: whether or not the damping function returns the
168+
damping factor in normal form. Defaults to
169+
`requires_normal_form_jacobian(alg) || requires_normal_form_rhs(alg)`.
170+
- `(cache::AbstractDampingFunctionCache)(::Nothing)`: returns the damping factor. The type
171+
of the damping factor returned from `solve!` is guaranteed to be the same as this.
172+
173+
### `InternalAPI.solve!` specification
174+
175+
```julia
176+
InternalAPI.solve!(
177+
cache::AbstractDampingFunctionCache, J, fu, u, δu, descent_stats
178+
)
179+
```
180+
181+
Returns the damping factor.
182+
"""
183+
abstract type AbstractDampingFunctionCache <: AbstractNonlinearAlgorithm end
184+
185+
function requires_normal_form_jacobian end
186+
function requires_normal_form_rhs end
187+
function returns_norm_form_damping(f::F) where {F}
188+
return requires_normal_form_jacobian(f) || requires_normal_form_rhs(f)
189+
end
190+
191+
"""
192+
AbstractNonlinearSolveAlgorithm <: AbstractNonlinearAlgorithm
193+
194+
Abstract Type for all NonlinearSolveBase Algorithms.
195+
196+
### Interface Functions
197+
198+
- `concrete_jac(alg)`: whether or not the algorithm uses a concrete Jacobian. Defaults
199+
to `nothing`.
200+
- `get_name(alg)`: get the name of the algorithm.
201+
"""
32202
abstract type AbstractNonlinearSolveAlgorithm <: AbstractNonlinearAlgorithm end
33203

34204
get_name(alg::AbstractNonlinearSolveAlgorithm) = Utils.safe_getproperty(alg, Val(:name))
@@ -47,20 +217,20 @@ concrete_jac(v::Bool) = v
47217
concrete_jac(::Val{false}) = false
48218
concrete_jac(::Val{true}) = true
49219

50-
abstract type AbstractNonlinearSolveCache end
220+
abstract type AbstractNonlinearSolveCache <: AbstractNonlinearSolveBaseAPI end
51221

52222
"""
53223
AbstractLinearSolverCache
54224
55-
Abstract Type for all Linear Solvers used in NonlinearSolve. Subtypes of these are
225+
Abstract Type for all Linear Solvers used in NonlinearSolveBase. Subtypes of these are
56226
meant to be constructured via [`construct_linear_solver`](@ref).
57227
"""
58-
abstract type AbstractLinearSolverCache end
228+
abstract type AbstractLinearSolverCache <: AbstractNonlinearSolveBaseAPI end
59229

60230
"""
61231
AbstractJacobianCache
62232
63-
Abstract Type for all Jacobian Caches used in NonlinearSolve. Subtypes of these are
233+
Abstract Type for all Jacobian Caches used in NonlinearSolveBase. Subtypes of these are
64234
meant to be constructured via [`construct_jacobian_cache`](@ref).
65235
"""
66-
abstract type AbstractJacobianCache end
236+
abstract type AbstractJacobianCache <: AbstractNonlinearSolveBaseAPI end

src/descent/common.jl renamed to lib/NonlinearSolveBase/src/descent/common.jl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""
2-
DescentResult(; δu = missing, u = missing, success::Bool = true,
3-
linsolve_success::Bool = true, extras = (;))
2+
DescentResult(;
3+
δu = missing, u = missing, success::Bool = true, linsolve_success::Bool = true,
4+
extras = (;)
5+
)
46
57
Construct a `DescentResult` object.
68
@@ -23,8 +25,10 @@ Construct a `DescentResult` object.
2325
extras
2426
end
2527

26-
function DescentResult(; δu = missing, u = missing, success::Bool = true,
27-
linsolve_success::Bool = true, extras = (;))
28+
function DescentResult(;
29+
δu = missing, u = missing, success::Bool = true, linsolve_success::Bool = true,
30+
extras = (;)
31+
)
2832
@assert δu !== missing || u !== missing
2933
return DescentResult(δu, u, success, linsolve_success, extras)
3034
end

0 commit comments

Comments
 (0)