@@ -11,7 +11,162 @@ export IpoptOptimizer
1111
1212const DenseOrSparse{T} = Union{Matrix{T}, SparseMatrixCSC{T}}
1313
14- struct IpoptOptimizer end
14+ """
15+ IpoptOptimizer(; kwargs...)
16+
17+ Optimizer using the Interior Point Optimizer (Ipopt) for nonlinear optimization.
18+
19+ Ipopt is designed to find (local) solutions of mathematical optimization problems of the form:
20+
21+ min f(x)
22+ s.t. g_L ≤ g(x) ≤ g_U
23+ x_L ≤ x ≤ x_U
24+
25+ where f(x) and g(x) are twice continuously differentiable functions.
26+
27+ # Common Interface Arguments
28+
29+ The following common optimization arguments can be passed to `solve`:
30+ - `reltol`: Overrides the Ipopt `tol` option (desired convergence tolerance)
31+ - `maxiters`: Overrides the Ipopt `max_iter` option (maximum iterations)
32+ - `maxtime`: Overrides the Ipopt `max_wall_time` option (maximum wall clock time)
33+ - `verbose`: Overrides the Ipopt `print_level` option (0 for silent, 5 for default, up to 12 for maximum verbosity)
34+
35+ # Keyword Arguments
36+
37+ ## Termination Options
38+ - `acceptable_tol::Float64 = 1e-6`: Acceptable convergence tolerance (relative)
39+ - `acceptable_iter::Int = 15`: Number of acceptable iterations before termination
40+ - `dual_inf_tol::Float64 = 1.0`: Desired threshold for dual infeasibility
41+ - `constr_viol_tol::Float64 = 1e-4`: Desired threshold for constraint violation
42+ - `compl_inf_tol::Float64 = 1e-4`: Desired threshold for complementarity conditions
43+
44+ ## Output Options
45+ - `print_timing_statistics::String = "no"`: Print timing statistics at end of optimization
46+ - `print_info_string::String = "no"`: Print info string with algorithm details
47+
48+ ## Linear Solver Options
49+ - `linear_solver::String = "mumps"`: Linear solver to use (mumps, ma27, ma57, ma86, ma97, pardiso, wsmp, etc.)
50+ - `linear_system_scaling::String = "none"`: Method for scaling linear system (none, mc19, slack-based)
51+ - `hsllib::String = ""`: Path to HSL library (if using HSL solvers)
52+ - `pardisolib::String = ""`: Path to Pardiso library (if using Pardiso)
53+ - `linear_scaling_on_demand::String = "yes"`: Enable scaling on demand for linear systems
54+
55+ ## NLP Scaling Options
56+ - `nlp_scaling_method::String = "gradient-based"`: Scaling method for NLP (none, user-scaling, gradient-based, equilibration-based)
57+ - `nlp_scaling_max_gradient::Float64 = 100.0`: Maximum gradient after scaling
58+ - `honor_original_bounds::String = "no"`: Honor original variable bounds after scaling
59+ - `check_derivatives_for_naninf::String = "no"`: Check derivatives for NaN/Inf values
60+
61+ ## Barrier Parameter Options
62+ - `mu_strategy::String = "monotone"`: Update strategy for barrier parameter (monotone, adaptive)
63+ - `mu_oracle::String = "quality-function"`: Oracle for adaptive mu strategy
64+ - `mu_init::Float64 = 0.1`: Initial value for barrier parameter
65+ - `adaptive_mu_globalization::String = "obj-constr-filter"`: Globalization strategy for adaptive mu
66+
67+ ## Warm Start Options
68+ - `warm_start_init_point::String = "no"`: Use warm start from previous solution
69+
70+ ## Hessian Options
71+ - `hessian_approximation::String = "exact"`: How to approximate the Hessian (exact, limited-memory)
72+ - `limited_memory_max_history::Int = 6`: History size for limited-memory Hessian approximation
73+ - `limited_memory_update_type::String = "bfgs"`: Quasi-Newton update formula for limited-memory approximation (bfgs, sr1)
74+
75+ ## Line Search Options
76+ - `accept_every_trial_step::String = "no"`: Accept every trial step (disables line search)
77+ - `line_search_method::String = "filter"`: Line search method (filter, penalty, cg-penalty)
78+
79+ ## Restoration Phase Options
80+ - `expect_infeasible_problem::String = "no"`: Enable if problem is expected to be infeasible
81+
82+ ## Additional Options
83+ - `additional_options::Dict{String, Any} = Dict()`: Dictionary to set any other Ipopt option not explicitly listed above.
84+ See https://coin-or.github.io/Ipopt/OPTIONS.html for the full list of available options.
85+
86+ # Examples
87+
88+ ```julia
89+ using Optimization, OptimizationIpopt
90+
91+ # Basic usage with default settings
92+ opt = IpoptOptimizer()
93+
94+ # Customized settings
95+ opt = IpoptOptimizer(
96+ linear_solver = "ma57", # needs HSL solvers configured
97+ nlp_scaling_method = "equilibration-based",
98+ hessian_approximation = "limited-memory",
99+ additional_options = Dict(
100+ "alpha_for_y" => "primal",
101+ "recalc_y" => "yes"
102+ )
103+ )
104+
105+ # Solve with common interface arguments
106+ result = solve(prob, opt;
107+ reltol = 1e-8, # Sets Ipopt's tol
108+ maxiters = 5000, # Sets Ipopt's max_iter
109+ maxtime = 300.0, # Sets Ipopt's max_wall_time (in seconds)
110+ verbose = 3 # Sets Ipopt's print_level
111+ )
112+ ```
113+
114+ # References
115+
116+ For complete documentation of all Ipopt options, see:
117+ https://coin-or.github.io/Ipopt/OPTIONS.html
118+ """
119+ @kwdef struct IpoptOptimizer
120+ # Most common Ipopt-specific options (excluding common interface options)
121+
122+ # Termination
123+ acceptable_tol:: Float64 = 1e-6
124+ acceptable_iter:: Int = 15
125+ dual_inf_tol:: Float64 = 1.0
126+ constr_viol_tol:: Float64 = 1e-4
127+ compl_inf_tol:: Float64 = 1e-4
128+
129+ # Output options
130+ print_timing_statistics:: String = " no"
131+ print_info_string:: String = " no"
132+
133+ # Linear solver
134+ linear_solver:: String = " mumps"
135+ linear_system_scaling:: String = " none"
136+ hsllib:: String = " "
137+ pardisolib:: String = " "
138+ linear_scaling_on_demand = " yes"
139+
140+ # NLP options
141+ nlp_scaling_method:: String = " gradient-based"
142+ nlp_scaling_max_gradient:: Float64 = 100.0
143+ honor_original_bounds:: String = " no"
144+ check_derivatives_for_naninf:: String = " no"
145+
146+ # Barrier parameter
147+ mu_strategy:: String = " monotone"
148+ mu_oracle:: String = " quality-function"
149+ mu_init:: Float64 = 0.1
150+ adaptive_mu_globalization:: String = " obj-constr-filter"
151+
152+ # Warm start
153+ warm_start_init_point:: String = " no"
154+
155+ # Hessian approximation
156+ hessian_approximation:: String = " exact"
157+ limited_memory_max_history:: Int = 6
158+ limited_memory_update_type:: String = " bfgs"
159+
160+ # Line search
161+ accept_every_trial_step:: String = " no"
162+ line_search_method:: String = " filter"
163+
164+ # Restoration phase
165+ expect_infeasible_problem:: String = " no"
166+
167+ # Additional options for any other Ipopt parameters
168+ additional_options:: Dict{String, Any} = Dict {String, Any} ()
169+ end
15170
16171@static if isdefined (SciMLBase, :supports_opt_cache_interface )
17172 function SciMLBase. supports_opt_cache_interface (alg:: IpoptOptimizer )
@@ -53,11 +208,9 @@ function __map_optimizer_args(cache,
53208 maxtime:: Union{Number, Nothing} = nothing ,
54209 abstol:: Union{Number, Nothing} = nothing ,
55210 reltol:: Union{Number, Nothing} = nothing ,
56- hessian_approximation = " exact" ,
57211 verbose = false ,
58- progress = false ,
59- callback = nothing ,
60- kwargs... )
212+ progress:: Bool = false ,
213+ callback = nothing )
61214 jacobian_sparsity = jacobian_structure (cache)
62215 hessian_sparsity = hessian_lagrangian_structure (cache)
63216
@@ -103,37 +256,53 @@ function __map_optimizer_args(cache,
103256 eval_jac_g,
104257 eval_h
105258 )
106- progress_callback = IpoptProgressLogger (cache. progress, cache, prob)
259+
260+ # Set up progress callback
261+ progress_callback = IpoptProgressLogger (progress, callback, prob, cache. n, cache. num_cons, maxiters, cache. iterations)
107262 intermediate = (args... ) -> progress_callback (args... )
108263 Ipopt. SetIntermediateCallback (prob, intermediate)
109264
110- if ! isnothing (maxiters)
111- Ipopt. AddIpoptIntOption (prob, " max_iter" , maxiters)
112- end
113- if ! isnothing (maxtime)
114- Ipopt. AddIpoptNumOption (prob, " max_wall_time" , float (maxtime))
265+ # Apply all options from struct using reflection and type dispatch
266+ for field in propertynames (opt)
267+ field == :additional_options && continue # Skip the dict field
268+
269+ field_str = string (field)
270+ value = getproperty (opt, field)
271+
272+ # Apply option based on type
273+ if value isa Int
274+ Ipopt. AddIpoptIntOption (prob, field_str, value)
275+ elseif value isa Float64
276+ Ipopt. AddIpoptNumOption (prob, field_str, value)
277+ elseif value isa String
278+ Ipopt. AddIpoptStrOption (prob, field_str, value)
279+ end
115280 end
116- if ! isnothing (reltol)
117- Ipopt. AddIpoptNumOption (prob, " tol" , reltol)
281+
282+ # Apply additional options with type dispatch
283+ for (key, value) in opt. additional_options
284+ if value isa Int
285+ Ipopt. AddIpoptIntOption (prob, key, value)
286+ elseif value isa Float64
287+ Ipopt. AddIpoptNumOption (prob, key, float (value))
288+ elseif value isa String
289+ Ipopt. AddIpoptStrOption (prob, key, value)
290+ else
291+ error (" Unsupported option type $(typeof (value)) for option $key . Must be Int, Float64, or String" )
292+ end
118293 end
294+
295+ # Override with common interface arguments if provided
296+ ! isnothing (reltol) && Ipopt. AddIpoptNumOption (prob, " tol" , reltol)
297+ ! isnothing (maxiters) && Ipopt. AddIpoptIntOption (prob, " max_iter" , maxiters)
298+ ! isnothing (maxtime) && Ipopt. AddIpoptNumOption (prob, " max_wall_time" , Float64 (maxtime))
299+
300+ # Handle verbose override
119301 if verbose isa Bool
120302 Ipopt. AddIpoptIntOption (prob, " print_level" , verbose * 5 )
121- else
303+ elseif verbose isa Int
122304 Ipopt. AddIpoptIntOption (prob, " print_level" , verbose)
123305 end
124- Ipopt. AddIpoptStrOption (prob, " hessian_approximation" , hessian_approximation)
125-
126- for kw in pairs (kwargs)
127- if kw[2 ] isa Int
128- Ipopt. AddIpoptIntOption (prob, string (kw[1 ]), kw[2 ])
129- elseif kw[2 ] isa Float64
130- Ipopt. AddIpoptNumOption (prob, string (kw[1 ]), kw[2 ])
131- elseif kw[2 ] isa String
132- Ipopt. AddIpoptStrOption (prob, string (kw[1 ]), kw[2 ])
133- else
134- error (" Keyword argument type $(typeof (kw[2 ])) not recognized" )
135- end
136- end
137306
138307 return prob
139308end
@@ -173,7 +342,9 @@ function SciMLBase.__solve(cache::IpoptCache)
173342 reltol = cache. solver_args. reltol,
174343 maxiters = maxiters,
175344 maxtime = maxtime,
176- cache. solver_args... )
345+ verbose = get (cache. solver_args, :verbose , false ),
346+ progress = cache. progress,
347+ callback = get (cache. solver_args, :callback , nothing ))
177348
178349 opt_setup. x .= cache. reinit_cache. u0
179350
@@ -191,7 +362,7 @@ function SciMLBase.__solve(cache::IpoptCache)
191362 minimizer = opt_setup. x
192363
193364 stats = Optimization. OptimizationStats (; time = time () - start_time,
194- iterations = cache. iterations, fevals = cache. f_calls, gevals = cache. f_grad_calls)
365+ iterations = cache. iterations[] , fevals = cache. f_calls, gevals = cache. f_grad_calls)
195366
196367 finalize (opt_setup)
197368
@@ -210,12 +381,14 @@ function SciMLBase.__init(prob::OptimizationProblem,
210381 maxtime:: Union{Number, Nothing} = nothing ,
211382 abstol:: Union{Number, Nothing} = nothing ,
212383 reltol:: Union{Number, Nothing} = nothing ,
384+ progress:: Bool = false ,
213385 kwargs... )
214386 cache = IpoptCache (prob, opt;
215387 maxiters,
216388 maxtime,
217389 abstol,
218390 reltol,
391+ progress,
219392 kwargs...
220393 )
221394 cache. reinit_cache. u0 .= prob. u0
0 commit comments