Callbacks

NLSolvers.jl supports user-defined callbacks that are invoked after every iteration of any optimization solver. Callbacks let you monitor progress, log intermediate state, build a convergence trace, or stop the solver early based on custom criteria.

Basic usage

A callback is any function (or callable object) that takes a single info argument and returns a Bool:

using NLSolvers

callback = info -> begin
    println("iter=$(info.iter), f=$(info.state.fz)")
    return false   # continue optimization
end

solve(prob, x0, LineSearch(BFGS()),
      OptimizationOptions(callback = callback))

Returning true stops the solver early; returning false lets it continue. The default callback = nothing disables the mechanism with zero overhead.

What the callback receives

The info argument is a NamedTuple with three fields:

FieldTypeDescription
iterIntCurrent iteration count (1-based)
timeFloat64Elapsed seconds since solve started
stateNamedTupleSolver-specific state (see below)

Solver-specific state

The contents of info.state depend on which solver is running. Use haskey if you write generic callbacks.

Line search solvers (BFGS, DBFGS, DFP, SR1, L-BFGS, CG, Gradient Descent, Newton)

state is the internal objvars named tuple:

FieldDescription
xPrevious iterate
fxObjective value at x
∇fxGradient at x
zNew iterate (after line search)
fzObjective value at z
∇fzGradient at z
BCurrent Hessian (or inverse) approximation; nothing for L-BFGS/CG
PgPreconditioned gradient if using a preconditioner; otherwise nothing

Trust region solvers

Same fields as line search, plus:

FieldDescription
ΔCurrent trust region radius
rejectedtrue if the previous step was rejected by the trust region rule

Nelder-Mead

FieldDescription
simplex_vectorVector of simplex vertices
simplex_valueFunction values at each vertex
x_centroidCentroid of the simplex (excluding the worst vertex)
nm_objConvergence metric — standard deviation of simplex_value

Simulated Annealing

FieldDescription
x_bestBest point found so far
f_bestBest objective value found so far
x_nowCurrent state of the chain
f_nowObjective value at x_now
temperatureCurrent temperature

Particle Swarm

FieldDescription
XCurrent particle positions
X_bestEach particle's personal best
FsFunction values at X
Fs_bestFunction values at X_best
xGlobal best particle
best_fGlobal best objective value
swarm_fConvergence metric for the swarm

Brent's method (univariate)

FieldDescription
xCurrent best point
fxFunction value at x
a, bCurrent bracketing interval [a, b]
v, wTwo previous iterates
fv, fwFunction values at v and w

Active Box (projected Newton)

FieldDescription
xPrevious iterate
zNew iterate
fzObjective value at z
∇fzGradient at z
BHessian approximation
activesetBoolean vector indicating active bound constraints

Examples

Build a convergence trace

trace = Float64[]
solve(prob, x0, LineSearch(BFGS()),
      OptimizationOptions(
          callback = info -> (push!(trace, info.state.fz); false),
          maxiter = 100,
      ))

Stop when the gradient is sufficiently small

gtol = 1e-6
solve(prob, x0, LineSearch(BFGS()),
      OptimizationOptions(
          callback = info -> norm(info.state.∇fz, Inf) < gtol,
          g_abstol = 0.0,  # disable built-in g-tolerance so callback wins
      ))

Time-limited optimization

time_limit = 5.0  # seconds
solve(prob, x0, LineSearch(BFGS()),
      OptimizationOptions(callback = info -> info.time > time_limit))

Save iterates for plotting later

Arrays in info.state (such as z, ∇fz, simplex_vector) are aliases of live solver buffers — they will be overwritten on the next iteration. Copy them if you need to retain them past the callback call.

history = Vector{Vector{Float64}}()
solve(prob, x0, LineSearch(BFGS()),
      OptimizationOptions(
          callback = info -> (push!(history, copy(info.state.z)); false),
      ))

Scalar fields like info.iter, info.time, info.state.fz are values and do not need copying.

Performance

The callback machinery has zero runtime overhead when callback === nothing (the default). The callback type is captured as a type parameter on OptimizationOptions, so the compiler eliminates the dispatch entirely. Passing a concrete callback function adds only the cost of calling that function once per iteration.