Add an objective merit so line searches serve OptimizationProblems - #86
Draft
ChrisRackauckas-Claude wants to merge 1 commit into
Draft
Add an objective merit so line searches serve OptimizationProblems#86ChrisRackauckas-Claude wants to merge 1 commit into
ChrisRackauckas-Claude wants to merge 1 commit into
Conversation
Line search algorithms in this package were tied to root finding in two ways: every `init` dispatched on `AbstractNonlinearProblem`, and each one inlined the merit `ϕ(α) = ‖F(u + α du)‖²/2` into its own closures. An `OptimizationProblem` could not enter at all — it is not a subtype of `AbstractNonlinearProblem` — so an optimizer wanting Wolfe conditions had to reimplement the searches. The scalar function is the only merit-specific part of any of these algorithms. This factors it out: - `AbstractMerit` with `ResidualMerit` (‖F‖²/2, ϕ' via a JVP) and `ObjectiveMerit` (f itself, ϕ' = ⟨∇f, du⟩ against a gradient the caller usually already has, rather than a Hessian-vector product). - `MeritEvaluator`, built once by `init_merit` and reused, so a search allocates nothing in steady state. - `init(prob::OptimizationProblem, alg, u)` alongside the existing nonlinear entry point, for BackTracking, StrongWolfe and GoldenSection. Two related interface gaps are closed at the same time: - `LineSearchSolution` gains optional `ϕ` and `dϕ` fields, and the evaluator is left holding the accepted point. Callers previously had to re-evaluate the objective to recover its value and gradient at the accepted step — a full derivative pass per outer iteration. - `solve!` accepts `ϕ0`/`dϕ0` keywords, and `set_initial_step!` sets the first trial step. Every algorithm re-evaluated at α = 0 on entry even though the caller had just computed it; on a quasi-Newton method that roughly doubled the gradient evaluations per iteration. Derivative-free searches keep working without an AD backend: `init_merit` takes `need_deriv`, and GoldenSection passes `false` rather than being forced to construct a Jacobian operator it never uses. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
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.
Motivation
Line searches here are currently tied to root finding in two ways:
CommonSolve.initdispatches onAbstractNonlinearProblem. AnOptimizationProblemcannot enter at all — it is not a subtype (OptimizationProblem <: AbstractNonlinearProblemisfalse, whileIntervalNonlinearProblem <: AbstractNonlinearProblemistrue).ϕ(α) = ‖F(u + α du)‖²/2into its ownϕ/ϕdϕclosures.But the scalar function is the only merit-specific part of any of these algorithms — the bracketing, zoom and interpolation logic is entirely merit-agnostic. Consequently an optimizer that wants Wolfe conditions has to reimplement searches this package already has.
Pointing the existing residual path at
∇f = 0is not a workaround:ϕwould be‖∇f‖²/2, which is zero at saddles and maxima too, andderiv_op'sdot(fu, jvp_op(du,u,p))becomes a Hessian-vector product at every trial step.What this does
AbstractMerit, withResidualMerit(‖F‖²/2,ϕ'via a JVP) andObjectiveMerit(fitself,ϕ' = ⟨∇f, du⟩— a dot product against a gradient the caller usually already holds).MeritEvaluator, built once byinit_meritand reused acrosssolve!calls, so a search allocates nothing in steady state.init(prob::OptimizationProblem, alg, u)alongside the existing nonlinear entry point, forBackTracking,StrongWolfeLineSearchandGoldenSection.Two related interface gaps are closed at the same time, both of which cost evaluations today:
LineSearchSolutiongains optionalϕanddϕ, and the evaluator is left holding the accepted point (ensure_evaluated_at!). Callers previously had to re-evaluate to recover the objective and gradient at the accepted step — a full derivative pass per outer iteration.solve!acceptsϕ0/dϕ0keywords, andset_initial_step!sets the first trial step per call. Every algorithm re-evaluated atα = 0on entry even though the caller had just computed it. Driving an external L-BFGS, removing that redundancy cut evaluations from 3519 to 1938 on a 45-problem suite — it was close to doubling the gradient count per iteration.init_merittakesneed_deriv, soGoldenSectionstays derivative-free instead of being forced to build a Jacobian operator it never uses.Compatibility
LineSearchSolution(step_size, retcode)still constructs; the new fields default tonothing.LiFukushimaLineSearchandRobustNonMonotoneLineSearchare untouched — their merits (‖F‖,‖F‖^n_exp) are residual-specific by design and baked into their acceptance tests.CoreandLineSearchesJLgroups pass unchanged (229 tests). Runic clean.Tests
test/objective_merit_tests.jlcovers the objective entry point for each converted algorithm, the derivative-free path,ϕ/dϕreporting and cache reuse, both gradient arities (2-arg instantiated and 3-arg raw), a clear error when no gradient is available, zero allocations insolve!, andFloat32/BigFloat.The sharpest check states one problem both ways —
F(u) = 0with merit‖F‖²/2, and the objectivef(u) = ‖F(u)‖²/2— and asserts the two merits produce identical step sizes, sinceϕis literally the same scalar function.🤖 Generated with Claude Code