From f938a95aed2219f28a432f346407148e49ed42cf Mon Sep 17 00:00:00 2001 From: ChrisRackauckas Date: Wed, 13 Aug 2025 12:28:38 -0400 Subject: [PATCH] Fix maxiters warning by implementing solver-specific parameter mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add _set_maxiters! function to map common maxiters to solver-specific parameters - Support major MOI solvers: Ipopt, Gurobi, CPLEX, SCIP, Mosek, OSQP, ECOS, SCS, COSMO - Implement generic fallback that tries common parameter names - Add comprehensive tests for the new functionality - Resolves SciML/Optimization.jl#844 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lib/OptimizationMOI/src/OptimizationMOI.jl | 52 +++++++++++++++++++++- lib/OptimizationMOI/test/runtests.jl | 37 +++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/lib/OptimizationMOI/src/OptimizationMOI.jl b/lib/OptimizationMOI/src/OptimizationMOI.jl index 5655eed01..b93b3c89c 100644 --- a/lib/OptimizationMOI/src/OptimizationMOI.jl +++ b/lib/OptimizationMOI/src/OptimizationMOI.jl @@ -60,6 +60,56 @@ function _create_new_optimizer(opt::MOI.AbstractOptimizer) return opt_setup end +""" + _set_maxiters!(optimizer, maxiters) + +Sets the maximum number of iterations for the optimizer using solver-specific parameter names. +Supports common MOI solvers including Ipopt, Gurobi, CPLEX, and SCIP. +""" +function _set_maxiters!(optimizer, maxiters::Number) + optimizer_name = string(typeof(optimizer)) + + # Try to set maxiters based on common solver patterns + try + if contains(optimizer_name, "Ipopt") + MOI.set(optimizer, MOI.RawOptimizerAttribute("max_iter"), Int(maxiters)) + elseif contains(optimizer_name, "Gurobi") + MOI.set(optimizer, MOI.RawOptimizerAttribute("IterationLimit"), Int(maxiters)) + elseif contains(optimizer_name, "CPLEX") || contains(optimizer_name, "Cplex") + MOI.set(optimizer, MOI.RawOptimizerAttribute("CPX_PARAM_ITLIM"), Int(maxiters)) + elseif contains(optimizer_name, "SCIP") || contains(optimizer_name, "Scip") + MOI.set(optimizer, MOI.RawOptimizerAttribute("limits/iterations"), Int(maxiters)) + elseif contains(optimizer_name, "Mosek") || contains(optimizer_name, "MOSEK") + MOI.set(optimizer, MOI.RawOptimizerAttribute("MSK_IPAR_INTPNT_MAX_ITERATIONS"), Int(maxiters)) + elseif contains(optimizer_name, "OSQP") + MOI.set(optimizer, MOI.RawOptimizerAttribute("max_iter"), Int(maxiters)) + elseif contains(optimizer_name, "ECOS") + MOI.set(optimizer, MOI.RawOptimizerAttribute("maxit"), Int(maxiters)) + elseif contains(optimizer_name, "SCS") + MOI.set(optimizer, MOI.RawOptimizerAttribute("max_iters"), Int(maxiters)) + elseif contains(optimizer_name, "COSMO") + MOI.set(optimizer, MOI.RawOptimizerAttribute("max_iter"), Int(maxiters)) + else + # Generic fallback - try common parameter names + for param_name in ["max_iter", "maxiter", "IterationLimit", "max_iterations"] + try + MOI.set(optimizer, MOI.RawOptimizerAttribute(param_name), Int(maxiters)) + return # Success, exit early + catch + continue # Try next parameter name + end + end + # If all attempts fail, show warning with guidance + @warn "common maxiters argument could not be mapped for $(typeof(optimizer)). " * + "Set number of iterations via optimizer specific keyword arguments." + end + catch e + # Catch any errors during parameter setting and show informative warning + @warn "Failed to set maxiters parameter for $(typeof(optimizer)): $(e). " * + "Set number of iterations via optimizer specific keyword arguments." + end +end + function __map_optimizer_args(cache, opt::Union{MOI.AbstractOptimizer, MOI.OptimizerWithAttributes }; @@ -82,7 +132,7 @@ function __map_optimizer_args(cache, @warn "common abstol argument is currently not used by $(optimizer). Set tolerances via optimizer specific keyword arguments." end if !isnothing(maxiters) - @warn "common maxiters argument is currently not used by $(optimizer). Set number of iterations via optimizer specific keyword arguments." + _set_maxiters!(optimizer, maxiters) end return optimizer end diff --git a/lib/OptimizationMOI/test/runtests.jl b/lib/OptimizationMOI/test/runtests.jl index ff6d2fbbf..a23b102a1 100644 --- a/lib/OptimizationMOI/test/runtests.jl +++ b/lib/OptimizationMOI/test/runtests.jl @@ -276,3 +276,40 @@ end prob = OptimizationProblem(optprob, x0, _p, lcons = [1.0, 0.5], ucons = [1.0, 0.5]) sol = solve(prob, Ipopt.Optimizer()) end + +@testset "common maxiters interface" begin + # Test that the common maxiters interface works without warnings + rosenbrock(x, p) = (p[1] - x[1])^2 + p[2] * (x[2] - x[1]^2)^2 + x0 = zeros(2) + _p = [1.0, 100.0] + + optprob = OptimizationFunction(rosenbrock, Optimization.AutoZygote()) + prob = OptimizationProblem(optprob, x0, _p) + + # Test with Ipopt using maxiters parameter + @testset "Ipopt maxiters" begin + # This should not produce a warning and should respect the iteration limit + sol = solve(prob, Ipopt.Optimizer(); maxiters = 5, print_level = 0) + # Should terminate due to iteration limit + @test sol.stats.iterations <= 5 + end + + # Test with cache interface + @testset "Cache interface maxiters" begin + cache = init(prob, Ipopt.Optimizer(); maxiters = 3, print_level = 0) + sol = solve!(cache) + @test sol.stats.iterations <= 3 + end + + # Test that unknown solver fallback works gracefully + @testset "Generic fallback" begin + # Mock optimizer that doesn't match any known pattern + struct MockOptimizer <: MathOptInterface.AbstractOptimizer end + + # This should not error, but may show a warning for unknown solver + mock_opt = MockOptimizer() + # We can't actually solve with this mock optimizer, but we can test + # that the parameter setting doesn't crash + @test_nowarn OptimizationMOI._set_maxiters!(mock_opt, 10) + end +end