From a3a7104ac214ac285959937637a1ffcc4207d604 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Wed, 24 Jun 2026 22:06:54 -0400 Subject: [PATCH 1/2] Fix `verbose` handling in OptimizationMOI `__map_optimizer_args` read `cache.verbose`, but the MOI caches (`MOIOptimizationNLPCache`/`MOIOptimizationCache`) store solve kwargs in `solver_args` and have no `verbose` field, so passing `verbose` together with a common tolerance threw `FieldError: type MOIOptimizationNLPCache has no field verbose`. Separately, `verbose` was splatted from `solver_args` into the raw optimizer-attribute loop, so a plain `verbose=`-solve failed with `Unable to add option "verbose"`. Consume `verbose` as a named keyword of `__map_optimizer_args` (so it is no longer forwarded as a raw solver attribute) and run it through `_process_verbose_param` for the `@SciMLMessage` calls. Both manifestations surface through the BoundaryValueDiffEq optimization-based BVP path, which passes `abstol` and an `OptimizationVerbosity` to the optimizer. Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01RtJAi7bALH5XkakjS6Yb6j --- lib/OptimizationMOI/Project.toml | 2 +- lib/OptimizationMOI/src/OptimizationMOI.jl | 11 +++++++---- lib/OptimizationMOI/src/moi.jl | 1 - lib/OptimizationMOI/src/nlp.jl | 1 - lib/OptimizationMOI/test/core_tests.jl | 20 ++++++++++++++++++++ 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/lib/OptimizationMOI/Project.toml b/lib/OptimizationMOI/Project.toml index 5ce8950a9..e54a4251c 100644 --- a/lib/OptimizationMOI/Project.toml +++ b/lib/OptimizationMOI/Project.toml @@ -1,6 +1,6 @@ name = "OptimizationMOI" uuid = "fd9f6733-72f4-499f-8506-86b2bdd0dea1" -version = "1.3.1" +version = "1.3.2" authors = ["Vaibhav Dixit and contributors"] [deps] diff --git a/lib/OptimizationMOI/src/OptimizationMOI.jl b/lib/OptimizationMOI/src/OptimizationMOI.jl index fe51c14f6..b59c1360c 100644 --- a/lib/OptimizationMOI/src/OptimizationMOI.jl +++ b/lib/OptimizationMOI/src/OptimizationMOI.jl @@ -88,7 +88,6 @@ function _create_new_optimizer(opt::MOI.AbstractOptimizer) end function __map_optimizer_args( - cache, opt::Union{ MOI.AbstractOptimizer, MOI.OptimizerWithAttributes, }; @@ -96,8 +95,12 @@ function __map_optimizer_args( maxtime::Union{Number, Nothing} = nothing, abstol::Union{Number, Nothing} = nothing, reltol::Union{Number, Nothing} = nothing, + verbose = OptimizationBase.DEFAULT_VERBOSE, kwargs... ) + # `verbose` is consumed here as a named keyword so it isn't forwarded into the + # `kwargs` loop below, which would otherwise try to set it as a raw solver attribute. + opt_verbose = OptimizationBase._process_verbose_param(verbose) optimizer = _create_new_optimizer(opt) for (key, value) in kwargs MOI.set(optimizer, MOI.RawOptimizerAttribute("$(key)"), value) @@ -108,19 +111,19 @@ function __map_optimizer_args( if !isnothing(reltol) @SciMLMessage( lazy"common reltol argument is currently not used by $(optimizer). Set tolerances via optimizer specific keyword arguments.", - cache.verbose, :unsupported_kwargs + opt_verbose, :unsupported_kwargs ) end if !isnothing(abstol) @SciMLMessage( lazy"common abstol argument is currently not used by $(optimizer). Set tolerances via optimizer specific keyword arguments.", - cache.verbose, :unsupported_kwargs + opt_verbose, :unsupported_kwargs ) end if !isnothing(maxiters) @SciMLMessage( lazy"common maxiters argument is currently not used by $(optimizer). Set number of iterations via optimizer specific keyword arguments.", - cache.verbose, :unsupported_kwargs + opt_verbose, :unsupported_kwargs ) end return optimizer diff --git a/lib/OptimizationMOI/src/moi.jl b/lib/OptimizationMOI/src/moi.jl index 442c559d3..e30f9fe99 100644 --- a/lib/OptimizationMOI/src/moi.jl +++ b/lib/OptimizationMOI/src/moi.jl @@ -101,7 +101,6 @@ function SciMLBase.__solve(cache::MOIOptimizationCache) maxiters = OptimizationBase._check_and_convert_maxiters(cache.solver_args.maxiters) maxtime = OptimizationBase._check_and_convert_maxtime(cache.solver_args.maxtime) opt_setup = __map_optimizer_args( - cache, cache.opt; abstol = cache.solver_args.abstol, reltol = cache.solver_args.reltol, diff --git a/lib/OptimizationMOI/src/nlp.jl b/lib/OptimizationMOI/src/nlp.jl index 87d7bc97c..18a06e280 100644 --- a/lib/OptimizationMOI/src/nlp.jl +++ b/lib/OptimizationMOI/src/nlp.jl @@ -555,7 +555,6 @@ function SciMLBase.__solve(cache::MOIOptimizationNLPCache) maxiters = OptimizationBase._check_and_convert_maxiters(cache.solver_args.maxiters) maxtime = OptimizationBase._check_and_convert_maxtime(cache.solver_args.maxtime) opt_setup = __map_optimizer_args( - cache, cache.opt; abstol = cache.solver_args.abstol, reltol = cache.solver_args.reltol, diff --git a/lib/OptimizationMOI/test/core_tests.jl b/lib/OptimizationMOI/test/core_tests.jl index 9cc5baac8..f3c23da5f 100644 --- a/lib/OptimizationMOI/test/core_tests.jl +++ b/lib/OptimizationMOI/test/core_tests.jl @@ -267,6 +267,26 @@ end end end +@testset "verbose kwarg" begin + rosenbrock(x, p) = (p[1] - x[1])^2 + p[2] * (x[2] - x[1]^2)^2 + optprob = OptimizationFunction(rosenbrock, AutoZygote()) + prob = OptimizationProblem(optprob, zeros(2), [1.0, 100.0]; lb = [-5.0, -5.0], ub = [5.0, 5.0]) + + # `verbose` must be consumed as a verbosity setting, not forwarded as a raw + # MOI optimizer attribute. + sol = solve(prob, Ipopt.Optimizer(); verbose = OptimizationVerbosity()) + @test sol.retcode == ReturnCode.Success + + # Passing a common tolerance triggers the `:unsupported_kwargs` message path, + # which previously read a nonexistent `cache.verbose` field and threw. + sol = solve(prob, Ipopt.Optimizer(); abstol = 1.0e-8, verbose = OptimizationVerbosity()) + @test sol.retcode == ReturnCode.Success + + # A `Bool` verbosity must also be accepted. + sol = solve(prob, Ipopt.Optimizer(); abstol = 1.0e-8, verbose = true) + @test sol.retcode == ReturnCode.Success +end + @testset "MOI" begin @parameters c = 0.0 @variables x[1:2] = [0.0, 0.0] [bounds = ([c, c], [Inf, Inf])] From cff51dd752ccd2d88569e74f028fd824bd36990d Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Thu, 25 Jun 2026 05:39:45 -0400 Subject: [PATCH 2/2] Fix OptimizationMOI Aqua QA: allowed piracy + ReverseDiff compat Two pre-existing Aqua failures in the OptimizationMOI QA suite: - Declare the SciMLBase solver-trait methods defined on the MathOptInterface optimizer types (`__init`, `allowsbounds`, `requiresgradient`, ...) as allowed via `piracies = (treat_as_own = [...],)`. These are an intentional interface extension, not piracy. - Add the missing `[compat]` bound for `ReverseDiff` (a test-only `[extras]` dependency), which Aqua's deps_compat check requires. Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01RtJAi7bALH5XkakjS6Yb6j --- lib/OptimizationMOI/Project.toml | 1 + lib/OptimizationMOI/test/qa/qa.jl | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/OptimizationMOI/Project.toml b/lib/OptimizationMOI/Project.toml index e54a4251c..950913362 100644 --- a/lib/OptimizationMOI/Project.toml +++ b/lib/OptimizationMOI/Project.toml @@ -37,6 +37,7 @@ ModelingToolkitBase = "1" NLopt = "1" OptimizationBase = "5.1" Reexport = "1.2" +ReverseDiff = "1" SciMLBase = "2.130, 3" SciMLStructures = "1" SparseArrays = "1.6" diff --git a/lib/OptimizationMOI/test/qa/qa.jl b/lib/OptimizationMOI/test/qa/qa.jl index 337143cb7..67241f196 100644 --- a/lib/OptimizationMOI/test/qa/qa.jl +++ b/lib/OptimizationMOI/test/qa/qa.jl @@ -2,7 +2,17 @@ using OptimizationMOI, Aqua, JET using Test @testset "Aqua" begin - Aqua.test_all(OptimizationMOI) + # OptimizationMOI deliberately extends SciMLBase's solver-trait interface onto the + # MathOptInterface optimizer types, so those methods are intentional, not piracy. + Aqua.test_all( + OptimizationMOI; + piracies = ( + treat_as_own = [ + OptimizationMOI.MOI.AbstractOptimizer, + OptimizationMOI.MOI.OptimizerWithAttributes, + ], + ) + ) end @testset "JET static analysis" begin