|
| 1 | +""" |
| 2 | + JacobianReuse(; max_age::Int = 10, max_residual_ratio::Real = 1) |
| 3 | +
|
| 4 | +Reuse a Jacobian across accepted nonlinear iterations. This turns a first-order method into |
| 5 | +an adaptive modified-Newton method: the current Jacobian is reused while the residual norm |
| 6 | +continues to improve, subject to a maximum Jacobian age. Solvers of an unchanged concrete |
| 7 | +linear system also reuse its factorization; damped and matrix-free systems retain their own |
| 8 | +linear-solver update behavior. |
| 9 | +
|
| 10 | +The Jacobian is refreshed when either of these conditions holds: |
| 11 | +
|
| 12 | + - `max_age` accepted steps have used the current Jacobian; |
| 13 | + - the new residual norm is not strictly less than `max_residual_ratio` times the previous |
| 14 | + residual norm; |
| 15 | + - a linear solve or globalization step fails with stale Jacobian information. |
| 16 | +
|
| 17 | +`max_age = 1` recomputes the Jacobian after every accepted step. Setting |
| 18 | +`max_residual_ratio = Inf` selects purely periodic refreshes. The reuse state is reset by |
| 19 | +`reinit!`; retaining a Jacobian across separate nonlinear solves requires the manual |
| 20 | +`step!(cache; recompute_jacobian = false)` interface. |
| 21 | +
|
| 22 | +Pass `jacobian_reuse = JacobianReuse()` (or `jacobian_reuse = true`) to |
| 23 | +[`NewtonRaphson`](@ref), [`TrustRegion`](@ref), or another first-order solver to enable the |
| 24 | +policy. Jacobian reuse is disabled by default. |
| 25 | +""" |
| 26 | +struct JacobianReuse{R <: Real} |
| 27 | + max_age::Int |
| 28 | + max_residual_ratio::R |
| 29 | + |
| 30 | + function JacobianReuse(max_age::Int, max_residual_ratio::R) where {R <: Real} |
| 31 | + max_age > 0 || throw(ArgumentError("`max_age` must be positive, got $max_age.")) |
| 32 | + max_residual_ratio >= 0 || throw( |
| 33 | + ArgumentError( |
| 34 | + "`max_residual_ratio` must be nonnegative, got $max_residual_ratio." |
| 35 | + ) |
| 36 | + ) |
| 37 | + return new{R}(max_age, max_residual_ratio) |
| 38 | + end |
| 39 | +end |
| 40 | + |
| 41 | +function JacobianReuse(; max_age::Int = 10, max_residual_ratio::Real = 1) |
| 42 | + return JacobianReuse(max_age, max_residual_ratio) |
| 43 | +end |
| 44 | + |
| 45 | +normalize_jacobian_reuse(::Nothing) = nothing |
| 46 | +normalize_jacobian_reuse(reuse::JacobianReuse) = reuse |
| 47 | +normalize_jacobian_reuse(reuse::Bool) = reuse ? JacobianReuse() : nothing |
| 48 | +function normalize_jacobian_reuse(reuse) |
| 49 | + throw( |
| 50 | + ArgumentError( |
| 51 | + "`jacobian_reuse` must be `nothing`, a `Bool`, or a `JacobianReuse`, got $(typeof(reuse))." |
| 52 | + ) |
| 53 | + ) |
| 54 | +end |
| 55 | + |
| 56 | +@concrete mutable struct JacobianReuseCache |
| 57 | + residual_norm |
| 58 | + age::Int |
| 59 | + internalnorm |
| 60 | +end |
| 61 | + |
| 62 | +init_jacobian_reuse_cache(::Nothing, fu, internalnorm) = nothing |
| 63 | +function init_jacobian_reuse_cache(::JacobianReuse, fu, internalnorm) |
| 64 | + return JacobianReuseCache(internalnorm(fu), 0, internalnorm) |
| 65 | +end |
| 66 | + |
| 67 | +reset_jacobian_reuse!(::Nothing, fu) = nothing |
| 68 | +function reset_jacobian_reuse!(cache::JacobianReuseCache, fu) |
| 69 | + cache.residual_norm = cache.internalnorm(fu) |
| 70 | + cache.age = 0 |
| 71 | + return nothing |
| 72 | +end |
| 73 | + |
| 74 | +mark_jacobian_refresh!(cache, fu) = reset_jacobian_reuse!(cache, fu) |
| 75 | + |
| 76 | +jacobian_is_stale(::Nothing) = false |
| 77 | +jacobian_is_stale(cache::JacobianReuseCache) = cache.age > 0 |
| 78 | + |
| 79 | +function prepare_next_jacobian!(::Nothing, ::Nothing, fu) |
| 80 | + return true |
| 81 | +end |
| 82 | +function prepare_next_jacobian!(cache::JacobianReuseCache, policy::JacobianReuse, fu) |
| 83 | + residual_norm = cache.internalnorm(fu) |
| 84 | + cache.age += 1 |
| 85 | + residual_improved = isfinite(residual_norm) && isfinite(cache.residual_norm) && |
| 86 | + residual_norm < policy.max_residual_ratio * cache.residual_norm |
| 87 | + cache.residual_norm = residual_norm |
| 88 | + return !(residual_improved && cache.age < policy.max_age) |
| 89 | +end |
0 commit comments