Description
The missing_second_order_ad verbosity message warns that a Hessian-requiring solver was selected without a SecondOrder AD type, and that a SecondOrder fallback "will be created". The check that emits it, however, only inspects prob.f.adtype and the solver's requirements — it never checks whether the OptimizationFunction already carries a user-supplied analytical Hessian:
https://github.com/SciML/Optimization.jl/blob/main/lib/OptimizationBase/src/cache.jl#L66-L76
if !(
prob.f.adtype isa DifferentiationInterface.SecondOrder ||
prob.f.adtype isa AutoZygote
) &&
(
SciMLBase.requireshessian(opt) || SciMLBase.requiresconshess(opt) ||
SciMLBase.requireslagh(opt)
)
@SciMLMessage(
lazy"The selected optimization algorithm requires second order derivatives, but `SecondOrder` ADtype was not provided. ...",
processed_verbose, :missing_second_order_ad
)
So the warning fires for the fully-supported pattern "analytical grad + analytical hess + NoAD", where it is a false positive on both counts:
- Nothing is missing. With
NoAD, instantiate_function simply wraps the user callbacks (lib/OptimizationBase/src/function.jl, the SciMLBase.NoAD methods), and on the DI path AD Hessians are only constructed when f.hess === nothing (lib/OptimizationBase/src/OptimizationDIExt.jl, hess = if h == true && f.hess === nothing ...). The user Hessian always takes precedence.
- Nothing "will be created." For
NoAD, adtype_to_soadtype maps soadtype = NoAD — no SecondOrder wrapper is built, contrary to the message text.
MWE
using Optimization, OptimizationOptimJL
rosenbrock(x, p) = (1 - x[1])^2 + 100 * (x[2] - x[1]^2)^2
function rosen_grad!(g, x, p)
g[1] = -2 * (1 - x[1]) - 400 * x[1] * (x[2] - x[1]^2)
g[2] = 200 * (x[2] - x[1]^2)
return nothing
end
function rosen_hess!(H, x, p)
H[1, 1] = 2 - 400 * (x[2] - 3 * x[1]^2)
H[1, 2] = -400 * x[1]; H[2, 1] = -400 * x[1]; H[2, 2] = 200.0
return nothing
end
f = OptimizationFunction(rosenbrock; grad = rosen_grad!, hess = rosen_hess!)
prob = OptimizationProblem(f, zeros(2))
sol = solve(prob, Newton())
emits
┌ Warning: Verbosity toggle: missing_second_order_ad
│ The selected optimization algorithm requires second order derivatives, but `SecondOrder` ADtype was not provided. So a `SecondOrder` with SciMLBase.NoAD() for both inner and outer will be created, this can be suboptimal and not work in some cases so an explicit `SecondOrder` ADtype is recommended.
while converging to (1.0, 1.0) with retcode: Success. Instrumenting rosen_hess! with a counter shows it is called 18 times during this solve — the analytical Hessian is what Newton consumes, exactly as intended.
Why it matters beyond noise
The message is emitted at cache creation, i.e. once per solve call. Workflows that re-solve a cached OptimizationProblem in a hot loop get one warning per solve. In my use case, where I solve a convex dual problem with analytical derivatives once per MCMC likelihood evaluation, this produces 25,011 copies of the warning (11 MB of logs) in a single chain.
Proposed fix
Gate each requirement on the corresponding user-supplied field being absent, e.g.
needs_hess = SciMLBase.requireshessian(opt) && prob.f.hess === nothing &&
prob.f.fgh === nothing && prob.f.hv === nothing
needs_cons_hess = SciMLBase.requiresconshess(opt) && prob.f.cons_h === nothing
needs_lag_hess = SciMLBase.requireslagh(opt) && prob.f.lag_h === nothing
if !(prob.f.adtype isa DifferentiationInterface.SecondOrder ||
prob.f.adtype isa AutoZygote) &&
(needs_hess || needs_cons_hess || needs_lag_hess)
@SciMLMessage(...)
end
(and analogously for the AutoZygote branch). Happy to open a PR along these lines if the approach sounds right.
Workaround for anyone hitting this
solve(prob, Newton(); verbose = OptimizationVerbosity(missing_second_order_ad = SciMLLogging.Silent())) silences exactly this toggle while keeping other diagnostics.
Environment
- Optimization v5.6.1, OptimizationBase v5.1.3 (same check present on current
main)
- OptimizationOptimJL v0.4.14
- Julia 1.12.6, macOS (aarch64)
Description
The
missing_second_order_adverbosity message warns that a Hessian-requiring solver was selected without aSecondOrderAD type, and that aSecondOrderfallback "will be created". The check that emits it, however, only inspectsprob.f.adtypeand the solver's requirements — it never checks whether theOptimizationFunctionalready carries a user-supplied analytical Hessian:https://github.com/SciML/Optimization.jl/blob/main/lib/OptimizationBase/src/cache.jl#L66-L76
So the warning fires for the fully-supported pattern "analytical
grad+ analyticalhess+NoAD", where it is a false positive on both counts:NoAD,instantiate_functionsimply wraps the user callbacks (lib/OptimizationBase/src/function.jl, theSciMLBase.NoADmethods), and on the DI path AD Hessians are only constructed whenf.hess === nothing(lib/OptimizationBase/src/OptimizationDIExt.jl,hess = if h == true && f.hess === nothing ...). The user Hessian always takes precedence.NoAD,adtype_to_soadtypemapssoadtype = NoAD— noSecondOrderwrapper is built, contrary to the message text.MWE
emits
while converging to
(1.0, 1.0)withretcode: Success. Instrumentingrosen_hess!with a counter shows it is called 18 times during this solve — the analytical Hessian is what Newton consumes, exactly as intended.Why it matters beyond noise
The message is emitted at cache creation, i.e. once per
solvecall. Workflows that re-solve a cachedOptimizationProblemin a hot loop get one warning per solve. In my use case, where I solve a convex dual problem with analytical derivatives once per MCMC likelihood evaluation, this produces 25,011 copies of the warning (11 MB of logs) in a single chain.Proposed fix
Gate each requirement on the corresponding user-supplied field being absent, e.g.
(and analogously for the
AutoZygotebranch). Happy to open a PR along these lines if the approach sounds right.Workaround for anyone hitting this
solve(prob, Newton(); verbose = OptimizationVerbosity(missing_second_order_ad = SciMLLogging.Silent()))silences exactly this toggle while keeping other diagnostics.Environment
main)