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:
| Field | Type | Description |
|---|---|---|
iter | Int | Current iteration count (1-based) |
time | Float64 | Elapsed seconds since solve started |
state | NamedTuple | Solver-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:
| Field | Description |
|---|---|
x | Previous iterate |
fx | Objective value at x |
∇fx | Gradient at x |
z | New iterate (after line search) |
fz | Objective value at z |
∇fz | Gradient at z |
B | Current Hessian (or inverse) approximation; nothing for L-BFGS/CG |
Pg | Preconditioned gradient if using a preconditioner; otherwise nothing |
Trust region solvers
Same fields as line search, plus:
| Field | Description |
|---|---|
Δ | Current trust region radius |
rejected | true if the previous step was rejected by the trust region rule |
Nelder-Mead
| Field | Description |
|---|---|
simplex_vector | Vector of simplex vertices |
simplex_value | Function values at each vertex |
x_centroid | Centroid of the simplex (excluding the worst vertex) |
nm_obj | Convergence metric — standard deviation of simplex_value |
Simulated Annealing
| Field | Description |
|---|---|
x_best | Best point found so far |
f_best | Best objective value found so far |
x_now | Current state of the chain |
f_now | Objective value at x_now |
temperature | Current temperature |
Particle Swarm
| Field | Description |
|---|---|
X | Current particle positions |
X_best | Each particle's personal best |
Fs | Function values at X |
Fs_best | Function values at X_best |
x | Global best particle |
best_f | Global best objective value |
swarm_f | Convergence metric for the swarm |
Brent's method (univariate)
| Field | Description |
|---|---|
x | Current best point |
fx | Function value at x |
a, b | Current bracketing interval [a, b] |
v, w | Two previous iterates |
fv, fw | Function values at v and w |
Active Box (projected Newton)
| Field | Description |
|---|---|
x | Previous iterate |
z | New iterate |
fz | Objective value at z |
∇fz | Gradient at z |
B | Hessian approximation |
activeset | Boolean 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.