From 2e2325651bbac7230c09410b76c7493866532029 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sat, 2 Aug 2025 13:29:54 +0530 Subject: [PATCH 01/16] Update NLPModelsIpopt.jl --- src/NLPModelsIpopt.jl | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/NLPModelsIpopt.jl b/src/NLPModelsIpopt.jl index ee85159..a75806f 100644 --- a/src/NLPModelsIpopt.jl +++ b/src/NLPModelsIpopt.jl @@ -112,6 +112,42 @@ end set_callbacks(nlp::AbstractNLPModel) Return the set of functions needed to instantiate an `IpoptProblem`. + +## Callback Usage Example + +You can use a callback to monitor the optimization process. The callback must have the signature: + + function my_callback(alg_mod, iter_count, problem_ptr, args...) + +The `problem_ptr` argument is required to access the current iterate and constraint violations using `Ipopt.GetIpoptCurrentIterate` and `Ipopt.GetIpoptCurrentViolations`. + +`Ipopt.GetIpoptCurrentIterate(problem_ptr)` returns: + - `x`: current primal variables + - `z_L`: current multipliers for lower bounds + - `z_U`: current multipliers for upper bounds + - `g`: current constraint values + - `lambda`: current multipliers for constraints + +`Ipopt.GetIpoptCurrentViolations(problem_ptr)` returns: + - `constr_viol`: constraint violation + - `dual_inf`: dual infeasibility + - `compl`: complementarity + +Example: + +```julia +function my_callback(alg_mod, iter_count, problem_ptr, args...) + # Get current iterate (primal and dual variables) + x, z_L, z_U, g, lambda = Ipopt.GetIpoptCurrentIterate(problem_ptr) + # Get current constraint violations + constr_viol, dual_inf, compl = Ipopt.GetIpoptCurrentViolations(problem_ptr) + @info "Iter $iter_count: primal = $x, dual = $lambda, constr_viol = $constr_viol, dual_inf = $dual_inf, compl = $compl" + return true # return false to stop +end + +# Pass the callback to ipopt using the `callback` keyword: +stats = ipopt(nlp, callback = my_callback) +``` """ function set_callbacks(nlp::AbstractNLPModel) eval_f(x) = obj(nlp, x) From 45606fd93dd65eb4dd7d010a837db36f68b6a2ee Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Tue, 5 Aug 2025 13:32:39 +0530 Subject: [PATCH 02/16] Update NLPModelsIpopt.jl --- src/NLPModelsIpopt.jl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/NLPModelsIpopt.jl b/src/NLPModelsIpopt.jl index a75806f..8f58dc3 100644 --- a/src/NLPModelsIpopt.jl +++ b/src/NLPModelsIpopt.jl @@ -113,22 +113,20 @@ end Return the set of functions needed to instantiate an `IpoptProblem`. -## Callback Usage Example - You can use a callback to monitor the optimization process. The callback must have the signature: function my_callback(alg_mod, iter_count, problem_ptr, args...) The `problem_ptr` argument is required to access the current iterate and constraint violations using `Ipopt.GetIpoptCurrentIterate` and `Ipopt.GetIpoptCurrentViolations`. -`Ipopt.GetIpoptCurrentIterate(problem_ptr)` returns: +-`Ipopt.GetIpoptCurrentIterate(problem_ptr)` returns: - `x`: current primal variables - `z_L`: current multipliers for lower bounds - `z_U`: current multipliers for upper bounds - `g`: current constraint values - `lambda`: current multipliers for constraints -`Ipopt.GetIpoptCurrentViolations(problem_ptr)` returns: +- `Ipopt.GetIpoptCurrentViolations(problem_ptr)` returns: - `constr_viol`: constraint violation - `dual_inf`: dual infeasibility - `compl`: complementarity @@ -147,6 +145,11 @@ end # Pass the callback to ipopt using the `callback` keyword: stats = ipopt(nlp, callback = my_callback) + +# For advanced access to the underlying problem struct: +nlp = ADNLPModel(...) +solver = IpoptSolver(nlp) +stats = solve!(solver, nlp, callback = my_callback) ``` """ function set_callbacks(nlp::AbstractNLPModel) From e7e4bb75f8b8f14a8d7e5c1a9d4a86f06742d1fa Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Thu, 7 Aug 2025 00:08:34 +0530 Subject: [PATCH 03/16] iter_count --- src/NLPModelsIpopt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NLPModelsIpopt.jl b/src/NLPModelsIpopt.jl index df031c7..acdd2d4 100644 --- a/src/NLPModelsIpopt.jl +++ b/src/NLPModelsIpopt.jl @@ -149,7 +149,7 @@ function my_callback(alg_mod, iter_count, problem_ptr, args...) x, z_L, z_U, g, lambda = Ipopt.GetIpoptCurrentIterate(problem_ptr) # Get current constraint violations constr_viol, dual_inf, compl = Ipopt.GetIpoptCurrentViolations(problem_ptr) - @info "Iter $iter_count: primal = $x, dual = $lambda, constr_viol = $constr_viol, dual_inf = $dual_inf, compl = $compl" + @info "Iter \$iter_count: primal = \$x, dual = \$lambda, constr_viol = \$constr_viol, dual_inf = \$dual_inf, compl = \$compl" return true # return false to stop end From 114ee14cdbb22e09c98ef96cb5ec370871f090bd Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 8 Aug 2025 20:44:25 +0530 Subject: [PATCH 04/16] tutorial --- docs/src/tutorial.md | 91 +++++++++++++++++++++++++++++++++++++++++++ src/NLPModelsIpopt.jl | 47 +++++----------------- 2 files changed, 100 insertions(+), 38 deletions(-) diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index 2b6c508..642824a 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -62,6 +62,97 @@ Here is an example using the constrained problem solve: stats.solver_specific[:internal_msg] ``` +## Monitoring optimization with callbacks + +You can monitor the optimization process using a callback function. The callback allows you to access the current iterate and constraint violations at each iteration, which is useful for custom stopping criteria, logging, or real-time analysis. + +### Callback signature + +The callback function must have the following signature: + +```julia +function my_callback(alg_mod, iter_count, problem_ptr, args...) + # Your custom code here + return true # return false to stop optimization +end +``` + +### Accessing current iterate information + +The `problem_ptr` argument provides access to the current state of the optimization: + +- `Ipopt.GetIpoptCurrentIterate(problem_ptr)` returns: + - `x`: current primal variables + - `z_L`: current multipliers for lower bounds + - `z_U`: current multipliers for upper bounds + - `g`: current constraint values + - `lambda`: current multipliers for constraints + +- `Ipopt.GetIpoptCurrentViolations(problem_ptr)` returns: + - `constr_viol`: constraint violation + - `dual_inf`: dual infeasibility + - `compl`: complementarity + +### Example usage + +Here's a complete example showing how to use callbacks to monitor the optimization: + +```@example ex4 +using ADNLPModels, NLPModelsIpopt + +# Define a callback function to monitor iterations +function my_callback(alg_mod, iter_count, problem_ptr, args...) + # Get current iterate (primal and dual variables) + x, z_L, z_U, g, lambda = Ipopt.GetIpoptCurrentIterate(problem_ptr) + # Get current constraint violations + constr_viol, dual_inf, compl = Ipopt.GetIpoptCurrentViolations(problem_ptr) + + # Log iteration information + println("Iteration $iter_count:") + println(" x = ", x) + println(" Constraint violation = ", constr_viol) + println(" Dual infeasibility = ", dual_inf) + println(" Complementarity = ", compl) + + # Return true to continue, false to stop + return iter_count < 5 # Stop after 5 iterations for this example +end + +# Create and solve a problem with callback +nlp = ADNLPModel(x -> (x[1] - 1)^2 + 100 * (x[2] - x[1]^2)^2, [-1.2; 1.0]) +stats = ipopt(nlp, callback = my_callback, print_level = 0) +``` + +You can also use callbacks with the advanced solver interface: + +```@example ex4 +# Advanced usage with IpoptSolver +solver = IpoptSolver(nlp) +stats = solve!(solver, nlp, callback = my_callback, print_level = 0) +``` + +### Custom stopping criteria + +Callbacks are particularly useful for implementing custom stopping criteria: + +```@example ex4 +function custom_stopping_callback(alg_mod, iter_count, problem_ptr, args...) + x, z_L, z_U, g, lambda = Ipopt.GetIpoptCurrentIterate(problem_ptr) + constr_viol, dual_inf, compl = Ipopt.GetIpoptCurrentViolations(problem_ptr) + + # Custom stopping criterion: stop if x[1] gets close to 1 + if abs(x[1] - 1.0) < 0.1 + println("Custom stopping criterion met at iteration $iter_count") + return false # Stop optimization + end + + return true # Continue optimization +end + +nlp = ADNLPModel(x -> (x[1] - 1)^2 + 100 * (x[2] - x[1]^2)^2, [-1.2; 1.0]) +stats = ipopt(nlp, callback = custom_stopping_callback, print_level = 0) +``` + ## Manual input In this section, we work through an example where we specify the problem and its derivatives manually. For this, we need to implement the following `NLPModel` API methods: diff --git a/src/NLPModelsIpopt.jl b/src/NLPModelsIpopt.jl index acdd2d4..f39f039 100644 --- a/src/NLPModelsIpopt.jl +++ b/src/NLPModelsIpopt.jl @@ -123,44 +123,15 @@ end Return the set of functions needed to instantiate an `IpoptProblem`. -You can use a callback to monitor the optimization process. The callback must have the signature: - - function my_callback(alg_mod, iter_count, problem_ptr, args...) - -The `problem_ptr` argument is required to access the current iterate and constraint violations using `Ipopt.GetIpoptCurrentIterate` and `Ipopt.GetIpoptCurrentViolations`. - --`Ipopt.GetIpoptCurrentIterate(problem_ptr)` returns: - - `x`: current primal variables - - `z_L`: current multipliers for lower bounds - - `z_U`: current multipliers for upper bounds - - `g`: current constraint values - - `lambda`: current multipliers for constraints - -- `Ipopt.GetIpoptCurrentViolations(problem_ptr)` returns: - - `constr_viol`: constraint violation - - `dual_inf`: dual infeasibility - - `compl`: complementarity - -Example: - -```julia -function my_callback(alg_mod, iter_count, problem_ptr, args...) - # Get current iterate (primal and dual variables) - x, z_L, z_U, g, lambda = Ipopt.GetIpoptCurrentIterate(problem_ptr) - # Get current constraint violations - constr_viol, dual_inf, compl = Ipopt.GetIpoptCurrentViolations(problem_ptr) - @info "Iter \$iter_count: primal = \$x, dual = \$lambda, constr_viol = \$constr_viol, dual_inf = \$dual_inf, compl = \$compl" - return true # return false to stop -end - -# Pass the callback to ipopt using the `callback` keyword: -stats = ipopt(nlp, callback = my_callback) - -# For advanced access to the underlying problem struct: -nlp = ADNLPModel(...) -solver = IpoptSolver(nlp) -stats = solve!(solver, nlp, callback = my_callback) -``` +This function creates the callback functions that Ipopt needs to evaluate: +- the objective function +- the constraint function (if any) +- the objective gradient +- the constraint Jacobian (if any) +- the Hessian of the Lagrangian + +For information on using callbacks to monitor the optimization process, +see the tutorial documentation. """ function set_callbacks(nlp::AbstractNLPModel) eval_f(x) = obj(nlp, x) From 9cb2bc0904b189562cdfbf7b59949a28d49c2cd9 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Mon, 11 Aug 2025 10:16:03 +0530 Subject: [PATCH 05/16] Update src/NLPModelsIpopt.jl Co-authored-by: Tangi Migot --- src/NLPModelsIpopt.jl | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/NLPModelsIpopt.jl b/src/NLPModelsIpopt.jl index f39f039..d926d17 100644 --- a/src/NLPModelsIpopt.jl +++ b/src/NLPModelsIpopt.jl @@ -122,16 +122,6 @@ end set_callbacks(nlp::AbstractNLPModel) Return the set of functions needed to instantiate an `IpoptProblem`. - -This function creates the callback functions that Ipopt needs to evaluate: -- the objective function -- the constraint function (if any) -- the objective gradient -- the constraint Jacobian (if any) -- the Hessian of the Lagrangian - -For information on using callbacks to monitor the optimization process, -see the tutorial documentation. """ function set_callbacks(nlp::AbstractNLPModel) eval_f(x) = obj(nlp, x) From 8ecc37989c5083449f51a6b3f3fcb6eceeb494d6 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Mon, 11 Aug 2025 19:48:19 +0530 Subject: [PATCH 06/16] docs build --- Project.toml | 1 + build/NLPModelsIpopt.jl | 373 ++++++++++++++++++++++++++++++++++++++++ docs/Project.toml | 1 + docs/src/tutorial.md | 63 +++---- 4 files changed, 408 insertions(+), 30 deletions(-) create mode 100644 build/NLPModelsIpopt.jl diff --git a/Project.toml b/Project.toml index d590270..98ab74a 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,7 @@ uuid = "f4238b75-b362-5c4c-b852-0801c9a21d71" version = "0.10.4" [deps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" NLPModelsModifiers = "e01155f1-5c6f-4375-a9d8-616dd036575f" diff --git a/build/NLPModelsIpopt.jl b/build/NLPModelsIpopt.jl new file mode 100644 index 0000000..f39f039 --- /dev/null +++ b/build/NLPModelsIpopt.jl @@ -0,0 +1,373 @@ +module NLPModelsIpopt + +export ipopt, IpoptSolver, reset!, solve! + +using NLPModels, Ipopt, SolverCore +using NLPModelsModifiers: FeasibilityFormNLS + +const ipopt_statuses = Dict( + 0 => :first_order, + 1 => :acceptable, + 2 => :infeasible, + 3 => :small_step, + #4 => Diverging iterates + 5 => :user, + #6 => Feasible point found + -1 => :max_iter, + #-2 => Restoration failed + #-3 => Error in step computation + -4 => :max_time, # Maximum cputime exceeded + -5 => :max_time, # Maximum walltime exceeded + #-10 => Not enough degress of freedom + #-11 => Invalid problem definition + #-12 => Invalid option + #-13 => Invalid number detected + -100 => :exception, # Unrecoverable exception + -101 => :exception, # NonIpopt exception thrown + -102 => :exception, # Insufficient memory + -199 => :exception, # Internal error +) + +const ipopt_internal_statuses = Dict( + 0 => :Solve_Succeeded, + 1 => :Solved_To_Acceptable_Level, + 2 => :Infeasible_Problem_Detected, + 3 => :Search_Direction_Becomes_Too_Small, + 4 => :Diverging_Iterates, + 5 => :User_Requested_Stop, + 6 => :Feasible_Point_Found, + -1 => :Maximum_Iterations_Exceeded, + -2 => :Restoration_Failed, + -3 => :Error_In_Step_Computation, + -4 => :Maximum_CpuTime_Exceeded, + -5 => :Maximum_WallTime_Exceeded, + -10 => :Not_Enough_Degrees_Of_Freedom, + -11 => :Invalid_Problem_Definition, + -12 => :Invalid_Option, + -13 => :Invalid_Number_Detected, + -100 => :Unrecoverable_Exception, + -101 => :NonIpopt_Exception_Thrown, + -102 => :Insufficient_Memory, + -199 => :Internal_Error, +) + +""" + IpoptSolver(nlp; kwargs...,) + +Returns an `IpoptSolver` structure to solve the problem `nlp` with `ipopt`. +""" +mutable struct IpoptSolver <: AbstractOptimizationSolver + problem::IpoptProblem +end + +function IpoptSolver(nlp::AbstractNLPModel) + eval_f, eval_g, eval_grad_f, eval_jac_g, eval_h = set_callbacks(nlp) + + problem = CreateIpoptProblem( + nlp.meta.nvar, + nlp.meta.lvar, + nlp.meta.uvar, + nlp.meta.ncon, + nlp.meta.lcon, + nlp.meta.ucon, + nlp.meta.nnzj, + nlp.meta.nnzh, + eval_f, + eval_g, + eval_grad_f, + eval_jac_g, + eval_h, + ) + return IpoptSolver(problem) +end + +""" + solver = reset!(solver::IpoptSolver, nlp::AbstractNLPModel) + +Reset the `solver` with the new model `nlp`. + +If `nlp` has different bounds on the variables/constraints or a different number of nonzeros elements in the Jacobian/Hessian, then you need to create a new `IpoptSolver`. +""" +function SolverCore.reset!(solver::IpoptSolver, nlp::AbstractNLPModel) + problem = solver.problem + @assert nlp.meta.nvar == problem.n + @assert nlp.meta.ncon == problem.m + + problem.obj_val = Inf + problem.status = -1 + problem.x .= nlp.meta.x0 + eval_f, eval_g, eval_grad_f, eval_jac_g, eval_h = set_callbacks(nlp) + problem.eval_f = eval_f + problem.eval_g = eval_g + problem.eval_grad_f = eval_grad_f + problem.eval_jac_g = eval_jac_g + problem.eval_h = eval_h + problem.intermediate = nothing + + # TODO: reset problem.ipopt_problem + return problem +end + +function SolverCore.reset!(solver::IpoptSolver) + problem = solver.problem + + problem.obj_val = Inf + problem.status = -1 # Use -1 to indicate not solved yet + problem.intermediate = nothing + + return solver +end + +""" + set_callbacks(nlp::AbstractNLPModel) + +Return the set of functions needed to instantiate an `IpoptProblem`. + +This function creates the callback functions that Ipopt needs to evaluate: +- the objective function +- the constraint function (if any) +- the objective gradient +- the constraint Jacobian (if any) +- the Hessian of the Lagrangian + +For information on using callbacks to monitor the optimization process, +see the tutorial documentation. +""" +function set_callbacks(nlp::AbstractNLPModel) + eval_f(x) = obj(nlp, x) + eval_g(x, g) = nlp.meta.ncon > 0 ? cons!(nlp, x, g) : zeros(0) + eval_grad_f(x, g) = grad!(nlp, x, g) + eval_jac_g(x, rows::Vector{Int32}, cols::Vector{Int32}, values) = begin + nlp.meta.ncon == 0 && return + if values == nothing + jac_structure!(nlp, rows, cols) + else + jac_coord!(nlp, x, values) + end + end + eval_h(x, rows::Vector{Int32}, cols::Vector{Int32}, σ, λ, values) = begin + if values == nothing + hess_structure!(nlp, rows, cols) + else + if nlp.meta.ncon > 0 + hess_coord!(nlp, x, λ, values, obj_weight = σ) + else + hess_coord!(nlp, x, values, obj_weight = σ) + end + end + end + + return eval_f, eval_g, eval_grad_f, eval_jac_g, eval_h +end + +""" + output = ipopt(nlp; kwargs...) + +Solves the `NLPModel` problem `nlp` using `IpOpt`. + +For advanced usage, first define a `IpoptSolver` to preallocate the memory used in the algorithm, and then call `solve!`: + solver = IpoptSolver(nlp) + solve!(solver, nlp; kwargs...) + solve!(solver, nlp, stats; kwargs...) + +# Optional keyword arguments +* `x0`: a vector of size `nlp.meta.nvar` to specify an initial primal guess +* `y0`: a vector of size `nlp.meta.ncon` to specify an initial dual guess for the general constraints +* `zL`: a vector of size `nlp.meta.nvar` to specify initial multipliers for the lower bound constraints +* `zU`: a vector of size `nlp.meta.nvar` to specify initial multipliers for the upper bound constraints + +All other keyword arguments will be passed to Ipopt as an option. +See [https://coin-or.github.io/Ipopt/OPTIONS.html](https://coin-or.github.io/Ipopt/OPTIONS.html) for the list of options accepted. + +# Output +The returned value is a `GenericExecutionStats`, see `SolverCore.jl`. + +# Examples +``` +using NLPModelsIpopt, ADNLPModels +nlp = ADNLPModel(x -> sum(x.^2), ones(3)); +stats = ipopt(nlp, print_level = 0) +``` + +``` +using NLPModelsIpopt, ADNLPModels +nlp = ADNLPModel(x -> sum(x.^2), ones(3)); +solver = IpoptSolver(nlp); +stats = solve!(solver, nlp, print_level = 0) +``` +""" +function ipopt(nlp::AbstractNLPModel; kwargs...) + solver = IpoptSolver(nlp) + stats = GenericExecutionStats(nlp) + return solve!(solver, nlp, stats; kwargs...) +end + +""" + ipopt(nls::AbstractNLSModel; kwargs...) + +Solve the least-squares problem `nls` using `IPOPT` by moving the nonlinear residual to the constraints. + +# Arguments +- `nls::AbstractNLSModel`: The least-squares problem to solve. + +For advanced usage, first define an `IpoptSolver` to preallocate the memory used in the algorithm, and then call `solve!`: + solver = IpoptSolver(nls) + solve!(solver, nls; kwargs...) + +# Examples +```julia +using NLPModelsIpopt, ADNLPModels +nls = ADNLSModel(x -> [x[1] - 1, x[2] - 2], [0.0, 0.0], 2) +stats = ipopt(nls, print_level = 0) +``` +""" +function ipopt(ff_nls::FeasibilityFormNLS; kwargs...) + solver = IpoptSolver(ff_nls) + stats = GenericExecutionStats(ff_nls) + stats = solve!(solver, ff_nls, stats; kwargs...) + + return stats +end + +function ipopt(nls::AbstractNLSModel; kwargs...) + ff_nls = FeasibilityFormNLS(nls) + stats = ipopt(ff_nls; kwargs...) + + stats.solution = + length(stats.solution) >= nls.meta.nvar ? stats.solution[1:nls.meta.nvar] : stats.solution + stats.multipliers_L = + length(stats.multipliers_L) >= nls.meta.nvar ? stats.multipliers_L[1:nls.meta.nvar] : + stats.multipliers_L + stats.multipliers_U = + length(stats.multipliers_U) >= nls.meta.nvar ? stats.multipliers_U[1:nls.meta.nvar] : + stats.multipliers_U + stats.multipliers = + length(stats.multipliers) >= nls.meta.ncon ? stats.multipliers[(end - nls.meta.ncon + 1):end] : + stats.multipliers + return stats +end + +function SolverCore.solve!( + solver::IpoptSolver, + nlp::AbstractNLPModel, + stats::GenericExecutionStats; + callback = (args...) -> true, + kwargs..., +) + problem = solver.problem + SolverCore.reset!(stats) + kwargs = Dict(kwargs) + + # see if user wants to warm start from an initial primal-dual guess + if all(k ∈ keys(kwargs) for k ∈ [:x0, :y0, :zL0, :zU0]) + AddIpoptStrOption(problem, "warm_start_init_point", "yes") + pop!(kwargs, :warm_start_init_point, nothing) # in case the user passed this option + end + if :x0 ∈ keys(kwargs) + problem.x = Vector{Float64}(kwargs[:x0]) + pop!(kwargs, :x0) + else + problem.x = Vector{Float64}(nlp.meta.x0) + end + if :y0 ∈ keys(kwargs) + problem.mult_g = Vector{Float64}(kwargs[:y0]) + pop!(kwargs, :y0) + end + if :zL0 ∈ keys(kwargs) + problem.mult_x_L = Vector{Float64}(kwargs[:zL0]) + pop!(kwargs, :zL0) + end + if :zU0 ∈ keys(kwargs) + problem.mult_x_U = Vector{Float64}(kwargs[:zU0]) + pop!(kwargs, :zU0) + end + + # pass options to IPOPT + # make sure IPOPT logs to file so we can grep time, residuals and number of iterations + ipopt_log_to_file = false + ipopt_file_log_level = 3 + local ipopt_log_file + for (k, v) in kwargs + if k == :output_file + ipopt_log_file = v + ipopt_log_to_file = true + elseif k == :file_print_level + ipopt_file_log_level = v + elseif typeof(v) <: Integer + AddIpoptIntOption(problem, string(k), v) + elseif typeof(v) <: Real + AddIpoptNumOption(problem, string(k), v) + elseif typeof(v) <: String + AddIpoptStrOption(problem, string(k), v) + else + @warn "$k does not seem to be a valid Ipopt option." + end + end + + if !nlp.meta.minimize + AddIpoptNumOption(problem, "obj_scaling_factor", -1.0) + end + + if ipopt_log_to_file + 0 < ipopt_file_log_level < 3 && @warn( + "`file_print_level` should be 0 or ≥ 3 for IPOPT to report elapsed time, final residuals and number of iterations" + ) + else + # log to file anyways to parse the output + ipopt_log_file = tempname() + # make sure the user didn't specify a file log level without a file name + 0 < ipopt_file_log_level < 3 && (ipopt_file_log_level = 3) + end + + AddIpoptStrOption(problem, "output_file", ipopt_log_file) + AddIpoptIntOption(problem, "file_print_level", ipopt_file_log_level) + + # Callback + SetIntermediateCallback(problem, callback) + + real_time = time() + status = IpoptSolve(problem) + real_time = time() - real_time + + set_status!(stats, get(ipopt_statuses, status, :unknown)) + set_solution!(stats, problem.x) + set_objective!(stats, problem.obj_val) + set_constraint_multipliers!(stats, problem.mult_g) + if has_bounds(nlp) + set_bounds_multipliers!(stats, problem.mult_x_L, problem.mult_x_U) + end + set_solver_specific!(stats, :internal_msg, ipopt_internal_statuses[status]) + set_solver_specific!(stats, :real_time, real_time) + + try + ipopt_output = readlines(ipopt_log_file) + + Δt = 0.0 + dual_feas = primal_feas = Inf + iter = -1 + for line in ipopt_output + if occursin("Total seconds", line) + Δt += Meta.parse(split(line, "=")[2]) + elseif occursin("Dual infeasibility", line) + dual_feas = Meta.parse(split(line)[4]) + elseif occursin("Constraint violation", line) + primal_feas = Meta.parse(split(line)[4]) + elseif occursin("Number of Iterations....", line) + iter = Meta.parse(split(line)[4]) + end + end + set_residuals!(stats, primal_feas, dual_feas) + set_iter!(stats, iter) + set_time!(stats, Δt) + catch e + @warn("could not parse Ipopt log file. $e") + stats.primal_residual_reliable = false + stats.dual_residual_reliable = false + stats.iter_reliable = false + stats.time_reliable = false + end + + stats +end + +end # module diff --git a/docs/Project.toml b/docs/Project.toml index 030a953..c6a300e 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -6,6 +6,7 @@ GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [compat] diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index 642824a..6515516 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -77,21 +77,32 @@ function my_callback(alg_mod, iter_count, problem_ptr, args...) end ``` -### Accessing current iterate information +### Callback function signature -The `problem_ptr` argument provides access to the current state of the optimization: +Callback functions must have the following signature: -- `Ipopt.GetIpoptCurrentIterate(problem_ptr)` returns: - - `x`: current primal variables - - `z_L`: current multipliers for lower bounds - - `z_U`: current multipliers for upper bounds - - `g`: current constraint values - - `lambda`: current multipliers for constraints +```julia +function my_callback(alg_mod, iter_count, obj_value, inf_pr, inf_du, mu, d_norm, regularization_size, alpha_du, alpha_pr, ls_trials, args...) + # Your callback code here + return true # return false to stop optimization +end +``` + +### Callback parameters -- `Ipopt.GetIpoptCurrentViolations(problem_ptr)` returns: - - `constr_viol`: constraint violation - - `dual_inf`: dual infeasibility - - `compl`: complementarity +The callback function receives the following parameters from Ipopt: + +- `alg_mod`: algorithm mode (0 = regular, 1 = restoration phase) +- `iter_count`: current iteration number +- `obj_value`: current objective function value +- `inf_pr`: primal infeasibility (constraint violation) +- `inf_du`: dual infeasibility +- `mu`: complementarity measure +- `d_norm`: norm of the primal step +- `regularization_size`: size of regularization +- `alpha_du`: step size for dual variables +- `alpha_pr`: step size for primal variables +- `ls_trials`: number of line search trials ### Example usage @@ -100,19 +111,14 @@ Here's a complete example showing how to use callbacks to monitor the optimizati ```@example ex4 using ADNLPModels, NLPModelsIpopt -# Define a callback function to monitor iterations -function my_callback(alg_mod, iter_count, problem_ptr, args...) - # Get current iterate (primal and dual variables) - x, z_L, z_U, g, lambda = Ipopt.GetIpoptCurrentIterate(problem_ptr) - # Get current constraint violations - constr_viol, dual_inf, compl = Ipopt.GetIpoptCurrentViolations(problem_ptr) - - # Log iteration information +# Define a simple callback function to monitor iterations +function my_callback(alg_mod, iter_count, obj_value, inf_pr, inf_du, mu, d_norm, regularization_size, alpha_du, alpha_pr, ls_trials, args...) + # Log iteration information (these are the standard parameters passed by Ipopt) println("Iteration $iter_count:") - println(" x = ", x) - println(" Constraint violation = ", constr_viol) - println(" Dual infeasibility = ", dual_inf) - println(" Complementarity = ", compl) + println(" Objective value = ", obj_value) + println(" Primal infeasibility = ", inf_pr) + println(" Dual infeasibility = ", inf_du) + println(" Complementarity = ", mu) # Return true to continue, false to stop return iter_count < 5 # Stop after 5 iterations for this example @@ -136,12 +142,9 @@ stats = solve!(solver, nlp, callback = my_callback, print_level = 0) Callbacks are particularly useful for implementing custom stopping criteria: ```@example ex4 -function custom_stopping_callback(alg_mod, iter_count, problem_ptr, args...) - x, z_L, z_U, g, lambda = Ipopt.GetIpoptCurrentIterate(problem_ptr) - constr_viol, dual_inf, compl = Ipopt.GetIpoptCurrentViolations(problem_ptr) - - # Custom stopping criterion: stop if x[1] gets close to 1 - if abs(x[1] - 1.0) < 0.1 +function custom_stopping_callback(alg_mod, iter_count, obj_value, inf_pr, inf_du, mu, d_norm, regularization_size, alpha_du, alpha_pr, ls_trials, args...) + # Custom stopping criterion: stop if objective gets close to optimum + if obj_value < 0.01 println("Custom stopping criterion met at iteration $iter_count") return false # Stop optimization end From 62e7cb49d33914fe40c292ba135d2e349c5e1856 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Tue, 12 Aug 2025 09:19:45 +0530 Subject: [PATCH 07/16] Update Project.toml Co-authored-by: Tangi Migot --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index 98ab74a..d590270 100644 --- a/Project.toml +++ b/Project.toml @@ -3,7 +3,6 @@ uuid = "f4238b75-b362-5c4c-b852-0801c9a21d71" version = "0.10.4" [deps] -ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" NLPModelsModifiers = "e01155f1-5c6f-4375-a9d8-616dd036575f" From 79de7f4845bd5c8a44fd19cda0d6f6c1b3ac2ad0 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Tue, 12 Aug 2025 09:19:57 +0530 Subject: [PATCH 08/16] Update docs/Project.toml Co-authored-by: Tangi Migot --- docs/Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index c6a300e..030a953 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -6,7 +6,6 @@ GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" -NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [compat] From 221dfd93487bca73c1399b6d4d220096dedd88a9 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Tue, 12 Aug 2025 09:21:26 +0530 Subject: [PATCH 09/16] Update NLPModelsIpopt.jl --- build/NLPModelsIpopt.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/build/NLPModelsIpopt.jl b/build/NLPModelsIpopt.jl index f39f039..af9e2fa 100644 --- a/build/NLPModelsIpopt.jl +++ b/build/NLPModelsIpopt.jl @@ -1,5 +1,3 @@ -module NLPModelsIpopt - export ipopt, IpoptSolver, reset!, solve! using NLPModels, Ipopt, SolverCore From e6ffa2649e549ad3b8c12aab27bd1b14308e7c3a Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Tue, 12 Aug 2025 09:22:50 +0530 Subject: [PATCH 10/16] Update tutorial.md --- docs/src/tutorial.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index 6515516..21197d8 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -77,17 +77,6 @@ function my_callback(alg_mod, iter_count, problem_ptr, args...) end ``` -### Callback function signature - -Callback functions must have the following signature: - -```julia -function my_callback(alg_mod, iter_count, obj_value, inf_pr, inf_du, mu, d_norm, regularization_size, alpha_du, alpha_pr, ls_trials, args...) - # Your callback code here - return true # return false to stop optimization -end -``` - ### Callback parameters The callback function receives the following parameters from Ipopt: From f8e6beaa5dab433dcb7eece7bddc13cc6a426201 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Tue, 12 Aug 2025 09:23:31 +0530 Subject: [PATCH 11/16] Delete build/NLPModelsIpopt.jl --- build/NLPModelsIpopt.jl | 371 ---------------------------------------- 1 file changed, 371 deletions(-) delete mode 100644 build/NLPModelsIpopt.jl diff --git a/build/NLPModelsIpopt.jl b/build/NLPModelsIpopt.jl deleted file mode 100644 index af9e2fa..0000000 --- a/build/NLPModelsIpopt.jl +++ /dev/null @@ -1,371 +0,0 @@ -export ipopt, IpoptSolver, reset!, solve! - -using NLPModels, Ipopt, SolverCore -using NLPModelsModifiers: FeasibilityFormNLS - -const ipopt_statuses = Dict( - 0 => :first_order, - 1 => :acceptable, - 2 => :infeasible, - 3 => :small_step, - #4 => Diverging iterates - 5 => :user, - #6 => Feasible point found - -1 => :max_iter, - #-2 => Restoration failed - #-3 => Error in step computation - -4 => :max_time, # Maximum cputime exceeded - -5 => :max_time, # Maximum walltime exceeded - #-10 => Not enough degress of freedom - #-11 => Invalid problem definition - #-12 => Invalid option - #-13 => Invalid number detected - -100 => :exception, # Unrecoverable exception - -101 => :exception, # NonIpopt exception thrown - -102 => :exception, # Insufficient memory - -199 => :exception, # Internal error -) - -const ipopt_internal_statuses = Dict( - 0 => :Solve_Succeeded, - 1 => :Solved_To_Acceptable_Level, - 2 => :Infeasible_Problem_Detected, - 3 => :Search_Direction_Becomes_Too_Small, - 4 => :Diverging_Iterates, - 5 => :User_Requested_Stop, - 6 => :Feasible_Point_Found, - -1 => :Maximum_Iterations_Exceeded, - -2 => :Restoration_Failed, - -3 => :Error_In_Step_Computation, - -4 => :Maximum_CpuTime_Exceeded, - -5 => :Maximum_WallTime_Exceeded, - -10 => :Not_Enough_Degrees_Of_Freedom, - -11 => :Invalid_Problem_Definition, - -12 => :Invalid_Option, - -13 => :Invalid_Number_Detected, - -100 => :Unrecoverable_Exception, - -101 => :NonIpopt_Exception_Thrown, - -102 => :Insufficient_Memory, - -199 => :Internal_Error, -) - -""" - IpoptSolver(nlp; kwargs...,) - -Returns an `IpoptSolver` structure to solve the problem `nlp` with `ipopt`. -""" -mutable struct IpoptSolver <: AbstractOptimizationSolver - problem::IpoptProblem -end - -function IpoptSolver(nlp::AbstractNLPModel) - eval_f, eval_g, eval_grad_f, eval_jac_g, eval_h = set_callbacks(nlp) - - problem = CreateIpoptProblem( - nlp.meta.nvar, - nlp.meta.lvar, - nlp.meta.uvar, - nlp.meta.ncon, - nlp.meta.lcon, - nlp.meta.ucon, - nlp.meta.nnzj, - nlp.meta.nnzh, - eval_f, - eval_g, - eval_grad_f, - eval_jac_g, - eval_h, - ) - return IpoptSolver(problem) -end - -""" - solver = reset!(solver::IpoptSolver, nlp::AbstractNLPModel) - -Reset the `solver` with the new model `nlp`. - -If `nlp` has different bounds on the variables/constraints or a different number of nonzeros elements in the Jacobian/Hessian, then you need to create a new `IpoptSolver`. -""" -function SolverCore.reset!(solver::IpoptSolver, nlp::AbstractNLPModel) - problem = solver.problem - @assert nlp.meta.nvar == problem.n - @assert nlp.meta.ncon == problem.m - - problem.obj_val = Inf - problem.status = -1 - problem.x .= nlp.meta.x0 - eval_f, eval_g, eval_grad_f, eval_jac_g, eval_h = set_callbacks(nlp) - problem.eval_f = eval_f - problem.eval_g = eval_g - problem.eval_grad_f = eval_grad_f - problem.eval_jac_g = eval_jac_g - problem.eval_h = eval_h - problem.intermediate = nothing - - # TODO: reset problem.ipopt_problem - return problem -end - -function SolverCore.reset!(solver::IpoptSolver) - problem = solver.problem - - problem.obj_val = Inf - problem.status = -1 # Use -1 to indicate not solved yet - problem.intermediate = nothing - - return solver -end - -""" - set_callbacks(nlp::AbstractNLPModel) - -Return the set of functions needed to instantiate an `IpoptProblem`. - -This function creates the callback functions that Ipopt needs to evaluate: -- the objective function -- the constraint function (if any) -- the objective gradient -- the constraint Jacobian (if any) -- the Hessian of the Lagrangian - -For information on using callbacks to monitor the optimization process, -see the tutorial documentation. -""" -function set_callbacks(nlp::AbstractNLPModel) - eval_f(x) = obj(nlp, x) - eval_g(x, g) = nlp.meta.ncon > 0 ? cons!(nlp, x, g) : zeros(0) - eval_grad_f(x, g) = grad!(nlp, x, g) - eval_jac_g(x, rows::Vector{Int32}, cols::Vector{Int32}, values) = begin - nlp.meta.ncon == 0 && return - if values == nothing - jac_structure!(nlp, rows, cols) - else - jac_coord!(nlp, x, values) - end - end - eval_h(x, rows::Vector{Int32}, cols::Vector{Int32}, σ, λ, values) = begin - if values == nothing - hess_structure!(nlp, rows, cols) - else - if nlp.meta.ncon > 0 - hess_coord!(nlp, x, λ, values, obj_weight = σ) - else - hess_coord!(nlp, x, values, obj_weight = σ) - end - end - end - - return eval_f, eval_g, eval_grad_f, eval_jac_g, eval_h -end - -""" - output = ipopt(nlp; kwargs...) - -Solves the `NLPModel` problem `nlp` using `IpOpt`. - -For advanced usage, first define a `IpoptSolver` to preallocate the memory used in the algorithm, and then call `solve!`: - solver = IpoptSolver(nlp) - solve!(solver, nlp; kwargs...) - solve!(solver, nlp, stats; kwargs...) - -# Optional keyword arguments -* `x0`: a vector of size `nlp.meta.nvar` to specify an initial primal guess -* `y0`: a vector of size `nlp.meta.ncon` to specify an initial dual guess for the general constraints -* `zL`: a vector of size `nlp.meta.nvar` to specify initial multipliers for the lower bound constraints -* `zU`: a vector of size `nlp.meta.nvar` to specify initial multipliers for the upper bound constraints - -All other keyword arguments will be passed to Ipopt as an option. -See [https://coin-or.github.io/Ipopt/OPTIONS.html](https://coin-or.github.io/Ipopt/OPTIONS.html) for the list of options accepted. - -# Output -The returned value is a `GenericExecutionStats`, see `SolverCore.jl`. - -# Examples -``` -using NLPModelsIpopt, ADNLPModels -nlp = ADNLPModel(x -> sum(x.^2), ones(3)); -stats = ipopt(nlp, print_level = 0) -``` - -``` -using NLPModelsIpopt, ADNLPModels -nlp = ADNLPModel(x -> sum(x.^2), ones(3)); -solver = IpoptSolver(nlp); -stats = solve!(solver, nlp, print_level = 0) -``` -""" -function ipopt(nlp::AbstractNLPModel; kwargs...) - solver = IpoptSolver(nlp) - stats = GenericExecutionStats(nlp) - return solve!(solver, nlp, stats; kwargs...) -end - -""" - ipopt(nls::AbstractNLSModel; kwargs...) - -Solve the least-squares problem `nls` using `IPOPT` by moving the nonlinear residual to the constraints. - -# Arguments -- `nls::AbstractNLSModel`: The least-squares problem to solve. - -For advanced usage, first define an `IpoptSolver` to preallocate the memory used in the algorithm, and then call `solve!`: - solver = IpoptSolver(nls) - solve!(solver, nls; kwargs...) - -# Examples -```julia -using NLPModelsIpopt, ADNLPModels -nls = ADNLSModel(x -> [x[1] - 1, x[2] - 2], [0.0, 0.0], 2) -stats = ipopt(nls, print_level = 0) -``` -""" -function ipopt(ff_nls::FeasibilityFormNLS; kwargs...) - solver = IpoptSolver(ff_nls) - stats = GenericExecutionStats(ff_nls) - stats = solve!(solver, ff_nls, stats; kwargs...) - - return stats -end - -function ipopt(nls::AbstractNLSModel; kwargs...) - ff_nls = FeasibilityFormNLS(nls) - stats = ipopt(ff_nls; kwargs...) - - stats.solution = - length(stats.solution) >= nls.meta.nvar ? stats.solution[1:nls.meta.nvar] : stats.solution - stats.multipliers_L = - length(stats.multipliers_L) >= nls.meta.nvar ? stats.multipliers_L[1:nls.meta.nvar] : - stats.multipliers_L - stats.multipliers_U = - length(stats.multipliers_U) >= nls.meta.nvar ? stats.multipliers_U[1:nls.meta.nvar] : - stats.multipliers_U - stats.multipliers = - length(stats.multipliers) >= nls.meta.ncon ? stats.multipliers[(end - nls.meta.ncon + 1):end] : - stats.multipliers - return stats -end - -function SolverCore.solve!( - solver::IpoptSolver, - nlp::AbstractNLPModel, - stats::GenericExecutionStats; - callback = (args...) -> true, - kwargs..., -) - problem = solver.problem - SolverCore.reset!(stats) - kwargs = Dict(kwargs) - - # see if user wants to warm start from an initial primal-dual guess - if all(k ∈ keys(kwargs) for k ∈ [:x0, :y0, :zL0, :zU0]) - AddIpoptStrOption(problem, "warm_start_init_point", "yes") - pop!(kwargs, :warm_start_init_point, nothing) # in case the user passed this option - end - if :x0 ∈ keys(kwargs) - problem.x = Vector{Float64}(kwargs[:x0]) - pop!(kwargs, :x0) - else - problem.x = Vector{Float64}(nlp.meta.x0) - end - if :y0 ∈ keys(kwargs) - problem.mult_g = Vector{Float64}(kwargs[:y0]) - pop!(kwargs, :y0) - end - if :zL0 ∈ keys(kwargs) - problem.mult_x_L = Vector{Float64}(kwargs[:zL0]) - pop!(kwargs, :zL0) - end - if :zU0 ∈ keys(kwargs) - problem.mult_x_U = Vector{Float64}(kwargs[:zU0]) - pop!(kwargs, :zU0) - end - - # pass options to IPOPT - # make sure IPOPT logs to file so we can grep time, residuals and number of iterations - ipopt_log_to_file = false - ipopt_file_log_level = 3 - local ipopt_log_file - for (k, v) in kwargs - if k == :output_file - ipopt_log_file = v - ipopt_log_to_file = true - elseif k == :file_print_level - ipopt_file_log_level = v - elseif typeof(v) <: Integer - AddIpoptIntOption(problem, string(k), v) - elseif typeof(v) <: Real - AddIpoptNumOption(problem, string(k), v) - elseif typeof(v) <: String - AddIpoptStrOption(problem, string(k), v) - else - @warn "$k does not seem to be a valid Ipopt option." - end - end - - if !nlp.meta.minimize - AddIpoptNumOption(problem, "obj_scaling_factor", -1.0) - end - - if ipopt_log_to_file - 0 < ipopt_file_log_level < 3 && @warn( - "`file_print_level` should be 0 or ≥ 3 for IPOPT to report elapsed time, final residuals and number of iterations" - ) - else - # log to file anyways to parse the output - ipopt_log_file = tempname() - # make sure the user didn't specify a file log level without a file name - 0 < ipopt_file_log_level < 3 && (ipopt_file_log_level = 3) - end - - AddIpoptStrOption(problem, "output_file", ipopt_log_file) - AddIpoptIntOption(problem, "file_print_level", ipopt_file_log_level) - - # Callback - SetIntermediateCallback(problem, callback) - - real_time = time() - status = IpoptSolve(problem) - real_time = time() - real_time - - set_status!(stats, get(ipopt_statuses, status, :unknown)) - set_solution!(stats, problem.x) - set_objective!(stats, problem.obj_val) - set_constraint_multipliers!(stats, problem.mult_g) - if has_bounds(nlp) - set_bounds_multipliers!(stats, problem.mult_x_L, problem.mult_x_U) - end - set_solver_specific!(stats, :internal_msg, ipopt_internal_statuses[status]) - set_solver_specific!(stats, :real_time, real_time) - - try - ipopt_output = readlines(ipopt_log_file) - - Δt = 0.0 - dual_feas = primal_feas = Inf - iter = -1 - for line in ipopt_output - if occursin("Total seconds", line) - Δt += Meta.parse(split(line, "=")[2]) - elseif occursin("Dual infeasibility", line) - dual_feas = Meta.parse(split(line)[4]) - elseif occursin("Constraint violation", line) - primal_feas = Meta.parse(split(line)[4]) - elseif occursin("Number of Iterations....", line) - iter = Meta.parse(split(line)[4]) - end - end - set_residuals!(stats, primal_feas, dual_feas) - set_iter!(stats, iter) - set_time!(stats, Δt) - catch e - @warn("could not parse Ipopt log file. $e") - stats.primal_residual_reliable = false - stats.dual_residual_reliable = false - stats.iter_reliable = false - stats.time_reliable = false - end - - stats -end - -end # module From f74a8d578a6cac60a6aedc681ce2a3385b1b405f Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Thu, 14 Aug 2025 08:23:25 +0530 Subject: [PATCH 12/16] Update docs/src/tutorial.md Co-authored-by: Tangi Migot --- docs/src/tutorial.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index 21197d8..dacd446 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -100,7 +100,6 @@ Here's a complete example showing how to use callbacks to monitor the optimizati ```@example ex4 using ADNLPModels, NLPModelsIpopt -# Define a simple callback function to monitor iterations function my_callback(alg_mod, iter_count, obj_value, inf_pr, inf_du, mu, d_norm, regularization_size, alpha_du, alpha_pr, ls_trials, args...) # Log iteration information (these are the standard parameters passed by Ipopt) println("Iteration $iter_count:") From a12d6996a54637c9fc15152cd6ff2a216a3bae67 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Thu, 14 Aug 2025 08:23:38 +0530 Subject: [PATCH 13/16] Update docs/src/tutorial.md Co-authored-by: Tangi Migot --- docs/src/tutorial.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index dacd446..e6b4df1 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -112,7 +112,6 @@ function my_callback(alg_mod, iter_count, obj_value, inf_pr, inf_du, mu, d_norm, return iter_count < 5 # Stop after 5 iterations for this example end -# Create and solve a problem with callback nlp = ADNLPModel(x -> (x[1] - 1)^2 + 100 * (x[2] - x[1]^2)^2, [-1.2; 1.0]) stats = ipopt(nlp, callback = my_callback, print_level = 0) ``` From 64f4a94909d5cc5fd44e592cb1b51fa58baddb1c Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Thu, 14 Aug 2025 08:23:57 +0530 Subject: [PATCH 14/16] Update docs/src/tutorial.md Co-authored-by: Tangi Migot --- docs/src/tutorial.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index e6b4df1..f59e470 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -111,7 +111,6 @@ function my_callback(alg_mod, iter_count, obj_value, inf_pr, inf_du, mu, d_norm, # Return true to continue, false to stop return iter_count < 5 # Stop after 5 iterations for this example end - nlp = ADNLPModel(x -> (x[1] - 1)^2 + 100 * (x[2] - x[1]^2)^2, [-1.2; 1.0]) stats = ipopt(nlp, callback = my_callback, print_level = 0) ``` From ebbee9ca3093ed71bbd1b031ae0d25cfb4916b30 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Thu, 14 Aug 2025 08:24:06 +0530 Subject: [PATCH 15/16] Update docs/src/tutorial.md Co-authored-by: Tangi Migot --- docs/src/tutorial.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index f59e470..7001922 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -116,7 +116,6 @@ stats = ipopt(nlp, callback = my_callback, print_level = 0) ``` You can also use callbacks with the advanced solver interface: - ```@example ex4 # Advanced usage with IpoptSolver solver = IpoptSolver(nlp) From 4fa0e73107e522a73075f026fdec411090c2e5ec Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Thu, 14 Aug 2025 08:26:14 +0530 Subject: [PATCH 16/16] Update tutorial.md --- docs/src/tutorial.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index 7001922..b9b4f4a 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -66,17 +66,6 @@ stats.solver_specific[:internal_msg] You can monitor the optimization process using a callback function. The callback allows you to access the current iterate and constraint violations at each iteration, which is useful for custom stopping criteria, logging, or real-time analysis. -### Callback signature - -The callback function must have the following signature: - -```julia -function my_callback(alg_mod, iter_count, problem_ptr, args...) - # Your custom code here - return true # return false to stop optimization -end -``` - ### Callback parameters The callback function receives the following parameters from Ipopt: