SimpleSolvers
SimpleSolvers.ARMIJO_τ_DEMAND_FRACTIONSimpleSolvers.BACKTRACKING_SHRINK_MINSimpleSolvers.DEFAULT_ARMIJO_pSimpleSolvers.DEFAULT_ARMIJO_α₀SimpleSolvers.DEFAULT_ARMIJO_τ_ULPSSimpleSolvers.DEFAULT_BRACKETING_kSimpleSolvers.DEFAULT_BRACKETING_nmaxSimpleSolvers.DEFAULT_BRACKETING_sSimpleSolvers.DEFAULT_ITERATIONS_QUASI_NEWTON_SOLVERSimpleSolvers.DEFAULT_PICARD_BACKTRACKING_pSimpleSolvers.DEFAULT_WOLFE_c₁SimpleSolvers.DEFAULT_WOLFE_c₂SimpleSolvers.DEFAULT_WOLFE_αmaxSimpleSolvers.DEFAULT_s_REDUCTIONSimpleSolvers.DOGLEG_Δ_EXPANDSimpleSolvers.DOGLEG_Δ_INITIALSimpleSolvers.DOGLEG_Δ_MAXSimpleSolvers.DOGLEG_Δ_SHRINKSimpleSolvers.DOGLEG_ηSimpleSolvers.DOGLEG_ρ_HIGHSimpleSolvers.DOGLEG_ρ_LOWSimpleSolvers.MAX_STALLSSimpleSolvers.N_STATIC_THRESHOLDSimpleSolvers.AbstractLinearProblemSimpleSolvers.AbstractNonlinearSolverCacheSimpleSolvers.BacktrackingSimpleSolvers.BacktrackingConditionSimpleSolvers.BierlaireQuadraticSimpleSolvers.BisectionSimpleSolvers.BracketMinimumCriterionSimpleSolvers.BracketRootCriterionSimpleSolvers.BracketingCriterionSimpleSolvers.CurvatureConditionSimpleSolvers.DogLegSimpleSolvers.DogLegCacheSimpleSolvers.DogLegSolverSimpleSolvers.GradientSimpleSolvers.GradientAutodiffSimpleSolvers.GradientFiniteDifferencesSimpleSolvers.GradientFunctionSimpleSolvers.HessianSimpleSolvers.HessianAutodiffSimpleSolvers.HessianFunctionSimpleSolvers.JacobianSimpleSolvers.JacobianSimpleSolvers.JacobianAutodiffSimpleSolvers.JacobianFiniteDifferencesSimpleSolvers.JacobianFunctionSimpleSolvers.LUSimpleSolvers.LUSolverCacheSimpleSolvers.LinearProblemSimpleSolvers.LinearSolverSimpleSolvers.LinearSolverCacheSimpleSolvers.LinearSolverMethodSimpleSolvers.LinesearchSimpleSolvers.LinesearchMethodSimpleSolvers.LinesearchOutcomeSimpleSolvers.LinesearchProblemSimpleSolvers.LinesearchStatusSimpleSolvers.LinesearchStatusSimpleSolvers.NewtonSimpleSolvers.NewtonSolverSimpleSolvers.NewtonSolverSimpleSolvers.NoLinearProblemSimpleSolvers.NonlinearProblemSimpleSolvers.NonlinearSolverSimpleSolvers.NonlinearSolverCacheSimpleSolvers.NonlinearSolverMethodSimpleSolvers.NonlinearSolverStateSimpleSolvers.NonlinearSolverStatusSimpleSolvers.OptionsSimpleSolvers.PicardSimpleSolvers.PicardSolverSimpleSolvers.QuadraticSimpleSolvers.StaticSimpleSolvers.StrongWolfeSimpleSolvers.SufficientDecreaseConditionGeometricBase.update!GeometricBase.update!LinearAlgebra.ldiv!SimpleSolvers.QuasiNewtonSimpleSolvers._staticSimpleSolvers.absolute_toleranceSimpleSolvers.alloc_gSimpleSolvers.alloc_hSimpleSolvers.alloc_xSimpleSolvers.armijo_toleranceSimpleSolvers.armijo_ulpsSimpleSolvers.assess_convergenceSimpleSolvers.backtracking_interpolationSimpleSolvers.backtracking_αminSimpleSolvers.bisectionSimpleSolvers.bracketSimpleSolvers.bracket_minimumSimpleSolvers.bracket_minimum_with_fixed_pointSimpleSolvers.bracket_rootSimpleSolvers.cacheSimpleSolvers.change_precisionSimpleSolvers.check_anchorSimpleSolvers.check_gradientSimpleSolvers.check_hessianSimpleSolvers.check_jacobianSimpleSolvers.clear!SimpleSolvers.compute_new_iterate!SimpleSolvers.curvature_diagnosticSimpleSolvers.default_precisionSimpleSolvers.default_toleranceSimpleSolvers.default_ϵSimpleSolvers.directionSimpleSolvers.direction!SimpleSolvers.directions!SimpleSolvers.direction₁SimpleSolvers.direction₂SimpleSolvers.dogleg_direction!SimpleSolvers.factorize!SimpleSolvers.flag_stall!SimpleSolvers.increase_iteration_number!SimpleSolvers.initial_residualSimpleSolvers.initialize!SimpleSolvers.initialize!SimpleSolvers.isconvergedSimpleSolvers.isfloorSimpleSolvers.isstalledSimpleSolvers.issufficientSimpleSolvers.iterate_settledSimpleSolvers.iteration_numberSimpleSolvers.jacobianSimpleSolvers.jacobianmatrixSimpleSolvers.linearsolverSimpleSolvers.linesearch_iterationsSimpleSolvers.linesearch_max_iterationsSimpleSolvers.linesearch_problemSimpleSolvers.linesearch_problemSimpleSolvers.linesearch_warningsSimpleSolvers.lucache_eltypeSimpleSolvers.max_stallsSimpleSolvers.maybe_refactorize!SimpleSolvers.meets_stopping_criteriaSimpleSolvers.methodSimpleSolvers.minimum_decrease_thresholdSimpleSolvers.nan_recovery!SimpleSolvers.needs_refreshSimpleSolvers.nonlinear_solver_warningsSimpleSolvers.nonlinearproblemSimpleSolvers.outcomeSimpleSolvers.pivot_indexSimpleSolvers.print_jacobianSimpleSolvers.print_statusSimpleSolvers.record_stall!SimpleSolvers.report_linesearch_statusSimpleSolvers.residual_smallSimpleSolvers.residualsSimpleSolvers.resolve_jacobianSimpleSolvers.rhsSimpleSolvers.shift_χ_to_avoid_stallingSimpleSolvers.solveSimpleSolvers.solveSimpleSolvers.solveSimpleSolvers.solveSimpleSolvers.solveSimpleSolvers.solveSimpleSolvers.solveSimpleSolvers.solveSimpleSolvers.solve!SimpleSolvers.solve!SimpleSolvers.solve!SimpleSolvers.solve!SimpleSolvers.solve!SimpleSolvers.solve_with_statusSimpleSolvers.solver_step!SimpleSolvers.solver_step!SimpleSolvers.stall_numberSimpleSolvers.stalled_stepSimpleSolvers.statusSimpleSolvers.steplengthSimpleSolvers.trialsSimpleSolvers.triple_point_finderSimpleSolvers.trust_radiusSimpleSolvers.trust_radius!SimpleSolvers.value!SimpleSolvers.with_config
SimpleSolvers.ARMIJO_τ_DEMAND_FRACTION — Constant
const ARMIJO_τ_DEMAND_FRACTIONThe largest fraction of the demanded decrease that the round-off resolution $\tau$ is allowed to amount to, used by armijo_ulps to cap $\tau$ in low precision. Its value is 0.01, i.e. $\tau$ may distort the SufficientDecreaseCondition by at most one percent.
SimpleSolvers.BACKTRACKING_SHRINK_MIN — Constant
const BACKTRACKING_SHRINK_MINLower bound on the factor by which a rejected step is shrunk by the interpolation in Backtracking: the new trial step is confined to $[\mathrm{BACKTRACKING\_SHRINK\_MIN}\cdot\alpha, p\alpha]$ (see [1, §3.5], [2, Alg. A6.3.1]). Its value is 0.1.
SimpleSolvers.DEFAULT_ARMIJO_p — Constant
const DEFAULT_ARMIJO_pConstant used in Backtracking. Its value is 0.5
This is the default for the constant $p$ by which α is decreased if the SufficientDecreaseCondition and the CurvatureCondition are not satisfied.
SimpleSolvers.DEFAULT_ARMIJO_α₀ — Constant
const DEFAULT_ARMIJO_α₀The default starting value for $\alpha$ used in Backtracking. Its value is 1.0.
SimpleSolvers.DEFAULT_ARMIJO_τ_ULPS — Constant
const DEFAULT_ARMIJO_τ_ULPSThe nominal number of units in the last place (ulps) of $\varphi(0)$ taken as the round-off resolution $\tau$ of a merit function, i.e. $\tau = \mathrm{DEFAULT\_ARMIJO\_τ\_ULPS}\cdot\mathrm{ulp}(\varphi(0))$ (see armijo_tolerance). Its value is 4.
Use armijo_ulps rather than this constant: it caps the nominal value at what the element type can actually support, which matters in Float16.
$\tau$ is used for three things, and it is worth keeping them apart:
- it slackens the
SufficientDecreaseConditioninsideBacktrackingto $\varphi(\alpha) \leq \min\{\varphi(0),\ \varphi(0) + c_1\alpha\varphi'(0) + \tau\}$. The $\min$ is what keeps the allowance honest: it may reduce the decrease demanded, but it can never license a step whose merit exceeds $\varphi(0)$; - it fixes the smallest informative trial step $\alpha_\mathrm{min}$ below which the condition could only be decided by rounding (see
backtracking_αmin); - every
LinesearchMethoduses it to decide whether an accepted step's decrease was genuine (LINESEARCH_DECREASED) or within the noise (LINESEARCH_FLOOR, seeLinesearchOutcome).
Set τ_ulps = 0 in Backtracking to recover the exact condition for (1) and (2).
SimpleSolvers.DEFAULT_BRACKETING_k — Constant
const DEFAULT_BRACKETING_kGives the default ratio by which the bracket is increased if bracketing was not successful. See bracket_minimum.
SimpleSolvers.DEFAULT_BRACKETING_nmax — Constant
Default constant. Number of maximum iterations for bracket_minimum, bracket_minimum_with_fixed_point and bracket_root.
SimpleSolvers.DEFAULT_BRACKETING_s — Constant
const DEFAULT_BRACKETING_sGives the default initial width of the interval (the bracket). Used for bracket_minimum, bracket_minimum_with_fixed_point and bracket_root.
SimpleSolvers.DEFAULT_ITERATIONS_QUASI_NEWTON_SOLVER — Constant
The default number of iterations before the Jacobian is refactored when constructing a quasi-Newton method via QuasiNewton.
SimpleSolvers.DEFAULT_PICARD_BACKTRACKING_p — Constant
Backtracking shrink factor used by the PicardSolver residual safeguard.
SimpleSolvers.DEFAULT_WOLFE_c₁ — Constant
const DEFAULT_WOLFE_c₁A constant $c_1$ that is used in the SufficientDecreaseCondition (the Armijo condition):
\[f(\alpha) \leq f(\alpha_0) + c_1 \alpha f'(\alpha_0).\]
SimpleSolvers.DEFAULT_WOLFE_c₂ — Constant
const DEFAULT_WOLFE_c₂The constant used in the second Wolfe condition (the CurvatureCondition). According to [1, 3] we should have
\[c_2 \in (c_1, 1),\]
where $c_1$ is the constant specified by DEFAULT_WOLFE_c₁.
Furthermore [1] recommend $c_2 = 0.9$; in [3] the authors write: "it is common to set $c_2=0.1$ when approximate line search is used with the conjugate gradient method and to 0.9 when used with Newton's method." We use $c_2 = 0.9$ as default.
SimpleSolvers.DEFAULT_WOLFE_αmax — Constant
const DEFAULT_WOLFE_αmaxDefault upper bound on the step length for the bracketing phase of StrongWolfe. Its value is 65536.0.
SimpleSolvers.DEFAULT_s_REDUCTION — Constant
A factor by which s is reduced in each bracketing iteration (see bracket_minimum_with_fixed_point).
SimpleSolvers.DOGLEG_Δ_EXPAND — Constant
Factor by which the trust-region radius is expanded on a very good step ($\rho > 3/4$ at the boundary); the default of the dogleg_radius_expand field of Options, which the solver actually reads.
SimpleSolvers.DOGLEG_Δ_INITIAL — Constant
Default initial trust-region radius for the DogLegSolver; the default of the dogleg_radius_initial field of Options, which the solver actually reads.
SimpleSolvers.DOGLEG_Δ_MAX — Constant
Default maximum trust-region radius ($\hat\Delta$ in [1, Alg. 4.1]) for the DogLegSolver; the radius is never expanded beyond this. The default of the dogleg_radius_max field of Options, which the solver actually reads.
SimpleSolvers.DOGLEG_Δ_SHRINK — Constant
Factor by which the trust-region radius is shrunk on a poor step ($\rho < 1/4$); the default of the dogleg_radius_shrink field of Options, which the solver actually reads.
SimpleSolvers.DOGLEG_η — Constant
Minimum ρ (actual/predicted reduction) for a step to be accepted ($\eta$ in [1, Alg. 4.1]).
SimpleSolvers.DOGLEG_ρ_HIGH — Constant
Upper ρ threshold above which the trust-region radius may be expanded.
SimpleSolvers.DOGLEG_ρ_LOW — Constant
Lower ρ threshold below which the trust-region radius is shrunk.
SimpleSolvers.MAX_STALLS — Constant
const MAX_STALLSThe default number of consecutive stalled steps after which a NonlinearSolver gives up; the default of the max_stalls field of Options. Its value is 2.
A step is stalled when it does not move the iterate while the residual is not small (see stalled_step), i.e. when the merit $\|F\|^2$ cannot be reduced along the current direction. One stalled step is not conclusive, because the next step is guaranteed to be attempted under better conditions: a stall forces a fresh Jacobian immediately (see maybe_refactorize! and needs_refresh) rather than waiting for the next refactorize multiple, and the DogLegSolver additionally resets a collapsed trust-region radius. A second consecutive stall is therefore one that a freshly evaluated Jacobian did not fix, which is conclusive — and it is conclusive for every refactorize, not just refactorize = 1.
Set max_stalls = typemax(Int) to restore the previous behaviour of running all the way to max_iterations.
SimpleSolvers.N_STATIC_THRESHOLD — Constant
Threshold for the maximum size a static matrix should have. See _static.
SimpleSolvers.AbstractLinearProblem — Type
Encompasses the NoLinearProblem and the LinearProblem. Subtyped from AbstractProblem, coming from GeometricBase.
SimpleSolvers.AbstractNonlinearSolverCache — Type
AbstractNonlinearSolverCacheAn abstract type that comprises e.g. the NonlinearSolverCache and the DogLegCache.
SimpleSolvers.Backtracking — Type
Backtracking <: LinesearchMethodKeys
The keys are:
α₀=1.0: the initial step size $\alpha$. This is decreased iteratively by a factor $p$ until theSufficientDecreaseConditionis satisfied.c₁=0.0001: the constant $c_1$ in theSufficientDecreaseCondition(Armijo condition). Also seeDEFAULT_WOLFE_c₁.c₂=0.9: the constant on whose basis theCurvatureConditionis tested. We should have $c_2\in(c_1, 1).$ The closer this constant is to 1, the easier it is to satisfy theCurvatureCondition.p=0.5: an upper bound on the factor by which $\alpha$ is decreased in every step until the stopping criterion is satisfied. The actual factor is chosen by interpolation and confined to $[$BACKTRACKING_SHRINK_MIN$\cdot\alpha, p\alpha]$, so the trial sequence is never longer than the plain $\alpha \gets p\alpha$ ladder.τ_ulps=armijo_ulps(T, c₁)(4 inFloat64andFloat32, less inFloat16): the round-off resolution of the merit, in units in the last place of $\varphi(0)$. It slackens theSufficientDecreaseCondition(never past $\varphi(0)$), fixes $\alpha_\mathrm{min}$, and separates a genuine decrease from one within the noise. A value larger thanarmijo_ulps(T, c₁)is capped to it, since above that $\tau$ would swamp the decrease the condition demands. SeeDEFAULT_ARMIJO_τ_ULPS.
Implementation
The algorithm starts by setting
\[\begin{aligned} \varphi_0 &\gets \varphi(0),\\ d_0 &\gets \varphi'(0), \end{aligned}\]
where $\varphi$ is of type LinesearchProblem. Unless $\varphi_0$ and $d_0$ are finite with $d_0 < 0$ the search is abandoned at once — no $\alpha$ can satisfy the SufficientDecreaseCondition along a direction that is not decreasing, so shrinking $\alpha$ would only waste merit evaluations to find that out.
Otherwise it sets the round-off resolution $\tau$ (armijo_tolerance) and the smallest informative step $\alpha_\mathrm{min}$ (backtracking_αmin), and shrinks the trial step by backtracking_interpolation until one of the following happens:
- the
SufficientDecreaseConditionis satisfied — the step is accepted, and reported as a genuine decrease only if $\varphi(\alpha) \leq \varphi_0 - \tau$; - two consecutive trials return $\varphi(\alpha) = \varphi_0$ bit-exactly — the trial point no longer differs from the base point in floating point, so no smaller step can either;
- $\alpha \leq \alpha_\mathrm{min}$ — a smaller step could only be judged by rounding;
- the
linesearch_max_iterationsbudget ofOptionsis spent.
Cases 2–4 are distinguished in the returned LinesearchStatus: a merit that does not vary by more than $\tau$ has reached its round-off floor (LINESEARCH_FLOOR, benign and not improvable by any line search), whereas one that does vary contradicts $d_0 < 0$ (LINESEARCH_EXHAUSTED, a genuine inconsistency). See LinesearchOutcome.
The CurvatureCondition is not used to terminate the iteration — it cannot be honoured by shrinking alone — it is only checked afterwards to emit a warning (see curvature_diagnostic).
Extended help
Sometimes the parameters $p$ and $c_1$ have different names such as $\tau$ and $c$. Note that our $\tau$ is something else entirely (the round-off resolution above).
$\alpha_\mathrm{min}$ is a factor $2\,$ τ_ulps above the step $\alpha^*$ at which the condition degenerates into a test decided by rounding — provided backtracking_αmin's upper clamp at $\sqrt{\mathrm{eps}(T)}$ is inactive, which it is for a merit of ordinary steepness in double precision. Where the clamp binds (a very flat merit, or any merit in Float16) the search does trial steps below $\alpha^*$. That is deliberate and harmless: the $\min$ in the SufficientDecreaseCondition means the test there reduces to $\varphi(\alpha) \leq \varphi_0$, i.e. plain monotonicity, and such an accept is reported as LINESEARCH_FLOOR rather than as a decrease.
SimpleSolvers.BacktrackingCondition — Type
BacktrackingConditionAbstract type comprising the conditions that are used for checking step sizes for the backtracking line search (see Backtracking). This comprises the SufficientDecreaseCondition and the CurvatureCondition.
SimpleSolvers.BierlaireQuadratic — Type
BierlaireQuadratic <: LinesearchMethodAlgorithm taken from [4].
SimpleSolvers.Bisection — Type
Bisection <: LinesearchMethodSee bisection for the implementation of the algorithm.
Extended help
When invoked with a single trial step α (i.e. solve(ls, α)), the bracket is always lower-anchored at $\alpha = 0$ — the only point where a genuine descent direction is guaranteed to have a decreasing merit ($\varphi'(0) < 0$), which one-sided rightward bracketing requires. The caller's α is then folded in via one extra derivative evaluation (see issue #164):
- if $\varphi'(\alpha) \geq 0$ then
αovershot the minimum and $[0, \alpha]$ already brackets a stationary point, so it is handed straight tobisectionwith no bracketing loop; - otherwise $\alpha$ still lies on the descent side, so the bracket is grown outward from $0$ with the initial step seeded from $|\alpha|$ — clamped between
DEFAULT_BRACKETING_sand1so a largeαdoes not over-coarsen the search and a tinyαdoes not crawl — rather than the fixed default step.
This keeps the safe $\alpha = 0$ anchor while letting the caller's α set the search scale and, when it overshoots, serve directly as the upper bracket bound.
SimpleSolvers.BracketMinimumCriterion — Type
BracketMinimumCriterion <: BracketingCriterionThe criterion used for bracket_minimum. It checks whether $y(c)$ is greater than or equal to $y(b)$ (i.e. checks whether we are passed the minimum). Compare this with BracketRootCriterion.
Functor
bc = BracketMinimumCriterion()
yc = .1
yb = .2
bc(yb, yc)
# output
falseSimpleSolvers.BracketRootCriterion — Type
BracketRootCriterion <: BracketingCriterionThe criterion used for bracket_root. It checks whether there is a sign change between $b$ and $c$ (i.e. checks whether there is a root between those two points). Compare this with BracketMinimumCriterion.
Functor
bc = BracketRootCriterion()
yc = .1
yb = -.2
bc(yb, yc)
# output
trueSimpleSolvers.BracketingCriterion — Type
BracketingCriterionAbstract type for the criteria used while bracketing. It determines when a bracket has been found. The two concrete subtypes are BracketMinimumCriterion (used by bracket_minimum) and BracketRootCriterion (used by bracket_root).
SimpleSolvers.CurvatureCondition — Type
CurvatureCondition <: BacktrackingConditionThe second of the Wolfe conditions [1]. The first one is the SufficientDecreaseCondition.
This encompasses the standard curvature condition and the strong curvature condition. This can be specified via the mode keyword.
With the standard curvature condition we check:
\[f'(\alpha) ≥ c_2 d,\]
where $c_2$ is the associated hyperparameter and $d$ is the derivative at $\alpha_0$. Further note that $f'(\alpha_0)$ and $d$ should both be negative.
With the strong curvature condition we check:
\[|f'(\alpha)| ≤ c_2 |d|.\]
Constructor
CurvatureCondition(c, d₀, D, Val(:Standard))
CurvatureCondition(c, d₀, D, Val(:Strong))Here D has to be a function computing the derivative of the objective. The mode is passed as a Val (defaulting to Val(:Standard)) so that it is encoded in the type and dispatch — and hence inference — is stable without relying on constant propagation of a Symbol keyword. The other inputs are numbers.
SimpleSolvers.DogLeg — Type
DogLeg(refactorize=1)Powell's dogleg method [5].
Like Newton, the refactorize parameter determines after how many steps the Jacobian is re-evaluated and refactored (see factorize!). The default refactorize = 1 re-evaluates and refactorizes the Jacobian on every step; refactorize > 1 reuses the Jacobian (and its factorization) in between, giving a quasi-Newton-style dogleg method.
SimpleSolvers.DogLegCache — Type
DogLegCacheLike NonlinearSolverCache but storing two directions (callable with direction₁ and direction₂).
SimpleSolvers.DogLegSolver — Type
DogLegSolverThe NonlinearSolver for the DogLeg method.
Note that the DogLeg solver_step! is a trust-region method: it chooses the step length via the trust-region radius, not a line search. Unlike the NewtonSolver — but like the PicardSolver — no linesearch keyword is accepted (passing one is an error rather than being silently ignored).
SimpleSolvers.Gradient — Type
GradientAbstract type. structs that are derived from this need an associated functor that computes the gradient of a function (in-place).
Examples
Examples include:
SimpleSolvers.GradientAutodiff — Type
GradientAutodiff <: GradientA struct that realizes Gradient by using ForwardDiff.
Keys
The struct stores:
F: a function that has to be differentiated.∇config: result of applyingForwardDiff.GradientConfig.
Constructors
GradientAutodiff(F, x::AbstractVector)
GradientAutodiff{T}(F, nx::Integer)Functor
The functor does:
grad(g, x) = ForwardDiff.gradient!(g, grad.F, x, grad.∇config)SimpleSolvers.GradientFiniteDifferences — Type
GradientFiniteDifferences <: GradientA struct that realizes Gradient by using finite differences.
Keys
The struct stores:
F: a function that has to be differentiated.ϵ: small constant on whose basis the finite differences are computed.e: auxiliary vector used for computing finite differences. It's of the form $e_1 = \begin{bmatrix} 1 & 0 & \cdots & 0 \end{bmatrix}^T$.tx: auxiliary vector used for computing finite differences. It stores the offset in thexvector.
Constructor(s)
GradientFiniteDifferences{T}(F, nx::Integer; ϵ)By default for ϵ is default_ϵ(T).
Functor
The functor does (for grad(g, x)):
for j in eachindex(x,g)
ϵⱼ = grad.ϵ * abs(x[j]) + grad.ϵ
fill!(grad.e, 0)
grad.e[j] = 1
grad.tx .= x .- ϵⱼ .* grad.e
f1 = grad.F(grad.tx)
grad.tx .= x .+ ϵⱼ .* grad.e
f2 = grad.F(grad.tx)
g[j] = (f2 - f1) / (2ϵⱼ)
endSimpleSolvers.GradientFunction — Type
GradientFunction <: GradientA struct that realizes a Gradient by explicitly supplying a function.
Keys
The struct stores:
F: a function that has to be differentiated.∇F!: a function that can be applied in place.
Functor
The functor does:
grad(g, x) = grad.∇F!(g, x)SimpleSolvers.Hessian — Type
HessianAbstract type. structs derived from this need an associated functor that computes the Hessian of a function (in-place).
Also see Gradient.
Implementation
When a custom Hessian is implemented, a functor is needed:
function (hessian::Hessian)(h::AbstractMatrix, x::AbstractVector) endExamples
Examples include:
SimpleSolvers.HessianAutodiff — Type
HessianAutodiff <: HessianA struct that realizes Hessian by using ForwardDiff.
Keys
The struct stores:
F: a function that has to be differentiated.Hconfig: result of applyingForwardDiff.HessianConfig.
Constructors
HessianAutodiff{T}(F, Hconfig)
HessianAutodiff(F, x::AbstractVector)
HessianAutodiff{T}(F, nx::Integer)Functor
The functor does:
hes(H, x) = ForwardDiff.hessian!(H, hes.F, x, hes.Hconfig)SimpleSolvers.HessianFunction — Type
HessianFunction <: HessianA struct that realizes a Hessian by explicitly supplying a function.
Keys
The struct stores:
H!: a function that can be applied in place.
Functor
The functor does:
hes(H, x) = hes.H!(H, x)SimpleSolvers.Jacobian — Type
JacobianAbstract type. structs that are derived from this need an associated functor that computes the Jacobian of a function (in-place).
Implementation
When a custom Jacobian is implemented, a functor is needed:
function (j::Jacobian)(g::AbstractMatrix, x::AbstractVector) endExamples
Examples include:
SimpleSolvers.Jacobian — Method
Jacobian{T}(F, nx, ny; mode = :autodiff, kwargs...)Construct a Jacobian of element type T for a function F mapping nx inputs to ny outputs, selecting the backend via the mode keyword:
mode = :autodiff(default) builds aJacobianAutodiff(ForwardDiff).mode = :finitedifferencesbuilds aJacobianFiniteDifferences; any remaining keyword arguments (e.g.ϵ) are forwarded to it.
The convenience forms Jacobian{T}(F, n), Jacobian(F, x) and Jacobian(F, x, y) forward here.
SimpleSolvers.JacobianAutodiff — Type
JacobianAutodiff <: JacobianA struct that realizes Jacobian by using ForwardDiff.
Keys
The struct stores:
F: a function that has to be differentiated.Jconfig: result of applyingForwardDiff.JacobianConfig.ty: vector that is used for evaluatingForwardDiff.jacobian!
Constructors
JacobianAutodiff(F, x::AbstractVector)
JacobianAutodiff{T}(F, nx::Integer)Functor
The functor does:
jac(J, x, params) = ForwardDiff.jacobian!(J, (y, x) -> jac.F(y, x, params), jac.ty, x, jac.Jconfig)SimpleSolvers.JacobianFiniteDifferences — Type
JacobianFiniteDifferences <: JacobianA struct that realizes Jacobian by using finite differences.
Keys
The struct stores:
F: a function that has to be differentiated.ϵ: small constant on whose basis the finite differences are computed.f1: $f$ evaluated at $x - \epsilon_j e_j$ with $\epsilon_j = \epsilon|x_j| + \epsilon$ for all $j$.f2: $f$ evaluated at $x + \epsilon_j e_j$ with $\epsilon_j = \epsilon|x_j| + \epsilon$ for all $j$.e: auxiliary vector used for computing finite differences. It's of the form $e_1 = \begin{bmatrix} 1 & 0 & \cdots & 0 \end{bmatrix}^T$.tx: auxiliary vector used for computing finite differences. It stores the offset in thexvector.
Constructor(s)
JacobianFiniteDifferences{T}(F, nx::Integer, ny::Integer; ϵ)By default for ϵ is default_ϵ(T).
Functor
The functor does:
for j in eachindex(x)
ϵⱼ = jac.ϵ * abs(x[j]) + jac.ϵ
fill!(jac.e, 0)
jac.e[j] = 1
jac.tx .= x .- ϵⱼ .* jac.e
jac.F(jac.f1, jac.tx, params)
jac.tx .= x .+ ϵⱼ .* jac.e
jac.F(jac.f2, jac.tx, params)
for i in eachindex(jac.f1)
J[i,j] = (jac.f2[i] - jac.f1[i]) / (2ϵⱼ)
end
endSimpleSolvers.JacobianFunction — Type
JacobianFunction <: JacobianA struct that realizes a Jacobian by explicitly supplying a function taken from the NonlinearProblem.
Functor
f(y, x, params) = y .= [1. √2.; √2. 3.] * x
∇f(j, x, params) = j .= [1. √2.; √2. 3.]
jac = JacobianFunction(f, ∇f, Float64)
j = zeros(Float64, 2, 2)
x = ones(Float64, 2)
jac(j, x, NullParameters())
# output
2×2 Matrix{Float64}:
1.0 1.41421
1.41421 3.0SimpleSolvers.LU — Type
struct LU <: DirectMethodA custom implementation of an LU solver, meant to solve a LinearProblem.
Routines that use the LU solver include factorize!, ldiv! and solve!.
Constructor
The constructor is called with either no argument:
LU()
# output
LU{Missing}(missing, true)or with pivot and static as optional booleans:
LU(; pivot=true, static=true)
# output
LU{Bool}(true, true)Note that if we do not supply an explicit keyword static, the corresponding field is missing (as in the first case). In that default case the cache matrix type is chosen by size via _static: a matrix whose leading dimension does not exceed N_STATIC_THRESHOLD yields a mutable static (MMatrix) cache, a larger one yields a plain Matrix. An explicit static=true/false forces the choice regardless of the matrix size.
Example
We use the LU together with solve to solve a linear system:
A = [1. 2. 3.; 5. 7. 11.; 13. 17. 19.]
v = rand(3)
ls = LinearProblem(A, v)
lu = LU()
solve(lu, ls) ≈ inv(A) * v
# output
trueNote that role of LinearProblem here.
SimpleSolvers.LUSolverCache — Type
LUSolverCache <: LinearSolverCacheThe cache for the LU solver.
Keys
A: the factorized matrixA,pivots: a vector of pivots used during factorization,perms: a vector of permutations used during factorization,info: stores an index regarding pivoting.
SimpleSolvers.LinearProblem — Type
LinearProblemA LinearProblem describes $Ax = y$, where we want to solve for $x$.
Keys
Ay
Constructors
A LinearProblem can be allocated by calling:
LinearProblem(A, y)
LinearProblem(A)
LinearProblem(y)
LinearProblem{T}(n, m)
LinearProblem{T}(n)LinearProblem(A, y) stores copies of A and y, so the problem is ready to solve right after construction (and later mutations of the caller's arrays do not affect the stored copies):
A = [1. 2. 3.; 4. 5. 6.; 7. 8. 9.]
y = [1., 2., 3.]
ls = LinearProblem(A, y)
# output
LinearProblem{Float64, Vector{Float64}, Matrix{Float64}}([1.0 2.0 3.0; 4.0 5.0 6.0; 7.0 8.0 9.0], [1.0, 2.0, 3.0])The size-only constructors (LinearProblem(A), LinearProblem(y), LinearProblem{T}(n[, m])) allocate the unspecified parts as NaNs; use update! to fill the system with values:
ls = LinearProblem(y)
update!(ls, A, y)
# output
LinearProblem{Float64, Vector{Float64}, Matrix{Float64}}([1.0 2.0 3.0; 4.0 5.0 6.0; 7.0 8.0 9.0], [1.0, 2.0, 3.0])SimpleSolvers.LinearSolver — Type
LinearSolver <: AbstractSolverA struct that stores LinearSolverMethods (for example LU) and LinearSolverCaches (for example LUSolverCache). LinearSolvers are used to solve LinearProblems.
Constructors
LinearSolver(method, cache)
LinearSolver(method, A)
LinearSolver(method, ls::LinearProblem)
LinearSolver(method, x)We note that the constructors do not call the function factorize, so only allocate a new matrix. The factorization needs to be done manually.
You can manually factorize by either calling factorize! or solve!.
SimpleSolvers.LinearSolverCache — Type
LinearSolverCacheAn abstract type that summarizes all the caches used for LinearSolvers. See e.g. LUSolverCache.
SimpleSolvers.LinearSolverMethod — Type
LinearSolverMethod <: SolverMethodSummarizes all the methods used for solving linear systems of equations such as the LU method.
Extended help
The abstract type SolverMethod was imported from GeometricBase.
SimpleSolvers.Linesearch — Type
LinesearchA struct that stores a LinesearchProblem, LinesearchMethod and Options.
Keys
problem::LinesearchProblemmethod::LinesearchMethodconfig::Options
Constructors
The following constructors can be used:
Linesearch{T}(problem, method, config)
Linesearch(problem, method=Static(); kwargs...)
Linesearch(problem, method, config::Options)SimpleSolvers.LinesearchMethod — Type
LinesearchMethod{T} <: SolverMethodExamples include Static, Backtracking, Bisection , BierlaireQuadratic and Quadratic. See these examples for specific information on linesearch algorithms.
Extended help
A LinesearchMethod is usually used in Linesearch (or with solve).
It is a subtype of SolverMethod (imported from GeometricBase) — line searches are one-dimensional subproblems used inside nonlinear solvers and optimizers, so (unlike a NonlinearSolverMethod) a LinesearchMethod is not itself a nonlinear-solver method.
The line search contract
Every method reached through solve or solve_with_status guarantees:
- It never throws. A situation it cannot handle is reported, never raised — a line search must not abort the enclosing solve. Bracketing helpers signal failure with
nothing(seebracket_minimum,triple_point_finder) and the method maps that onto aLinesearchOutcome. - It returns $\alpha > 0$. Never the $\alpha = 0$ anchor, which would freeze the outer iterate (
x .+= 0 .* d), and never a negative step: $\alpha$ scales a direction that has already been chosen, so its sign is not the line search's to decide. - It reports through
linesearch_warningsonly — one message site and one verbosity policy for all methods (genuine failure atverbosity ≥ 1, rate limited; the benign round-off-floor and stationary outcomes at≥ 2). - A non-finite or ascending anchor is reported, not assumed away — see
check_anchor. - It terminates in a bounded number of merit evaluations, independently of the merit's scale. Multiplying $\varphi$ by a constant must not change the cost.
The two families
What is not standardised is the meaning of the input $\alpha$ and what each method guarantees about the step, because there are two distinct kinds:
- Condition-satisfying, $\alpha$-relative —
Backtracking,StrongWolfeand triviallyStatic: "given the trial step $\alpha$, return a step satisfying a decrease condition". The result depends on the input $\alpha$. - Minimising, $\alpha$-independent —
Bisection,QuadraticandBierlaireQuadratic: "approximate the minimiser of $\varphi$ along the direction". The input $\alpha$ only seeds the bracketing (see issue #164), and no Wolfe condition is checked.
SimpleSolvers.LinesearchOutcome — Type
LinesearchOutcomeWhy a LinesearchMethod stopped. Stored in a LinesearchStatus, which is returned by solve_with_status.
LINESEARCH_DECREASED: a step was found that decreased the merit by more than the round-off allowance $\tau$. This is the only outcome that reports progress. Note what it does not claim:BacktrackingandStrongWolfeadditionally verify their Wolfe condition before returning, whereas the minimising searches (Bisection,Quadratic,BierlaireQuadratic) approximate the line minimiser and never test one. The common guarantee across all of them is the $\tau$-exceeding decrease.LINESEARCH_FLOOR: the merit has reached its round-off floor — no trial step changes it by more than $\tau$, so no line search can make progress here. The returned step is the smallest informative one. This is not an error and is only reported atverbosity ≥ 2: it is the expected final state of a converged solve, and when it is not — when the residual is still large — the outer iteration reports it as stagnation instead (seestalled_stepandOptions).LINESEARCH_EXHAUSTED: no acceptable step although the merit does vary by more than $\tau$. Either $\varphi'(0)$ is inconsistent with $\varphi$ (a stale or regularizedJacobian, an inexact linear solve, a non-smooth merit), or thelinesearch_max_iterationsbudget ofOptionswas spent.LINESEARCH_NO_DESCENT: $\varphi'(0) > 0$, or $\varphi(0)$/$\varphi'(0)$ is not finite. No $\alpha$ can satisfy the sufficient decrease condition.LINESEARCH_STATIONARY: $\varphi'(0) = 0$, e.g. a vanishing direction at an exact root. Benign — there is nothing to search for.LINESEARCH_UNKNOWN: the method does not report an outcome (the generic fallback ofsolve_with_status).
SimpleSolvers.LinesearchProblem — Type
LinesearchProblem <: AbstractProblemIn practice LinesearchProblems are allocated by calling linesearch_problem.
Constructors
Below we show a constructor that can be used to allocate a LinesearchProblem. Note however that in practice one should call linesearch_problem and not use the constructor directly.
f(x) = x^2 - 1
g(x) = 2x
δx(x) = - g(x) / 2
x₀ = 3.
_f(α,_) = f(compute_new_iterate(x₀, α, δx(x₀)))
_d(α,_) = g(compute_new_iterate(x₀, α, δx(x₀)))
ls_obj = LinesearchProblem{typeof(x₀)}(_f, _d)
# output
LinesearchProblem{Float64, typeof(_f), typeof(_d)}(_f, _d)SimpleSolvers.LinesearchStatus — Type
LinesearchStatus{T}The step length returned by a line search together with the diagnostics needed to tell progress from stagnation. Obtained from solve_with_status; compare this to NonlinearSolverStatus, which plays the same role for the outer iteration.
The step length alone cannot express the difference: a tiny $\alpha$ may be the correct answer, or it may be all that is left after the merit turned out to be irreducible. See LinesearchOutcome.
Keys
α: the returned step length (the valuesolvereturns),outcome::LinesearchOutcome,trials: the number of trial steps $\alpha > 0$ at which the method actually evaluated the problem in its own iteration — not thelinesearch_max_iterationsbudget. That is the merit for every method exceptBisection, which drives on the derivative it bisects. Evaluations spent inside a bracketing helper (bracket_minimum,triple_point_finder) are not included, so for the bracketing searches this is a lower bound on the total cost; forBacktrackingandStrongWolfeit is exact, and every merit evaluation is either the $\alpha = 0$ anchor or a counted trial,φ₀,d₀: the merit and its derivative at the anchor $\alpha = 0$,φ: the merit at the returned step,τ: the round-off resolution of the merit (seearmijo_tolerance), against which every method decides whether the decrease it achieved was genuine,αmin: the smallest step length that could still be decided by the merit rather than by rounding (seebacktracking_αmin). This is a shrinking-ladder quantity and is thereforezero— meaning "not applicable" — for the minimising searches (Bisection,Quadratic,BierlaireQuadratic) and forStrongWolfe, which bracket rather than shrink.
SimpleSolvers.LinesearchStatus — Method
LinesearchStatus(α, outcome=LINESEARCH_UNKNOWN)Construct a LinesearchStatus that carries only the step length and the LinesearchOutcome; the remaining diagnostics are filled with NaN/zero. Used by the generic fallback of solve_with_status for methods that do not report them.
SimpleSolvers.Newton — Type
Newton(refactorize=1)The Newton (and quasi-Newton) nonlinear solver method.
Constructors
Newton()
# output
Newton(1)QuasiNewton()
# output
Newton(5)The refactorize parameter determines how often the Jacobian is re-evaluated and refactored (see factorize!). The default refactorize = 1 refactorizes on every step (a plain Newton method), whereas refactorize > 1 reuses the factorization in between, giving a quasi-Newton method (conveniently constructed via QuasiNewton).
SimpleSolvers.NewtonSolver — Type
NewtonSolverA const derived from NonlinearSolver as NewtonSolver{T} = NonlinearSolver{T,Newton}.
Constructors
The NewtonSolver can be called with a NonlinearProblem or with a Callable.
See NewtonSolver(::AbstractVector{T}, ::Callable, ::AbstractVector{T}) where {T}.
F(y, x, params) = y .= sin.(x) ^ 2
x = ones(5)
y = zeros(5)
ns = NewtonSolver(x, F, y)
typeof(ns) <: NewtonSolver
# output
trueKeywords
linear_solver_method: the method used to build the linear solver (seeLinearSolver) that computes the direction of the solver step (seesolver_step!),DF!: an in-place function computing the Jacobian,linesearch::Linesearchjacobian::Jacobianrefactorize::Int: determines after how many steps the Jacobian is re-evaluated and refactored (seefactorize!).refactorize > 1gives a quasi-Newton method (seeQuasiNewton),options_kwargs: seeOptions
SimpleSolvers.NewtonSolver — Method
NewtonSolver(x, F, y)Keywords
linear_solver_methodDF!linesearchjacobianrefactorizeoptions_kwargs: seeOptions
SimpleSolvers.NoLinearProblem — Type
A dummy linear system used for the fixed point iterator (Picard).
SimpleSolvers.NonlinearProblem — Type
NonlinearProblemA NonlinearProblem describes $F(x) = y$, where we want to solve for $x$ and $F$ is in nonlinear in general (also compare this to LinearProblem).
Keys
FJ::Union{Callable, Missing}: accessed by callingjacobian.
Constructors
We show an example for one particular constructor:
F(y, x, params) = y .= sin.(x) .^ 2
NonlinearProblem(F, zeros(3))
# output
NonlinearProblem{typeof(F), Missing}(F, missing)SimpleSolvers.NonlinearSolver — Type
NonlinearSolverA struct that comprises Newton solvers (see Newton), the Picard solver (also known as fixed-point iteration; see Picard) and the Dogleg solver (see DogLeg).
The associated solvers are consts derived from NonlinearSolver. See NewtonSolver, PicardSolver and DogLegSolver. In practice we usually call those associated constructors directly rather than creating a NonlinearSolver instance manually.
Keys
nonlinearproblem::NonlinearProblem: the system that has to be solved. This can be accessed by callingnonlinearproblem,linearproblem::LinearProblem,jacobian::Jacobian: the Jacobian is used to compute the direction in the solver step (seesolver_step!). This can be accessed by callingjacobian,linearsolver::LinearSolver: the linear solver is used to compute the direction of the solver step (seesolver_step!). This can be accessed by callinglinearsolver,linesearch::Linesearchmethod::NonlinearSolverMethod: the solver method (e.g.Newton),cache::NonlinearSolverCacheconfig::Options
SimpleSolvers.NonlinearSolverCache — Type
NonlinearSolverCache <: AbstractNonlinearSolverCacheDerived from AbstractNonlinearSolverCache. Used in NonlinearSolver.
Keys
x: the next iterate (or guess thereof),Δx: search direction. This is updated when callingsolver_step!via theLinearSolverstored in theNewtonSolver,rhs: the right-hand-side (this can be accessed by callingrhs),y: the problem evaluated atx,j::AbstractMatrix: the Jacobian evaluated atx. Note that this is not of typeJacobian!
The line search reads the current search direction Δx from this cache but writes its trial iterate, residual and Jacobian into its own private buffers (see linesearch_problem); it does not overwrite x, y or j.
SimpleSolvers.NonlinearSolverMethod — Type
NonlinearSolverMethod <: SolverMethodA supertype collecting all nonlinear solver methods, i.e. Newton, Picard and DogLeg.
Compare this with LinesearchMethod: both are subtypes of SolverMethod, but a LinesearchMethod describes a one-dimensional line search (used inside a solver step) whereas a NonlinearSolverMethod describes the outer nonlinear iteration itself.
SimpleSolvers.NonlinearSolverState — Type
NonlinearSolverState <: AbstractSolverStateThe NonlinearSolverState to be used together with a NonlinearSolver.
Note the difference to the NonlinearSolverCache and the NonlinearSolverStatus.
Examples
julia> state = NonlinearSolverState(zeros(3))
NonlinearSolverState{Float64, Vector{Float64}, Vector{Float64}}(0, [NaN, NaN, NaN], [NaN, NaN, NaN], [NaN, NaN, NaN], [NaN, NaN, NaN], NaN, 0, false)SimpleSolvers.NonlinearSolverStatus — Type
NonlinearSolverStatusStores absolute and successive residuals for x and f. It is used as a diagnostic tool in NewtonSolver.
Compare this to the NonlinearSolverState and the NonlinearSolverCache.
Keys
iterations: number of iterationsstalls: number of consecutive stalled steps, seestalled_stepandisstalled,rxₛ: successive residual inx,rfₐ: absolute residual inf,rfₛ: successive residual inf,x_converged::Boolf_converged::Boolf_increased::Boolstalled::Bool: the last step stalled, seestalled_step
Examples
x = [1., 2., 3., 4.]
state = NonlinearSolverState(x)
cache = NonlinearSolverCache(x, x)
config = Options()
NonlinearSolverStatus(state, config)
# output
i= 0,
rxₛ= NaN,
rfₐ= NaN,
rfₛ= NaNSimpleSolvers.Options — Type
OptionsExamples
Options()
# output
x_abstol = 4.440892098500626e-16
x_reltol = 4.440892098500626e-16
x_suctol = 4.440892098500626e-16
f_abstol = 0.0
f_reltol = 1.4901161193847656e-8
f_suctol = 4.440892098500626e-16
f_mindec = 0.0001
f_abstol_break = Inf
allow_f_increases = true
min_iterations = 0
max_iterations = 1000
warn_iterations = 1000
linesearch_max_iterations = 60
max_stalls = 2
show_trace = false
store_trace = false
extended_trace = false
show_every = 1
verbosity = 1
nan_max_iterations = 10
nan_factor = 0.5
regularization_factor = 0.0
dogleg_radius_initial = 1.0
dogleg_radius_shrink = 0.25
dogleg_radius_expand = 2.0
dogleg_radius_max = 100.0
The tolerance constants (x_abstol through f_suctol) default to values derived from default_tolerance and absolute_tolerance, except f_reltol, which defaults to √eps(T): it is the relative residual tolerance used by assess_convergence — the residual is small when rfₐ ≤ f_abstol + f_reltol·‖F(x₀)‖, i.e. the absolute tolerance is f_abstol and the relative tolerance is f_reltol.
dogleg_radius_initial, dogleg_radius_shrink, dogleg_radius_expand and dogleg_radius_max are the trust-region parameters for the DogLegSolver: the initial and maximum radius ($\Delta_0$ and $\hat\Delta$ in [1, Alg. 4.1]) and the factors by which the radius is shrunk on a poor step / expanded on a very good boundary step. They default to DOGLEG_Δ_INITIAL, DOGLEG_Δ_SHRINK, DOGLEG_Δ_EXPAND and DOGLEG_Δ_MAX, and are ignored by the other solvers.
max_iterations bounds the outer nonlinear iteration (see meets_stopping_criteria); linesearch_max_iterations bounds the inner, one-dimensional line search taken within a single solver step — the Backtracking ladder, the StrongWolfe bracketing and zoom phases, bisection, and the Quadratic/BierlaireQuadratic fits. These used to be the same field, which meant that capping the solver at max_iterations = 50 silently also capped the ladder, and that the default of 1000 was applied to a ladder which can never need more than $\lceil-\log_2\varepsilon\rceil$ trials. See linesearch_iterations.
f_abstol is an absolute target for $\|F(x)\|$, and the default 0 (see absolute_tolerance) is never met by a nonzero residual: the absolute branch of assess_convergence is switched off by default and convergence is decided entirely by the relative (f_reltol) and successive (x_suctol, f_suctol) branches.
Conversely, an f_abstol below the round-off floor of your own residual — the cancellation level of the terms F sums internally, which the solver cannot see — is unsatisfiable. The iteration then reaches that floor, stops making progress, and is reported as stagnated (see max_stalls, stalled_step and nonlinear_solver_warnings) rather than converged.
Note that f_reltol does not rescue this case: the relative gate is anchored at the initial residual $\|F(x_0)\|$, so an excellent initial guess makes it tighter, not looser. If the stagnation warning reports an achieved rfₐ near your f_abstol, raise f_abstol above it — an order of magnitude of headroom is usual.
Also see meets_stopping_criteria.
SimpleSolvers.Picard — Type
Picard <: NonlinearSolverMethodSee PicardSolver.
SimpleSolvers.PicardSolver — Method
PicardSolver(x, F)Arguments
x: the initial guess for the solution.F: the nonlinear function to solve.y
Keywords
DF!: the Jacobian ofF,jacobian: the Jacobian ofF, defaults toJacobianAutodiff,options_kwargs: seeOptions.
Note that the Picard solver_step! is a residual-safeguarded fixed-point iteration and uses no line search, so — unlike the other solvers — no linesearch keyword is accepted (passing one is an error rather than being silently ignored).
Examples
F(y, x, params) = y .= sin.(x) .^ 2
x = zeros(2)
y = similar(x)
s = PicardSolver(x, F, y)
state = SolverState(s)
solve!(x, s, state)
# output
2-element Vector{Float64}:
0.0
0.0SimpleSolvers.Quadratic — Type
Quadratic <: LinesearchMethodQuadratic Polynomial line search based on the polynomial
\[p(α) = p_0 + p_1(\alpha - \alpha_0) + p_2(\alpha - \alpha_0)^2.\]
Performs multiple iterations in which all parameters $p_0$, $p_1$ and $p_2$ are adapted. We do not check the SufficientDecreaseCondition here. We instead repeatedly build new quadratic polynomials until a minimum is found (to sufficient accuracy). The iteration may also stop after it reaches the maximum number of iterations, the linesearch_max_iterations field of Options (see linesearch_iterations).
Keywords
ε: A constant that checks the precision/tolerance.s: A constant that determines the initial interval for bracketing. By default this isDEFAULT_BRACKETING_s.s_reduction:A constant that determines the factor by whichsis decreased in each new bracketing iteration.
Extended help
The quadratic method. Compare this to BierlaireQuadratic. The algorithm is adjusted from [6].
SimpleSolvers.Static — Type
Static <: LinesearchMethodThe static method.
Keys
Keys include:
α: equivalent to a step size. The default is1.
Examples
Static()
# output
Static with α = 1.0.SimpleSolvers.StrongWolfe — Type
StrongWolfe{T} <: LinesearchMethodA line search that finds a step $\alpha$ satisfying the strong Wolfe conditions
\[\begin{aligned} f(\alpha) &\leq f(0) + c_1\,\alpha\,f'(0), &\text{(sufficient decrease / Armijo)}\\ |f'(\alpha)| &\leq c_2\,|f'(0)|, &\text{(strong curvature)} \end{aligned}\]
with $0 < c_1 < c_2 < 1$. It implements the bracketing line search of [1, Alg. 3.5 and 3.6 (zoom)]: a bracketing phase grows the step until an interval containing an acceptable point is found, then a zoom phase shrinks that interval (by bisection) until the strong Wolfe conditions hold.
Unlike Backtracking — which enforces only sufficient decrease, since the curvature condition cannot be honoured by shrinking alone — StrongWolfe actually enforces the curvature condition, at the cost of evaluating the derivative at each trial step. Use it when curvature control is genuinely required; Backtracking is cheaper otherwise.
Keys
c₁(defaultDEFAULT_WOLFE_c₁): the Armijo constant $c_1$.c₂(defaultDEFAULT_WOLFE_c₂): the curvature constant $c_2$. We require $c_1 < c_2 < 1$.αmax(defaultDEFAULT_WOLFE_αmax): the largest step the bracketing phase will try.
SimpleSolvers.SufficientDecreaseCondition — Type
SufficientDecreaseCondition <: BacktrackingConditionThe condition that determines if the change induced by $\alpha_k$ is big enough. This is used in Backtracking.
Example
c = SimpleSolvers.DEFAULT_WOLFE_c₁
f(x) = (x - 1.) ^ 2
xₖ = 0.
fₖ = f(xₖ)
dₖ = 2xₖ - 2.
sdc = SufficientDecreaseCondition(c, fₖ, dₖ, f)
sdc(1.9), sdc(2.)
# output
(true, false)Extended help
We call the constant that pertains to the sufficient decrease condition $c$. This is typically called $c_1$ in the literature [1]. See DEFAULT_WOLFE_c₁ for the relevant constant
The optional keyword τ slackens the condition by an absolute amount:
\[f(\alpha) \leq \min\{f_0,\ f_0 + c\alpha{}d_0 + \tau\}.\]
It defaults to zero, i.e. the exact condition. Without it the accept/reject decision is taken by rounding alone as soon as $c\alpha|d_0|$ drops below one unit in the last place of $f_0$: the right-hand side then rounds back up to $f_0$ and the test degenerates to $f(\alpha) \leq f_0$, which a merit that has reached its round-off floor passes or fails at random. See armijo_tolerance and backtracking_αmin for how Backtracking chooses $\tau$ and derives a meaningful smallest step from it.
The $\min$ bounds the slackening: $\tau$ may reduce the decrease that is demanded, but it never accepts a step whose merit exceeds $f_0$. For $d_0 < 0$ — which both callers guarantee via check_anchor — the $\min$ is inactive wherever $f_0 + c\alpha{}d_0$ is representably below $f_0$, so it changes nothing in double precision; it matters at low precision, where $\tau$ can exceed the demanded $c\alpha|d_0|$ outright.
GeometricBase.update! — Method
update!(ls::LinearProblem, A, b)Set the rhs vector to b and the matrix stored in the LinearProblem ls to A.
Calling update! doesn't solve the LinearProblem, you still have to call solve! in combination with a LinearSolver.
GeometricBase.update! — Method
update!(state, x, y)Update x̄, ȳ, x and y.
Examples
julia> f(y, x, params) = y .= sin.(x .- .5) .^ 2
f (generic function with 1 method)
julia> x = ones(1) / 4
1-element Vector{Float64}:
0.25
julia> y = zero(x); f(y, x, NullParameters())
1-element Vector{Float64}:
0.06120871905481365
julia> state = NonlinearSolverState(x)
NonlinearSolverState{Float64, Vector{Float64}, Vector{Float64}}(0, [NaN], [NaN], [NaN], [NaN], NaN, 0, false)
julia> update!(state, x, y)
NonlinearSolverState{Float64, Vector{Float64}, Vector{Float64}}(0, [0.25], [NaN], [0.06120871905481365], [NaN], NaN, 0, false)
julia> x = ones(1) / 2
1-element Vector{Float64}:
0.5
julia> f(y, x, NullParameters())
1-element Vector{Float64}:
0.0
julia> update!(state, x, y)
NonlinearSolverState{Float64, Vector{Float64}, Vector{Float64}}(0, [0.5], [0.25], [0.0], [0.06120871905481365], NaN, 0, false)The NonlinearSolverState stores the previous solution, the previous value, the current solution and the current value.
All of these are updated during one update! step (and initialized with NaNs).
LinearAlgebra.ldiv! — Method
ldiv!(x, lsolver, b)Compute inv(cache(lsolver).A) * b by utilizing the factorization of the lu solver (see LU and LinearSolver) and store the result in x.
Examples
julia> A = [1.; 0.; 0.;; 0.; 2.; 0.;; 0.; 0.; 4.]
3×3 Matrix{Float64}:
1.0 0.0 0.0
0.0 2.0 0.0
0.0 0.0 4.0
julia> b = [1., 1., 1.]
3-element Vector{Float64}:
1.0
1.0
1.0
julia> s = LinearSolver(LU(), A); factorize!(s); x = zeros(3)
3-element Vector{Float64}:
0.0
0.0
0.0
julia> ldiv!(x, s, b)
3-element Vector{Float64}:
1.0
0.5
0.25
Note that we need to call factorize! here after having allocated the LinearSolver.
SimpleSolvers.QuasiNewton — Function
QuasiNewton(refactorize=5)Convenience constructor for a Newton method whose Jacobian is only re-evaluated and refactored every refactorize iterations. Equivalent to Newton(refactorize) but with a quasi-Newton default (see DEFAULT_ITERATIONS_QUASI_NEWTON_SOLVER).
SimpleSolvers._static — Method
_static(A)Determine whether the LUSolverCache for a default LU should store A as a mutable static matrix (MMatrix) or as a plain Matrix. Every matrix whose leading dimension is smaller than or equal to N_STATIC_THRESHOLD is stored as an MMatrix.
This is only consulted for the default LU() (i.e. LU{Missing}); an explicit static=true/false keyword overrides it. See the examples in factorize!.
SimpleSolvers.absolute_tolerance — Method
absolute_tolerance(T)Determine the absolute tolerance for a specific data type. This is used in the constructor of Options.
In comparison to default_tolerance, this should return a very small number, close to zero (i.e. not just machine precision).
Examples
julia> absolute_tolerance(Float64)
0.0julia> absolute_tolerance(Float32)
0.0f0SimpleSolvers.alloc_g — Function
alloc_g(x)Allocate NaNs of the size of the gradient of f (with respect to x).
SimpleSolvers.alloc_h — Function
alloc_h(x)Allocate NaNs of the size of the Hessian of f (with respect to x).
SimpleSolvers.alloc_x — Function
alloc_x(x)Allocate NaNs of the size of x.
SimpleSolvers.armijo_tolerance — Method
armijo_tolerance(φ₀, n)The absolute round-off resolution $\tau = n\cdot\mathrm{ulp}(\varphi_0)$ of a merit function, where n is a number of units in the last place — armijo_ulps for the default. See DEFAULT_ARMIJO_τ_ULPS for what it is used for and backtracking_αmin for the step length derived from it.
This lives here rather than next to Backtracking because triple_point_finder needs it too, and bracketing/ is included before linesearch/.
SimpleSolvers.armijo_ulps — Method
armijo_ulps(T, c₁)
armijo_ulps(T)The number of ulps of $\varphi(0)$ to use as the round-off resolution $\tau$ for element type T: the nominal DEFAULT_ARMIJO_τ_ULPS, capped at what T can support. The one-argument form uses DEFAULT_WOLFE_c₁.
$\tau$ has to satisfy two requirements that pull in opposite directions. To recognise a merit sitting at its round-off floor it must be at least an ulp or so of $\varphi(0)$. To leave the SufficientDecreaseCondition meaningful it must be far below the decrease that condition demands, which for the canonical $\|F\|^2$ merit of a Newton step ($\varphi'(0) = -2\varphi(0)$) is $2c_1\varphi(0)$ at $\alpha = 1$. Hence the cap
\[n \leq \frac{\mathtt{ARMIJO\_τ\_DEMAND\_FRACTION}\cdot 2c_1}{\mathrm{eps}(T)} .\]
The two requirements are compatible only while $\mathrm{eps}(T) \ll 2c_1$. They are, by a wide margin, in double precision (the cap is $\sim10^{10}$) and comfortably in single ($\sim17$), so the nominal 4 stands in both. They are not in Float16, where $\mathrm{eps}(T) = 9.8 \cdot 10^{-4}$ already exceeds $2c_1 = 2\cdot10^{-4}$: no value of τ_ulps above zero can satisfy both, so the cap resolves the conflict in favour of a meaningful condition and drops $\tau$ to about $2\cdot10^{-3}$ ulps — in effect the exact condition.
Nothing is lost by that. The floor is still detected, by two mechanisms that do not depend on $\tau$: at a trial step small enough that $\mathrm{fl}(\varphi(0) + c_1\alpha\varphi'(0))$ rounds back to $\varphi(0)$ the condition is $\varphi(\alpha) \leq \varphi(0)$, and Backtracking additionally stops on two consecutive bit-identical merits. What is gained is that a genuine decrease of one or two ulps — the smallest a Float16 merit can express — is reported as LINESEARCH_DECREASED instead of as LINESEARCH_FLOOR, which the outer iteration would otherwise count towards max_stalls.
Examples
julia> armijo_ulps(Float64), armijo_ulps(Float32)
(4.0, 4.0f0)julia> armijo_ulps(Float16)
Float16(0.002075)SimpleSolvers.assess_convergence — Method
assess_convergence(rxₛ, rfₐ, rfₛ, config, state)Assess convergence for status::NonlinearSolverStatus and return the triple (x_converged, f_converged, f_increased).
The successive-change criteria (in x and f) alone are not sufficient to declare convergence: a stalled step (e.g. an artificially tiny line-search step) makes the successive residuals rxₛ and rfₛ vanish even when the absolute residual rfₐ is large. We therefore require the residual to be small — written residual_small below — in addition to the successive-change criterion before reporting convergence. The residual passes the standard atol + rtol·‖F₀‖ test:
residual_small ⟺ rfₐ ≤ config.f_abstol + config.f_reltol * initial_residual(state),
with the absolute tolerance atol = config.f_abstol (defaulting to 0) and the relative tolerance rtol = config.f_reltol (defaulting to √eps(T)) applied to the initial residual ‖F(x₀)‖. Concretely:
x_converged:rxₛ ≤ norm(solution(state)) * config.x_suctolandresidual_small,f_converged: (rfₛ ≤ norm(value(state)) * config.f_suctolandresidual_small) orrfₐ ≤ config.f_abstol,f_increased:norm(value(state)) > norm(previousvalue(state)).
This guards the successive-change criteria against stagnation: it is loose enough that a genuinely converged iterate satisfies it (the successive-change criteria still supply the tight, machine-precision accuracy) yet tight enough to reject a step that stalls near its initial residual (rfₐ ≈ ‖F(x₀)‖ ≫ f_reltol·‖F(x₀)‖). The relative term is what lets a well-scaled solve whose residual floors at a large absolute value (e.g. a large-magnitude or ill-conditioned F) still converge; it drops to zero (leaving the pure absolute f_abstol test) until the state has been initialized (initial_residual is NaN).
Also see meets_stopping_criteria.
SimpleSolvers.backtracking_interpolation — Method
backtracking_interpolation(φ₀, d₀, α, φα, αp, φp, p)The next trial step of the safeguarded polynomial backtracking used by Backtracking (see [1, §3.5], [2, Alg. A6.3.1]).
α/φα is the trial step that was just rejected and αp/φp the one rejected before it (αp is NaN on the first backtrack). The model interpolates $\varphi(0)$, $\varphi'(0)$ and the rejected value(s) — a quadratic on the first backtrack, a cubic afterwards — and its minimiser is clamped to $[$ BACKTRACKING_SHRINK_MIN $\cdot\alpha, p\alpha]$.
The clamp is what makes this safe: an unclamped interpolant can return $\alpha$ itself (no progress at all), collapse to numerically zero, or be meaningless because the merit values it is built from are rounding noise. Because the upper bound is $p$, the trial sequence is pointwise never longer than the plain $\alpha \gets p\alpha$ ladder.
SimpleSolvers.backtracking_αmin — Method
backtracking_αmin(c₁, d₀, τ)The smallest step length for which the SufficientDecreaseCondition can still be decided by the merit rather than by rounding:
\[\alpha_\mathrm{min} = \frac{\tau}{c_1|\varphi'(0)|} .\]
Below $\alpha_\mathrm{min}$ the demanded decrease $c_1\alpha|\varphi'(0)|$ is smaller than the round-off resolution $\tau$, so a trial step carries no information. Writing $\tau = n\cdot\mathrm{ulp}(\varphi(0))$ (see armijo_tolerance) gives
\[\alpha_\mathrm{min} = 2n\,\alpha^*, \qquad \alpha^* = \frac{\mathrm{ulp}(\varphi(0))}{2c_1|\varphi'(0)|},\]
where $\alpha^*$ is the step below which $\mathrm{fl}(\varphi(0) + c_1\alpha\varphi'(0))$ rounds back up to $\varphi(0)$ and the condition degenerates to $\varphi(\alpha) \leq \varphi(0)$.
The result is clamped to $[\mathrm{eps}(T), \sqrt{\mathrm{eps}(T)}]$: the lower bound is the historical negligible-step floor, and the upper bound makes sure that a nearly flat but genuine merit (very small $|\varphi'(0)|$) is still searched — an unclamped $\alpha_\mathrm{min}$ grows without bound as $|\varphi'(0)| \to 0$ and would stop the search before it began.
$\alpha_\mathrm{min} = 2n\,\alpha^*$ puts the search a factor $2n$ clear of the region where the condition is decided by rounding, but the $\sqrt{\mathrm{eps}(T)}$ clamp can pull it below $\alpha^*$: that happens for $|\varphi'(0)| < \mathrm{ulp}(\varphi(0)) / (2c_1\sqrt{\mathrm{eps}(T)})$, i.e. below $7\cdot10^{-5}$ for Float64 with $\varphi(0) = 1$, below $1.7$ for Float32, and essentially always for Float16. Trial steps below $\alpha^*$ are then taken, and that is safe rather than merely tolerated: the $\min$ in the SufficientDecreaseCondition reduces the test there to $\varphi(\alpha) \leq \varphi(0)$, so it can accept a non-increase but never an increase, and such an accept is classified LINESEARCH_FLOOR.
SimpleSolvers.bisection — Method
bisection(f, αmin, αmax, params, config)Perform bisection of f in the interval [αmin, αmax] with Options config.
The algorithm is repeated until a root is found (up to tolerance config.f_abstol which is determined by default_tolerance by default).
When calling bisection it first checks if $x_\mathrm{min} < x_\mathrm{max}$ and else flips the two entries.
You can also call bisection with only one x as input argument. It then uses bracket_minimum to find a suitable interval.
Extended help
The bisection algorithm divides an interval into equal halves until a root is found (up to a desired accuracy).
We first initialize:
\[\begin{aligned} \alpha_0 \gets & \alpha_\mathrm{min}, \\ \alpha_1 \gets & \alpha_\mathrm{max}, \end{aligned}\]
and then repeat:
\[\begin{aligned} & \alpha \gets \frac{\alpha_0 + \alpha_1}{2}, \\ & \text{if $f(\alpha_0)f(\alpha) > 0$} \\ & \qquad \alpha_0 \gets \alpha, \\ & \text{else} \\ & \qquad \alpha_1 \gets \alpha, \\ & \text{end} \end{aligned}\]
So the algorithm checks in each step where the sign change occurred and moves the $\alpha_0$ or $\alpha_1$ accordingly. The loop is terminated if config.linesearch_max_iterations is reached (by default 60 for Float64 in the Options struct, see linesearch_iterations); in that case a warning is emitted (at verbosity ≥ 1) and the best estimate found so far is returned.
The obvious danger with using bisections is that the supplied interval can have multiple roots (or no roots). One should be careful to avoid this when fixing the interval.
Bisection can only locate a root if the endpoints straddle a sign change. If the endpoints have the same sign there is no (odd-multiplicity) root in the interval; this arises benignly in the line search once the derivative has flattened at a minimum (both endpoint values ≈ 0 with the same sign). Rather than erroring, bisection then returns the endpoint closest to a root (smallest |f|) and warns only at high verbosity.
SimpleSolvers.bracket — Method
bracket(f, x, bc, s, k, nmax)Grow a bracket outward from x (in steps scaled by k, starting from s) until the BracketingCriterion bc is satisfied. Used by bracket_minimum and bracket_root.
Extended help
Before entering the main loop we check whether the criterion is already satisfied just to the left of a (at a - s). This early exit is only valid for the BracketRootCriterion, where it corresponds to a sign change in (a - s, b). For the BracketMinimumCriterion it would instead bracket a maximum rather than a minimum, so it is deliberately skipped.
Returns nothing when no bracket is found within nmax steps. A line search must be able to report an unbracketable merit rather than abort the enclosing solve, so this is a nothing rather than an error; see bracket_minimum.
SimpleSolvers.bracket_minimum — Method
bracket_minimum(f, x)Move a bracket successively in the search direction (starting at x) and increase its size until a local minimum of f is found.
This is used in bisections when only one x is given (and not an entire interval).
This bracketing algorithm is taken from [3]. Also compare it to bracket_minimum_with_fixed_point.
Arguments
f: the function to be bracketed,x: the starting point,s: by defaultDEFAULT_BRACKETING_s,k: by defaultDEFAULT_BRACKETING_k,nmax: by defaultDEFAULT_BRACKETING_nmax.
Extended help
For bracketing we need two constants $s$ and $k$ (see DEFAULT_BRACKETING_s and DEFAULT_BRACKETING_k).
Before we start the algorithm we initialize it, i.e. we check that we indeed have a descent direction:
\[\begin{aligned} & a \gets x, \\ & b \gets a + s, \\ & \mathrm{if} \quad f(b) > f(a)\\ & \qquad\text{Flip $a$ and $b$ and set $s\gets-s$.}\\ & \mathrm{end} \end{aligned}\]
The algorithm then successively computes:
\[c \gets b + s,\]
and then checks whether $f(c) \geq f(b)$ (also see BracketMinimumCriterion). If this is true it returns $(a, c)$ or $(c, a)$, depending on whether $a<c$ or $c<a$ respectively. If this is not satisfied $a,$ $b$ and $s$ are updated:
\[\begin{aligned} a \gets & b, \\ b \gets & c, \\ s \gets & sk, \end{aligned}\]
and the algorithm is continued. If we have not found a bracket after $n_\mathrm{max}$ iterations (see DEFAULT_BRACKETING_nmax) the algorithm terminates and returns nothing. The interval that is returned by bracket_minimum is then typically used as a starting point for bisection.
A line search must be able to report a merit it cannot bracket rather than abort the enclosing solve, so an unbracketable f yields nothing rather than an error. Callers must handle it — see solve_with_status and LinesearchOutcome.
The function bracket_root is equivalent to bracket_minimum with the only difference that the criterion we check for is:
\[f(c)f(b) < 0,\]
i.e. that a sign change in the function occurs. Also see BracketRootCriterion.
SimpleSolvers.bracket_minimum_with_fixed_point — Method
bracket_minimum_with_fixed_point(f, x, s, k, nmax)Find a bracket while keeping the left side (i.e. x) fixed.
The algorithm is similar to bracket_minimum (also based on DEFAULT_BRACKETING_s and DEFAULT_BRACKETING_k) with the difference that for the latter the left side is also moving.
The function bracket_minimum_with_fixed_point is used as a starting point for Quadratic (adapted from [6]), as the coefficient $p_2$ of the fitted polynomial is:
\[p_2 = \frac{f(b) - f(a) - f'(a)b}{b^2},\]
where $b = \mathtt{bracket\_minimum\_with\_fixed\_point}(a)$. The right end b is grown outward (with the left end a held fixed) until f stops decreasing, i.e. until the turning point f(b) ≥ f(b_\mathrm{prev}) is reached, so that a minimum is bracketed in (a, b). (The earlier variant compared against the fixed anchor f(a) instead, which failed to bracket a minimum whose right tail stays below f(a).) The Quadratic caller guards the fitted curvature (p_2 ≤ 0 falls back to a bisection step), so f(b) > f(a) is no longer required.
Returns the bracket together with the function values at its endpoints, (a, b, f(a), f(b)) with a < b. The values are already computed during bracketing, so the caller (the Quadratic line search) does not have to re-evaluate f at the endpoints.
Returns nothing if no bracket is found within nmax steps — a line search must be able to report an unbracketable merit rather than abort the enclosing solve.
SimpleSolvers.bracket_root — Method
bracket_root(f, x)Make a bracket for the function based on x (for root finding).
This is largely equivalent to bracket_minimum. See the end of that docstring for more information.
Here we use BracketRootCriterion instead of BracketMinimumCriterion.
SimpleSolvers.cache — Method
cache(ls)Return the cache of the LinearSolver.
Examples
For the default LU(), a small matrix (leading dimension ≤ N_STATIC_THRESHOLD) is stored as a mutable static matrix (MMatrix):
julia> ls = LinearSolver(LU(), [1.0 2.0; 3.0 4.0]);
julia> cache(ls)
SimpleSolvers.LUSolverCache{Float64, StaticArraysCore.MMatrix{2, 2, Float64, 4}}([1.0 2.0; 3.0 4.0], [0, 0], [0, 0], 0)Passing static=false forces a plain Matrix cache regardless of size:
julia> ls = LinearSolver(LU(; static=false), [1.0 2.0; 3.0 4.0]);
julia> cache(ls)
SimpleSolvers.LUSolverCache{Float64, Matrix{Float64}}([1.0 2.0; 3.0 4.0], [0, 0], [0, 0], 0)SimpleSolvers.change_precision — Method
change_precision(T, method::LinesearchMethod)Return a copy of the LinesearchMethod method with its numeric fields converted to the element type T.
This is an internal helper used when constructing a Linesearch: the method's precision is adapted to the working precision T. It replaces a former misuse of Base.convert (which was ambiguous with Base and violated the convert contract by returning a differently-typed object).
SimpleSolvers.check_anchor — Method
check_anchor(φ₀, d₀, α)Validate the $\alpha = 0$ anchor of a line search problem. Return a LinesearchStatus that the caller should return immediately, or nothing if the anchor is usable and the search may proceed.
This is the one definition of the anchor policy shared by every LinesearchMethod:
- $\varphi(0)$ or $\varphi'(0)$ not finite, or $\varphi'(0) > 0$, gives
LINESEARCH_NO_DESCENT: no $\alpha$ can decrease the merit along this direction, so shrinking or bracketing would only spend merit evaluations to discover that. The caller's trial stepαis handed back — never the $\alpha = 0$ anchor, which would freeze the outer iterate (x .+= 0 .* d). - $\varphi'(0) = 0$ gives
LINESEARCH_STATIONARY. For the $\|F\|^2$ merit of aNonlinearSolverthis is the exact root ($F = 0 \Rightarrow \varphi'(0) = 0$ and the direction vanishes), so it is benign and every $\alpha$ is equivalent.
An ascent anchor arises in practice when the direction did not come from an exact, freshly factorized Newton solve — a stale Jacobian under refactorize > 1, a nonzero regularization_factor, or an inexact linear solve. The correct response is to refresh the Jacobian, which is why the line search reports the situation instead of trying to salvage a step from it, and solver_step! acts on the report: on LINESEARCH_NO_DESCENT it leaves the iterate where it is (moving along a direction that cannot decrease the merit would only make the retry start from a worse point) and records a stall, which forces a fresh Jacobian on the next step (see needs_refresh and maybe_refactorize!) and gives up after max_stalls if that does not help.
The step handed back is therefore still positive, as the contract requires — whether to use it is the caller's decision, not the line search's.
SimpleSolvers.check_gradient — Method
check_gradient([io], g)Check norm, maximum value and minimum value of a vector.
Output is written to io (defaulting to stdout).
Examples
julia> g = [1., 1., 1., 2., 0.9, 3.];
julia> SimpleSolvers.check_gradient(g; digits=3)
norm(Gradient): 4.1
minimum(|Gradient|): 0.9
maximum(|Gradient|): 3.0SimpleSolvers.check_hessian — Method
check_hessian([io], H)Check the condition number, determinant, max and min value of the Hessian H.
Output is written to io (defaulting to stdout).
Here the Hessian H is a matrix and not of type Hessian.
julia> H = [1. √2.; √2. 3.];
julia> SimpleSolvers.check_hessian(H)
Condition Number of Hessian: 13.9282
Determinant of Hessian: 1.0
minimum(|Hessian|): 1.0
maximum(|Hessian|): 3.0SimpleSolvers.check_jacobian — Method
check_jacobian([io], J)Check the condition number, determinant, max and min value of the Jacobian J.
Output is written to io (defaulting to stdout).
Here the Jacobian J is a matrix. It is not a Jacobian object.
julia> J = [1. √2.; √2. 3.];
julia> SimpleSolvers.check_jacobian(J)
Condition Number of Jacobian: 13.9282
Determinant of Jacobian: 1.0
minimum(|Jacobian|): 1.0
maximum(|Jacobian|): 3.0SimpleSolvers.clear! — Method
SimpleSolvers.compute_new_iterate! — Method
compute_new_iterate!(xₖ₊₁, xₖ, αₖ, pₖ)Compute xₖ₊₁ based on a direction pₖ and a step length αₖ.
Extended help
In the case of vector spaces this function simply does:
xₖ = xₖ + αₖ * pₖFor manifolds we instead perform a retraction [7].
SimpleSolvers.curvature_diagnostic — Method
curvature_diagnostic(status, ls, params)Method-specific extra diagnostic emitted by linesearch_warnings at verbosity ≥ 2. The fallback does nothing; Backtracking checks the CurvatureCondition, which costs a derivative evaluation — a full Jacobian for the line search problem of a NonlinearSolver, hence the verbosity gate.
SimpleSolvers.default_precision — Method
default_precision(T)Compute the default precision used for e.g. BierlaireQuadratic.
Compare this to the default_tolerance used in Options.
Examples
julia> default_precision(Float64)
1.7763568394002505e-15julia> default_precision(Float32)
9.536743f-7julia> default_precision(Float16)
Float16(0.007812)SimpleSolvers.default_tolerance — Method
default_tolerance(T)Determine the default tolerance for a specific data type. This is used in the constructor of Options.
Compare this to default_precision.
Examples
julia> default_tolerance(Float64)
4.440892098500626e-16julia> default_tolerance(Float32)
2.3841858f-7julia> default_tolerance(Float16)
Float16(0.001953)SimpleSolvers.default_ϵ — Method
default_ϵ(::Type{T})The default step size on whose basis finite differences are computed, for the working precision T. Used by GradientFiniteDifferences and JacobianFiniteDifferences.
Its value is $8\sqrt{\varepsilon_T}$, where $\varepsilon_T$ is the machine epsilon of T. Being precision-aware (eps(T), not a baked-in Float64 epsilon) is essential for Float32 finite differences to be accurate.
Examples
julia> default_ϵ(Float64)
1.1920928955078125e-7julia> default_ϵ(Float32)
0.0027621358f0SimpleSolvers.direction! — Method
direction!(d, x, s, params, iteration; stalled=false)Compute the Newton direction (for the NewtonSolver). stalled is forwarded to maybe_refactorize!; see needs_refresh.
SimpleSolvers.direction — Method
direction(cache)Return the direction (i.e. the step vector $\Delta{}x$) stored in a solver cache such as NonlinearSolverCache or DogLegCache.
SimpleSolvers.directions! — Method
directions!(s, x, params, iteration=1; force_refactorize=false)Compute direction₁ and direction₂ for the DogLegSolver.
This is equivalent to direction! for the NewtonSolver.
Examples
julia> J = [0 1; -1 0];
julia> f(y, x, params) = y .= cos.(J * x .- 2.) .^ 2 / l2norm(sin.(x) .- 1.);
julia> x = zeros(2); y = similar(x); s = DogLegSolver(x, y; F = f);
julia> directions!(s, x, NullParameters());
julia> direction₁(cache(s))
2-element Vector{Float64}:
-0.25513686072399455
0.1601152321012896
julia> direction₂(cache(s))
2-element Vector{Float64}:
-0.22882877718014286
0.22882877718014288Extended help
The Gauss-Newton direction (i.e. direction₂) is computed the usual way:
\[\mathbf{d}_2 = -\mathbf{J}^{-1} \mathbf{r}\]
where $\mathbf{J}$ is the Jacobian matrix and $\mathbf{r}$ is the residual vector. The steepest descent direction (taken from [1, Equation (11.46)]) is different:
\[\mathbf{d}_1 = -\frac{||\mathbf{J}^T\mathbf{r}||^2}{\mathbf{r}^T(\mathbf{J}\mathbf{J}^T)(\mathbf{J}\mathbf{J}^T)\mathbf{r}}\mathbf{J}^T\mathbf{r}.\]
The DogLegSolver then interpolates between these two directions (this interpolation is piecewise linear).
As for the (quasi-)NewtonSolver, the Jacobian is only re-evaluated and refactored every refactorize iterations (see DogLeg), and always on a fresh state or the first step (iteration ≤ 1), or when force_refactorize = true (used by solver_step! to recover from a collapsed trust-region radius, and after any step that did not move the iterate — see needs_refresh). In between, the stale Jacobian and its factorization are reused for both directions. The default refactorize = 1 refactorizes on every step.
SimpleSolvers.direction₁ — Method
SimpleSolvers.direction₂ — Method
SimpleSolvers.dogleg_direction! — Method
dogleg_direction!(cache, Δ)Compute the (piecewise-linear) dogleg step for trust-region radius Δ from the steepest-descent direction direction₁ and the Newton direction direction₂ (both already stored in cache), writing the result into direction(cache).
direction₁ and direction₂ do not depend on Δ, so this may be called repeatedly while shrinking Δ without recomputing (and refactorizing) the Jacobian.
SimpleSolvers.factorize! — Method
factorize!(lsolver::LinearSolver, A)Factorize the matrix A and store the result in cache(lsolver).A.
Note that calling cache on lsolver returns the instance of LUSolverCache stored in lsolver.
Examples
julia> A = [1. 2. 3.; 5. 7. 11.; 13. 17. 19.]
3×3 Matrix{Float64}:
1.0 2.0 3.0
5.0 7.0 11.0
13.0 17.0 19.0
julia> x = zeros(3);
julia> lsolver = LinearSolver(LU(; static=false), x);
julia> factorize!(lsolver, A).cache.A
3×3 Matrix{Float64}:
13.0 17.0 19.0
0.0769231 0.692308 1.53846
0.384615 0.666667 2.66667
julia> y = A * ldiv!(x, lsolver, ones(3));
julia> round.(y; digits = 10)
3-element Vector{Float64}:
1.0
1.0
1.0Here cache(lsolver).A stores the factorized matrix. If we call factorize! with two input arguments as above, the method first copies the matrix A into the LUSolverCache. We can equivalently also do:
julia> lsolver = LinearSolver(LU(), A);
julia> factorize!(lsolver).cache.A
3×3 StaticArraysCore.MMatrix{3, 3, Float64, 9} with indices SOneTo(3)×SOneTo(3):
13.0 17.0 19.0
0.0769231 0.692308 1.53846
0.384615 0.666667 2.66667Note the difference between the output types of the two refactorized matrices: the default LU() chose a mutable static (MMatrix) cache because the matrix is small (see _static and N_STATIC_THRESHOLD), whereas LU(; static=false) forced a plain Matrix.
Also see ldiv! for how the refactorized matrix is used.
SimpleSolvers.flag_stall! — Method
flag_stall!(state)Record that the line search of the current step reported that it cannot make progress along the current direction — either the merit is at its round-off floor (isfloor) or the anchor is not a descent direction at all (LINESEARCH_NO_DESCENT, see LinesearchOutcome). The flag is OR-ed into the verdict of the next record_stall!, which clears it again, and it makes needs_refresh true for the next step.
This is how a line search that knows it cannot help reports one iteration earlier than the step-based diagnosis of stalled_step — which remains the primary mechanism, since it is the only one that also covers a Static step along an underflowed direction, a collapsed DogLegSolver trust-region radius, and a locally expanding PicardSolver map.
SimpleSolvers.increase_iteration_number! — Method
increase_iteration_number!(state)To be used together with NonlinearSolverState.
SimpleSolvers.initial_residual — Method
initial_residual(state)Return the initial residual ‖F(x₀)‖ recorded by initialize! (NaN if the state has not been initialized). This is the reference scale for the relative-residual convergence test in assess_convergence.
SimpleSolvers.initialize! — Method
SimpleSolvers.initialize! — Method
initialize!(cache, x)Initialize the NonlinearSolverCache with NaNs.
SimpleSolvers.isconverged — Method
SimpleSolvers.isfloor — Method
isfloor(status)true if the line search could not find any step that changes the merit by more than the round-off allowance τ, i.e. the merit has reached its round-off floor. The outer iteration cannot make progress in this state no matter how the step is chosen — which is why a NonlinearSolver counts it as a stalled step (see record_stall!).
SimpleSolvers.isstalled — Method
isstalled(status, config)Check whether the iteration has stagnated: config.max_stalls consecutive steps stalled_step.
Mutually exclusive with isconverged — a stalled step is by definition one whose residual is not small, whereas both convergence branches require that it is.
A stagnated solve has reached the numerical floor of its residual and cannot improve it. Whether that counts as success is the caller's decision, which is why the status is queryable (see status): if status.rfₐ is acceptable to you, treat isstalled as success — and consider raising f_abstol above it, since the tolerance you asked for is not attainable.
SimpleSolvers.issufficient — Method
issufficient(status)true if the line search found a step with a genuine sufficient decrease, i.e. one that decreased the merit by more than the round-off allowance τ of status. Compare isfloor.
SimpleSolvers.iterate_settled — Method
iterate_settled(rxₛ, config, state)Return true when the last step did not move the iterate, rxₛ ≤ ‖x‖·x_suctol. Used by assess_convergence and stalled_step.
SimpleSolvers.iteration_number — Method
iteration_number(state)Return the number of iterations taken so far, as counted by increase_iteration_number! on state::NonlinearSolverState. Compare this to stall_number.
SimpleSolvers.jacobian — Method
jacobian(nlp)Return the Jacobian function stored in the NonlinearProblem nlp.
SimpleSolvers.jacobianmatrix — Method
jacobianmatrix(solver::NonlinearSolver)Return the evaluated Jacobian (a matrix) stored in the cache (see NonlinearSolverCache) of solver.
Also see jacobian(::NonlinearProblem).
SimpleSolvers.linearsolver — Method
linearsolver(solver)Return the linear part (i.e. a LinearSolver) of an NewtonSolver.
Examples
x = rand(3)
y = rand(3)
F(x) = tanh.(x)
F!(y, x, params) = y .= F(x)
s = NewtonSolver(x, y; F = F!)
linearsolver(s)
# output
LinearSolver{Float64, LU{Missing}, SimpleSolvers.LUSolverCache{Float64, StaticArraysCore.MMatrix{3, 3, Float64, 9}}}(LU{Missing}(missing, true), SimpleSolvers.LUSolverCache{Float64, StaticArraysCore.MMatrix{3, 3, Float64, 9}}([0.0 0.0 0.0; 0.0 0.0 0.0; 0.0 0.0 0.0], [0, 0, 0], [0, 0, 0], 0))SimpleSolvers.linesearch_iterations — Method
linesearch_iterations(T)Determine the default number of trial steps a line search may take, i.e. the default of the linesearch_max_iterations field of Options.
This is deliberately not the same quantity as max_iterations, which bounds the outer nonlinear iteration (see meets_stopping_criteria). A one-dimensional search inside a single solver step needs a budget on the order of the mantissa width, not thousands of trials: a Backtracking ladder $\alpha \gets p\alpha$ starting at $\alpha_0 = 1$ reaches the negligible-step floor after $\lceil-\log_2\varepsilon\rceil$ halvings (52 in double precision, 24 in single), and a bisection needs the same count to exhaust the mantissa. We take that count plus a small margin; everything beyond it can only produce denormals.
The count is derived for the default shrink factor $p = 0.5$. A Backtracking built with a p close to 1 needs more trials in principle, though in the case that matters — a merit frozen at its round-off floor — the safeguarded interpolation shrinks by about a half per trial regardless of p, because the quadratic model through a frozen value has its minimiser at $\alpha/2$.
Quadratic and BierlaireQuadratic are bounded by the same field even though they fit a quadratic rather than shrink a step: they converge on their own ε tolerance long before the budget, which serves only as a backstop, so there is no reason for them to carry a separate knob.
Compare this to default_tolerance and absolute_tolerance.
Examples
julia> linesearch_iterations(Float64)
60julia> linesearch_iterations(Float32)
31SimpleSolvers.linesearch_max_iterations — Method
linesearch_max_iterations(o::Options)The maximum number of trial steps a line search may take within a single solver step. See linesearch_iterations; not to be confused with max_iterations, which bounds the outer nonlinear iteration.
SimpleSolvers.linesearch_problem — Method
linesearch_problem(nl::NonlinearSolver)Build a line search problem based on a NonlinearSolver.
We apply L2norm to the output of value! (the evaluation of the nonlinear problem). This is because the solver operates on a function with array-valued outputs from which we have to find roots (in contrast to an optimizer which operates on a function with a scalar output of which we should find a minimum).
Examples
We show how to set up the LinesearchProblem for a simple example and compute $f^\mathrm{ls}(\alpha_0)$ and $\partial{}f^\mathrm{ls}/\partial\alpha(\alpha_0)$.
julia> F(y, x, params) = y .= (x .- 1.).^2;
julia> x = ones(3)/2; y = similar(x); nl = NewtonSolver(x, y; F = F);
julia> _params = NullParameters();
julia> direction!(nl, x, _params, 1)
3-element Vector{Float64}:
0.25
0.25
0.25
julia> ls_prob = linesearch_problem(nl);
julia> state = NonlinearSolverState(x); update!(state, x, F(y, x, _params));
julia> params = (parameters = _params, x = state.x)
(parameters = NullParameters(), x = [0.5, 0.5, 0.5])
julia> ls_prob.F(0., params)
0.1875
julia> ls_prob.D(0., params)
-0.375SimpleSolvers.linesearch_problem — Method
linesearch_problem(nlp, jacobian, cache)Make a line search problem for a Newton solver (the cache here is an instance of NonlinearSolverCache).
Extended help
The line search closures evaluate the merit at trial steps α using private scratch buffers rather than the solver's shared cache. The shared buffers (solution/value/jacobianmatrix) are read by the solver after the line search returns (e.g. the next direction! step and the convergence check), so writing trial iterates into them would be an aliasing hazard. The line search therefore only reads the current direction from the shared cache and the current iterate from params.x; every write goes to a closure-owned buffer.
params may carry an optional φ₀ field holding the merit at the $\alpha = 0$ anchor. Every line search evaluates that anchor first, and it is exactly the residual the solver has already computed at the current iterate, so solver_step! passes it along and saves one F evaluation per solver step — the most expensive single operation for a large residual. A caller who drives solver_step! by hand from a state whose value is stale must not supply it.
SimpleSolvers.linesearch_warnings — Function
linesearch_warnings(status, ls, params=NullParameters())Report a LinesearchStatus obtained from solve_with_status. Compare this to nonlinear_solver_warnings. This is the only place where a line search emits log messages, so solve and solver_step! report identically.
Two things keep this quiet in normal use. LINESEARCH_FLOOR and LINESEARCH_STATIONARY are reported only at verbosity ≥ 2, because both are the expected final state of a converged solve — a residual that cannot be improved because it is already as small as the arithmetic allows. And the remaining outcomes are rate limited with maxlog, because a solve that cannot make progress asks the line search for an impossible decrease at every one of its iterations, which an unconditional warning turns into thousands of identical messages.
Julia keys maxlog on the source location of the @warn, so the caps in report_linesearch_status are process-global and are not reset between solve! calls — one source location for every solver in the session. Once a message has appeared its quota is spent for the lifetime of the session, including for later solves of entirely different problems. That is deliberate — a time-stepping loop calling solve! once per step is precisely the case these caps exist for — but it does mean a genuinely new line-search failure late in a long run can go unreported. Raise verbosity to 2 and re-run when diagnosing one.
Whether an irreducible merit actually matters is the outer iteration's call, and nonlinear_solver_warnings makes it: it reports stagnation once, naming the residual that was achieved and the tolerance that was requested.
The messages themselves live in report_linesearch_status rather than here, which is a compile-time rather than a stylistic decision — see its docstring before merging them back.
SimpleSolvers.lucache_eltype — Method
lucache_eltype(T)The element type used by the LUSolverCache for an input matrix of element type T. Linear solves are only supported for floating-point problems — real (AbstractFloat) or complex (Complex{<:AbstractFloat}) — so any other element type (e.g. an integer or rational matrix) is rejected here with a clear error rather than silently promoted. For a supported type the cache uses T unchanged.
SimpleSolvers.max_stalls — Method
max_stalls(o::Options)The number of consecutive stalled steps after which a NonlinearSolver gives up. See MAX_STALLS and stalled_step.
SimpleSolvers.maybe_refactorize! — Method
maybe_refactorize!(s, x, params, iteration; force=false, stalled=false)Re-evaluate the Jacobian at x, copy it into the LinearProblem (adding the diagonal regularization_factor), and refactorize the LinearSolver — but only on a refactorization step: a fresh state or the first step (iteration ≤ 1), every refactorize iterations (see Newton), when the previous step made no progress (stalled, see needs_refresh), or when forced (used by the DogLegSolver to recover from a collapsed trust-region radius). Otherwise the stale Jacobian and its factorization are reused (quasi-Newton). Returns the solver s.
stalled is what makes the quasi-Newton mode safe to combine with max_stalls: a step that did not move the iterate would otherwise rebuild the same direction from the same stale Jacobian on the next refactorize - 1 iterations and reproduce the same negligible step, so the solve would be given up on (see stalled_step) for a reason a fresh Jacobian could have fixed. Refreshing immediately means the second consecutive stall is one that a fresh Jacobian did not fix, which is the conclusive evidence max_stalls = 2 assumes. It is also the response check_anchor prescribes for an ascent anchor, which is a stale-Jacobian symptom.
SimpleSolvers.meets_stopping_criteria — Method
meets_stopping_criteria(state, config)Determines whether the iteration stops based on the current NonlinearSolverState.
The function meets_stopping_criteria may return true even if the solver has not converged. To check convergence, call assess_convergence (with the same input arguments).
The function meets_stopping_criteria returns true if one of the following is satisfied:
- the
status::NonlinearSolverStatusis converged (checked withisconverged) andstate.iterations ≥ config.min_iterations, - the
statushas stagnated (checked withisstalled, i.e.config.max_stallsconsecutive steps that did not move the iterate while the residual is not small) andstate.iterations ≥ config.min_iterations, status.f_increasedandconfig.allow_f_increases = false(i.e.fincreased even though we do not allow it),state.iterations ≥ config.max_iterations,status.rfₐ > config.f_abstol_break(by defaultInf). In theory this returnstrueif the residual gets too big.- one of the residuals (
rxₛ,rfₐ,rfₛ) isNaN(checked withhavenan) andstate.iterations ≥ 1,
So convergence is only one possible criterion for which meets_stopping_criteria. We may also satisfy a stopping criterion without having convergence!
Examples
In the following example we show that meets_stopping_criteria evaluates to true when used on a freshly allocated NonlinearSolverStatus:
julia> config = Options(verbosity=0);
julia> x = [NaN, 2., 3.]
3-element Vector{Float64}:
NaN
2.0
3.0
julia> f = [NaN, 10., 20.]
3-element Vector{Float64}:
NaN
10.0
20.0
julia> cache = NonlinearSolverCache(x, copy(x));
julia> state = NonlinearSolverState(x);
julia> update!(state, x, f); state.iterations += 1
1
julia> status = NonlinearSolverStatus(state, config);
julia> meets_stopping_criteria(state, config)
trueThis obviously has not converged. To check convergence we can use assess_convergence. ```
SimpleSolvers.method — Method
method(ls)Return the method (of type LinearSolverMethod) of the LinearSolver.
SimpleSolvers.minimum_decrease_threshold — Method
minimum_decrease_threshold(T)The minimum value by which a function $f$ should decrease during an iteration.
The default value of $10^{-4}$ is often used in the literature [4], [1].
Examples
julia> minimum_decrease_threshold(Float64)
0.0001julia> minimum_decrease_threshold(Float32)
0.0001f0SimpleSolvers.nan_recovery! — Method
nan_recovery!(s, x, params)Damp direction(cache(s)) by nan_factor until the trial iterate x + d has a finite residual (or the nan_max_iterations budget is exhausted). On return solution(cache(s)) and value(cache(s)) hold the last trial iterate and its residual. Used by the generic and Picard solver_step!s. Returns the solver s.
SimpleSolvers.needs_refresh — Method
needs_refresh(state)true when the previous step made no progress, i.e. when a stall has been flagged for the current step (flag_stall!) or the consecutive-stall counter is nonzero (stall_number).
solver_step! passes this to maybe_refactorize! as its stalled keyword, so a quasi-Newton solver rebuilds its Jacobian immediately after a step that did not move the iterate instead of waiting for the next refactorize multiple. Both sources are consulted because record_stall! consumes the flag into the counter once per iteration, and a caller who drives solver_step! by hand may never call it.
SimpleSolvers.nonlinear_solver_warnings — Method
nonlinear_solver_warnings(status, config)Report a NonlinearSolverStatus at the end of a solve!: the iteration count if it reached warn_iterations, stagnation at the residual floor (see isstalled and stalled_step), a disallowed residual increase, a residual beyond f_abstol_break, and NaNs. Compare this to linesearch_warnings, which does the same for the inner line search, and to print_status.
All messages except the iteration count and the two hard-failure ones are gated on config.verbosity ≥ 1.
SimpleSolvers.nonlinearproblem — Method
nonlinearproblem(solver)Return the NonlinearProblem contained in the NonlinearSolver. Compare this to linearsolver.
SimpleSolvers.outcome — Method
outcome(status)The LinesearchOutcome stored in status::LinesearchStatus.
SimpleSolvers.pivot_index — Method
pivot_index(v, k)Return the index (starting from k) of the entry of v with the largest absolute value.
This is used for pivoting in factorize!.
SimpleSolvers.print_jacobian — Method
print_jacobian([io], J)Display the Jacobian J as an aligned text/plain table.
Output is written to io (defaulting to stdout).
Here the Jacobian J is a matrix. It is not a Jacobian object.
SimpleSolvers.print_status — Method
print_status(status, config)Print the solver status if:
config.verbosity$\geq1$ and one of the following three
- the solver is converged,
status.iterations ≥ config.max_iterations,status.iterations ≥ config.warn_iterations
config.verbosity$>1.$
SimpleSolvers.record_stall! — Method
record_stall!(state, config)Update the consecutive-stall counter of state::NonlinearSolverState: increment it when the last step stalled_step or the line search flagged a stall (see flag_stall!), and reset it to zero otherwise. The flag is cleared either way. Returns the new count (see stall_number).
This must be called exactly once per iteration — solve! does so right after update!. That is why the counter is not maintained inside assess_convergence or NonlinearSolverStatus: those are pure and are evaluated more than once per iteration, so incrementing there would double-count. A hand-rolled iteration that drives solver_step! directly and never calls record_stall! simply keeps the count at zero and behaves exactly as before.
SimpleSolvers.report_linesearch_status — Method
report_linesearch_status(status, name, config)Emit the messages for a LinesearchStatus; the reporting half of linesearch_warnings, whose docstring documents the verbosity and maxlog policy.
Implementation
This is a function barrier, and its signature is what makes it one. linesearch_warnings is called from solver_step! on every iteration of every solve, and takes a Linesearch — which carries the closure types of its LinesearchProblem — and a NamedTuple of parameters, so it is specialized once per problem a solver is built for. A message in its body is specialized with it, and all of the Base.CoreLogging and string-interpolation code that @warn expands to is re-inferred and re-codegen'd for each one, which on a caller that builds one solver per tableau dominates the cost of the whole solve.
Taking name and config, and nothing whose type can vary per solver, bounds the specializations of this function to one per element-type combination for the whole session. nonlinear_solver_warnings and print_status have the same shape for the same reason.
So: do not give this function a parameter whose type varies per solver, and do not move the messages back into linesearch_warnings. test/linesearch_tests.jl asserts both — the first from the method signature, which bounds the specialization set rather than sampling it, and the second by scanning the lowered code of each function for Base.CoreLogging.
The @noinline is a guard rather than the mechanism: Julia's inliner refuses a body this size anyway, but a future one that is more willing would undo the barrier, and nothing in the caller wants this inlined.
The element types are deliberately not tied together as LinesearchStatus{T}/Options{T}: this is a reporting path, and a precision mismatch anywhere upstream should not turn a diagnostic into a MethodError that replaces the problem being diagnosed. nonlinear_solver_warnings is written the same way.
SimpleSolvers.residual_small — Method
residual_small(rfₐ, config, state)Return true when the absolute residual rfₐ passes the standard $\mathrm{atol} + \mathrm{rtol}\cdot\|F(x_0)\|$ residual test,
\[r^f_a \leq \texttt{f\_abstol} + \texttt{f\_reltol}\cdot\|F(x_0)\|,\]
with $\|F(x_0)\|$ the initial_residual of state. This lets a large-magnitude or ill-conditioned solve converge once its residual is reduced by f_reltol from $\|F(x_0)\|$, while a step that stalls near $\|F(x_0)\|$ still fails. The relative term drops to zero until the state has been initialized (initial_residual is NaN), leaving the pure absolute f_abstol test.
This gate is shared by assess_convergence, which requires it in addition to a successive-change criterion, and by stalled_step, which requires its negation: a frozen iterate is convergence when the residual is small and stagnation when it is not. The two are therefore mutually exclusive by construction.
SimpleSolvers.residuals — Method
residuals(state)Compute the residuals for state::NonlinearSolverState. The computed residuals are the following:
rxₛ: successive residual (the norm of $x - \bar{x}$),rfₐ: absolute residual in $f$,rfₛ: successive residual (the norm of $y - \bar{y}$).
SimpleSolvers.resolve_jacobian — Method
resolve_jacobian(F, DF!, jacobian, x, y)Resolve the Jacobian for a nonlinear-solver constructor: an explicit DF! wins (wrapped as a JacobianFunction), otherwise an explicit jacobian, otherwise a lazily-built JacobianAutodiff. Building the autodiff Jacobian lazily avoids allocating a ForwardDiff config when either DF! or a jacobian is supplied.
SimpleSolvers.rhs — Method
rhs(cache)Return the right-hand side of the equation, stored in cache::NonlinearSolverCache.
SimpleSolvers.shift_χ_to_avoid_stalling — Method
shift_χ_to_avoid_stalling(χ, a, b, c, ε)Check whether b is closer to a or c and shift χ accordingly.
This is taken from [4].
SimpleSolvers.solve! — Function
solve!(x, s, state)Solve the NonlinearProblem contained in the NonlinearSolver with the initial condition x.
You also have to supply a NonlinearSolverState.
SimpleSolvers.solve! — Method
solve!(x, ls::LinearSolver, A, b)Solve the linear system described by:
\[ Ax = b,\]
and store it in x. Here $A$ and $b$ are provided as an input arguments.
Compare this to solve(::LinearSolver, ::AbstractVector).
SimpleSolvers.solve! — Method
solve!(x, ls::LinearSolver, b)Solve the linear system described by:
\[ Ax = b,\]
and store it in x. Here $b$ is provided as an input argument and the factorized $A$ is stored in the LinearSolver ls (respectively its LinearSolverCache).
SimpleSolvers.solve! — Method
solve!(x, ls::LinearSolver, lsys::LinearProblem)Solve the LinearProblem lsys with the LinearSolver ls and store the result in x.
Also see solve(::LU, ::AbstractMatrix, ::AbstractVector).
Examples
julia> x = zeros(3)
3-element Vector{Float64}:
0.0
0.0
0.0
julia> A = [1.; 0.; 0.;; 0.; 2.; 0.;; 0.; 0.; 4.]
3×3 Matrix{Float64}:
1.0 0.0 0.0
0.0 2.0 0.0
0.0 0.0 4.0
julia> b = ones(3)
3-element Vector{Float64}:
1.0
1.0
1.0
julia> ls = LinearSolver(LU(), x);
julia> problem = LinearProblem(x); update!(problem, A, b);
julia> solve!(x, ls, problem)
3-element Vector{Float64}:
1.0
0.5
0.25
SimpleSolvers.solve! — Method
solve!(ls::LinearSolver, args...)Solve the LinearProblem with the LinearSolver ls.
SimpleSolvers.solve — Method
solve(lu, A, b)Solve the linear problem determined by A and b.
This is the most straightforward way to solve this system.
Examples
julia> A = [1.; 0.; 0.;; 0.; 2.; 0.;; 0.; 0.; 4.]
3×3 Matrix{Float64}:
1.0 0.0 0.0
0.0 2.0 0.0
0.0 0.0 4.0
julia> b = ones(3)
3-element Vector{Float64}:
1.0
1.0
1.0
julia> solve(LU(), A, b)
3-element StaticArraysCore.SizedVector{3, Float64, Vector{Float64}} with indices SOneTo(3):
1.0
0.5
0.25Compare this to solve!(::AbstractVector, ::LinearSolver, ::LinearProblem).
SimpleSolvers.solve — Method
solve(ls::LinearSolver, args...)Counterpart of solve! for a prebuilt LinearSolver: allocates (and returns) a fresh solution vector instead of writing into a caller-supplied one. Note that the solver's cache is still updated in place (the factorization is computed there).
Accepts the same trailing arguments as solve!(ls, args...): a LinearProblem, a matrix-vector pair A, b, or a bare right-hand side b (the latter uses the factorization already stored in ls).
SimpleSolvers.solve — Method
solve(linesearch, α, params=NullParameters())Solve the LinesearchProblem (contained in Linesearch) starting at α.
The argument params needs to be of an appropriate form expected by the respective LinesearchProblem.
See linesearch_problem.
SimpleSolvers.solve — Method
solve(ls::Linesearch{T,<:Backtracking}, α, params)Run the backtracking line search from the trial step α, report the outcome through linesearch_warnings and return the accepted step length.
Use solve_with_status to obtain the LinesearchStatus instead: a caller that has to tell "I found a decreasing step" from "the merit is at its round-off floor and nothing can decrease it" cannot do so from the step length alone.
SimpleSolvers.solve — Method
solve(ls::Linesearch{T,<:BierlaireQuadratic}, α, params)Fit successive quadratics through three bracketing points to approximate the line minimiser, report the outcome through linesearch_warnings and return the step length. See BierlaireQuadratic and solve_with_status.
SimpleSolvers.solve — Method
solve(ls::Linesearch{T,<:Bisection}, α, params)Bisect the derivative of the merit to approximate the line minimiser, report the outcome through linesearch_warnings and return the step length. See Bisection and solve_with_status.
SimpleSolvers.solve — Method
solve(ls::Linesearch{T,<:Quadratic}, α, params)Fit successive quadratics to approximate the line minimiser, report the outcome through linesearch_warnings and return the step length. See Quadratic and solve_with_status.
SimpleSolvers.solve — Method
solve(ls::Linesearch{T,<:StrongWolfe}, α, params)Run the strong-Wolfe bracketing line search starting from the trial step α, report the outcome through linesearch_warnings and return the accepted step length. See StrongWolfe and solve_with_status.
SimpleSolvers.solve_with_status — Method
solve_with_status(ls, α, params=NullParameters())Like solve, but return a LinesearchStatus — the step length plus the reason the search stopped — and emit no log messages. Use linesearch_warnings to report the status.
Only Backtracking reports a genuine LinesearchOutcome; the generic fallback calls solve and reports LINESEARCH_UNKNOWN, so a caller may use solve_with_status uniformly for every LinesearchMethod.
SimpleSolvers.solver_step! — Method
solver_step!(x, s, state, params)Compute one step for solving the problem stored in an instance s of NonlinearSolver.
Examples
julia> f(y, x, params) = y .= sin.(x .- .5) .^ 2
f (generic function with 1 method)
julia> x = ones(3) / 4
3-element Vector{Float64}:
0.25
0.25
0.25
julia> y = zero(x)
3-element Vector{Float64}:
0.0
0.0
0.0
julia> s = NewtonSolver(x, similar(x); F = f);
julia> state = NonlinearSolverState(x); update!(state, x, f(y, x, NullParameters()));
julia> solver_step!(x, s, state, NullParameters())
3-element Vector{Float64}:
0.37767096061051814
0.37767096061051814
0.37767096061051814SimpleSolvers.solver_step! — Method
solver_step!(x, s::PicardSolver, state, params)Take one fixed-point (Picard) step $x \gets x + \alpha d$ with the residual direction $d = -F(x)$ (see direction!).
Unlike a Newton/Gauss-Newton step, the Picard direction $d = -F(x)$ is not in general a descent direction for the merit $\varphi = \|F\|^2$, so applying the derivative-based (Wolfe) line search used by the other NonlinearSolvers is inappropriate (a directional derivative that is not negative makes the sufficient- decrease/curvature tests meaningless).
Instead the step is damped by a residual-monotonicity backtracking: starting from the full fixed-point step $\alpha = 1$ the step is halved until the residual norm does not increase, $\|F(x + \alpha d)\| \le \|F(x)\|$. This safeguard uses only function values and makes no descent assumption. If no positive $\alpha$ reduces the residual (the fixed-point map is locally expanding), the smallest trial step is taken and the convergence test — which requires a small residual, not merely a small step — correctly reports non-convergence instead of a false positive.
SimpleSolvers.stall_number — Method
stall_number(state)Return the number of consecutive stalled steps recorded in state::NonlinearSolverState by record_stall!. Compare this to iteration_number.
SimpleSolvers.stalled_step — Method
stalled_step(rxₛ, rfₐ, config, state)Return true when the last step stalled: it left the iterate unchanged (see iterate_settled) while the residual is not small (see residual_small).
A stalled step is the failure mode that the residual gate in assess_convergence correctly refuses to call convergence, and that used to be invisible to the solver. The step length $\alpha\|d\|$ has dropped below the round-off level of $x$, so the merit $\|F\|^2$ cannot be reduced along the current direction — typically because the requested f_abstol lies below the round-off floor of the residual itself. Taking another step recomputes the same direction and the same negligible $\alpha$, so the iteration would spin all the way to max_iterations, asking the line search on every one of those steps to improve a residual that is already pure round-off noise.
meets_stopping_criteria therefore stops after config.max_stalls consecutive stalled steps (counted by record_stall!), and nonlinear_solver_warnings reports the achieved residual against the requested tolerance instead of the misleading "Solver took 1000 iterations.".
The condition is deliberately phrased in terms of the step actually taken rather than a line-search return code. It is the same diagnosis for a Backtracking ladder that exhausted, a StrongWolfe search that found no acceptable step, a Static step along an underflowed direction, a DogLegSolver whose trust-region radius collapsed and a PicardSolver whose fixed-point map is locally expanding. A line search that knows it is at the round-off floor can report one iteration earlier via flag_stall!.
SimpleSolvers.status — Method
status(solver, state)Return the NonlinearSolverStatus for the NonlinearSolverState state as assessed with the Options of solver.
solve! returns the solution x (updated in place), not a status, so this is how a caller inspects the outcome of a solve — in particular whether it converged (isconverged) or merely stagnated at the residual floor (isstalled). The state is the caller's own object (it is passed to solve!), so nothing has to be threaded back out of the solve.
Examples
julia> F(y, x, params) = y .= x .^ 2 .- 2;
julia> x = [1.0]; s = NewtonSolver(x, similar(x); F = F, verbosity = 0);
julia> state = SolverState(s);
julia> solve!(x, s, state);
julia> isconverged(status(s, state))
trueSimpleSolvers.steplength — Method
steplength(status)The step length stored in status::LinesearchStatus, i.e. what solve returns.
SimpleSolvers.trials — Method
trials(status)The number of trial steps at which the merit was evaluated, stored in status::LinesearchStatus.
SimpleSolvers.triple_point_finder — Method
triple_point_finder(f, x)Find three points a < b < c (strictly ordered in position) with f(a) ≥ f(b) and f(c) > f(b), so that a minimum is bracketed in (a, c). This is used for performing a quadratic line search (see BierlaireQuadratic). Returns a Symbol instead of a triple when no such triple exists — see the warning below.
The left inequality is non-strict (f(a) ≥ f(b)): while descending, consecutive samples may tie on a plateau, and for a flat-bottomed f a strict f(a) > f(b) is unattainable. f(b) is still strictly below f(c), and BierlaireQuadratic guards a degenerate (collinear) fit by falling back to a bisection step, so the non-strict left bound is sufficient to bracket the minimum.
Unlike bracket_minimum, which flips direction when f increases to the right, triple_point_finder only ever searches in the direction of increasing x and therefore requires f to be decreasing at x₀. A caller that cannot guarantee that — a line search whose direction came from a stale or regularized Jacobian, say — must check the anchor itself (see check_anchor).
When no triple can be found the function returns a Symbol rather than raising: a line search must be able to report an unbracketable merit rather than abort the enclosing solve. The two failures mean opposite things and are therefore distinguished, because a caller that conflates them reports a descending merit as stagnation:
:flat— the rise at the first probe is within the round-off resolution off(x₀)(armijo_tolerance), sofdoes not resolve a decrease here at all. No line search can improve on this point (LINESEARCH_FLOOR).:unbracketable— there is a decrease, but it cannot be bracketed: eithernmaxdoublings never reached a turning point, orfrose at every probe down to the smallestδtried. This is a genuine failure to report (LINESEARCH_EXHAUSTED), not a floor.
Implementation
For δ we take DEFAULT_BRACKETING_s as default. For nmax we take DEFAULT_BRACKETING_nmax as default.
Examples
julia> f(x) = x ^ 2
f (generic function with 1 method)
julia> x = -1.
-1.0
julia> a, b, c = round10.(triple_point_finder(f, x))
(-0.37, 0.27, 1.55)
julia> round10.((f(a), f(b), f(c)))
(0.1369, 0.0729, 2.4025)Extended help
The algorithm is taken from [4, Chapter 11.2.1].
SimpleSolvers.trust_radius! — Method
trust_radius!(cache::DogLegCache, Δ)Store the trust-region radius $\Delta$ in the DogLegCache so it carries over to the next outer solver step.
SimpleSolvers.trust_radius — Method
trust_radius(cache::DogLegCache)Return the current trust-region radius $\Delta$ carried by the DogLegCache.
SimpleSolvers.value! — Method
value!(y, nlp, x, params)Evaluate the NonlinearProblem at x.
SimpleSolvers.with_config — Method
with_config(ls, config)Return a Linesearch with the LinesearchProblem and the LinesearchMethod of ls, but with the Options config.
This is how a NonlinearSolver makes its line search share its options. A Linesearch built by Linesearch(problem, method) carries an Options of its own, constructed from nothing but defaults — so verbosity and linesearch_max_iterations would be configured twice, and verbosity = 0 on the solver would not silence the line search.
Linesearch is an immutable three-field wrapper, so rebuilding it is cheap: the problem (and hence its closures and scratch buffers) and the method are shared, not copied.
The Options element type is pinned to the Linesearch element type, so a mismatched config raises a MethodError rather than silently producing a broken object — the same guarantee the three-argument Linesearch constructor gives.