Use the default OptimizationCache in OptimizationIpopt - #1257
Merged
Conversation
Replace the bespoke IpoptCache (copied from OptimizationMOI's
evaluator/cache split) with OptimizationBase's default OptimizationCache,
mirroring the structure of OptimizationMadNLP: the package now only
defines the optimizer struct, the SciMLBase traits, and
__solve(::OptimizationCache{<:IpoptOptimizer}), with the solver-specific
state (derivative buffers, sparsity, call counters) held by a transient
IpoptEvaluator built inside __solve. This removes the hand-rolled
__init, the ReInitCache delegation, the solution accessors, and the
dead MOI leftovers (int, obj_expr/cons_expr, discarded sys).
Behavior fixes that fell out of the refactor:
- MaxSense is now handled with Ipopt's obj_scaling_factor = -1 instead
of manually negating in the evaluators, fixing the sign of
sol.objective and of the value passed to user callbacks.
- The live cache.p is forwarded to the instantiated derivative closures
(guarded for AutoSymbolics/NoAD/parameterless paths), so
reinit!(cache; p = ...) no longer silently converges to the optimum
of the old parameters.
- requireshessian/requiresconshess/requireslagh are conditional on
hessian_approximation, so limited-memory runs skip second-order AD
entirely and pass eval_h = nothing; requireslagh was previously
missing and the unused cons_vjp is no longer generated (Ipopt's C API
has no vjp/jvp callbacks).
Test suite cleanup, cross-checked against the Ipopt repo:
- Fix the mistranslated MyNLP problem and the invented LuksanVlcek1
constraint bounds/starting point; pin their optima.
- The restoration phase test's constraints are mutually infeasible, so
assert ReturnCode.Infeasible instead of a vacuous guarded check.
- Make the warm-start test actually warm-start via reinit!, verify
print levels through Ipopt's output_file, make the option-priority
test discriminating, check the derivative checker's verdict, and
seed the stress-test RNGs.
- Add tests for the MaxSense objective sign, reinit! with new
parameters, and the limited-memory trait behavior.
- Prune redundant testsets (NLS, mixed-integer, non-smooth robust
optimization, dead complementarity block, duplicate verbose loop)
and drop the inaccurate "automatically translated" headers, keeping
EPL attribution on the genuinely Ipopt-derived problems.
MTK reinit! stays @test_broken: both the plain-vector and symbolic-map
forms are blocked upstream (ReInitCache field conversion and SciMLBase's
process_p_u0_symbolic fallback).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BTyNNDwaNrJFyNzN6XTqcn
SebastianM-C
marked this pull request as ready for review
July 13, 2026 17:09
This was referenced Jul 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cleans up OptimizationIpopt, which had copied OptimizationMOI's structure when it was created. The bespoke
IpoptCache(a merge of MOI'sMOIOptimizationNLPEvaluator+MOIOptimizationNLPCache) is replaced with OptimizationBase's defaultOptimizationCache, following the structure of OptimizationMadNLP: the package now only definesIpoptOptimizer, the SciMLBase traits, and__solve(::OptimizationCache{<:IpoptOptimizer}). Solver-specific state (derivative buffers, sparsity, call counters) lives in a transientIpoptEvaluatorbuilt inside__solve.Deleted along with the custom cache: the hand-rolled
__init, the ReInitCachegetpropertydelegation, the four solution accessors, theAutoSymbolicsinstantiation special-case (traits + the evaluator'slag_h → hess + cons_hfallback cover it), and dead MOI leftovers (int,obj_expr/cons_expr, a discardedsys, staleevaluator.J/evaluator.Hcomments).Behavior fixes
__solvereportedobj_valwithout flipping it back, sosol.objective(and the objective passed to user callbacks) had the wrong sign for maximization. Now handled with Ipopt'sobj_scaling_factor = -1(negative ⇒ maximize, unscaled objective reported), deleting all manual sign-flipping.reinit!(cache; p = ...)silently converged to the old optimum: the DI closures capturepatinittime, so the objective saw the new parameters while every derivative saw the old ones. The evaluator now forwards the livecache.p(guarded forAutoSymbolics,NoADwithout parameters, andp === nothing).requireshessian/requiresconshess/requireslagh(the latter was previously missing) now depend onhessian_approximation, so limited-memory runs skip Hessian AD generation entirely and passeval_h = nothingto Ipopt. The unusedcons_vjpis no longer generated — Ipopt's C API has no vjp/jvp callbacks.Test suite
The three "automatically translated from the Ipopt tests" files were audited against the Ipopt repo; only two testsets actually traced back to it, both flawed:
min −(x₂−2)²s.t.x₁² + x₂ − 1 = 0, optimum(±1, 0)with objective −4).[4,6]vs the registered[0,0]/[−1,0]) and a changed start — now the equality form with the original alternating start, pinned to its optimum.x₁²+x₂² = 0.5the max ofx₁³+x₂³is ≈0.354 < 1), which is why its guarded assertion never ran; it now assertsReturnCode.Infeasible.reinit!; print levels are verified through Ipopt'soutput_file; the option-priority test is now discriminating; the derivative test checks the derivative checker's verdict; stress-test RNGs are seeded.reinit!with new parameters, limited-memory trait behavior.MTK
reinit!with new parameters remains@test_broken: the plain-vector form can'tconvertinto the concretely typedReInitCache{_, MTKParameters}field, and the symbolic-map form hits SciMLBase's genericprocess_p_u0_symbolicfallback ("Symbolic remake … is currently not supported"). Both need upstream fixes.Test results (Julia 1.12.6,
Pkg.test)no_implicit_imports)Version bumped to 1.3.0.
🤖 Generated with Claude Code