From c8021e6b0355528d499fe8dea4aa5dfc6c761012 Mon Sep 17 00:00:00 2001 From: BenjaminPINEAU Date: Mon, 4 May 2026 16:29:02 -0400 Subject: [PATCH 1/5] Detect unbounded and infeasible problems --- src/AL_alg.jl | 40 +++++++++++++++++++++++++++++++--------- src/R2N.jl | 36 ++++++++++++++++++++++++++---------- src/R2_alg.jl | 41 +++++++++++++++++++++++++++++------------ src/utils.jl | 8 ++++++++ 4 files changed, 94 insertions(+), 31 deletions(-) diff --git a/src/AL_alg.jl b/src/AL_alg.jl index c4cc8ec5..21583a63 100644 --- a/src/AL_alg.jl +++ b/src/AL_alg.jl @@ -102,7 +102,12 @@ If adopted, the Hessian is accessed as an abstract operator and need not be the - `x::AbstractVector`: a primal initial guess (default: `reg_nlp.model.meta.x0`) - `y::AbstractVector`: a dual initial guess (default: `reg_nlp.model.meta.y0`) -- `atol::T = √eps(T)`: absolute optimality tolerance; +- `atol::T = eps(T)^(1/3)`: absolute tolerance +- `diverging_iterates_tol::T = eps(T)^(-1)`: diverging tolerance for the norm of the iterates (the norm should be lower than the tolerance); +- `diverging_obj_tol::T = -eps(T)^(-1)`: diverging tolerance for the objective function (the objective function should be higher than the tolerance); +- `cviol_tol::T = eps(T)^(-1)`: tolerance to determine whether the constraints are infeasible +- `diverging_max_iter::Int = 5`: maximum number of iteration at which `diverging_obj_tol` or `diverging_iterates_tol` is violated; +- `cviol_max_iter::Int = 5`: maximum number of iteration at which the regularisation parameter is increasing and the constraints are still violated; - `ctol::T = atol`: absolute feasibility tolerance; - `verbose::Int = 0`: if > 0, display iteration details every `verbose` iteration; - `max_iter::Int = 10000`: maximum number of iterations; @@ -209,7 +214,12 @@ function SolverCore.solve!( callback = (args...) -> nothing, x::V = reg_nlp.model.meta.x0, y::V = reg_nlp.model.meta.y0, - atol::T = √eps(T), + atol::T = eps(T)^(1/3), + diverging_iterates_tol::T = eps(T)^(-1), + diverging_obj_tol::T = -eps(T)^(-1), + cviol_tol::T = eps(T)^(-1), + diverging_max_iter::Int = 5, + cviol_max_iter::Int = 5, verbose::Int = 0, max_iter::Int = 10000, max_time::Float64 = 30.0, @@ -225,6 +235,9 @@ function SolverCore.solve!( ) where {T, V} reset!(stats) + local diverging_iter::Int = zero(Int) + local cviol_iter::Int = zero(Int) + # Retrieve workspace nlp = reg_nlp.model h = reg_nlp.h @@ -315,6 +328,8 @@ function SolverCore.solve!( # objective fx = obj(nlp, solver.x) hx = @views h(solver.x[selected]) + improper = (hx == -Inf) + objx = fx + hx set_objective!(stats, objx) set_solver_specific!(stats, :smooth_obj, fx) @@ -345,19 +360,19 @@ function SolverCore.solve!( set_time!(stats, time() - start_time) set_status!( stats, - SolverCore.get_status( - nlp, + get_status( + reg_nlp; elapsed_time = stats.elapsed_time, iter = stats.iter, optimal = optimal, - infeasible = false, - parameter_too_large = false, - unbounded = false, - stalled = false, - exception = false, + improper = improper, + diverging_iter = diverging_iter, + cviol_iter = cviol_iter, max_eval = max_eval, max_time = max_time, max_iter = max_iter, + diverging_max_iter = diverging_max_iter, + cviol_max_iter = cviol_max_iter, ), ) @@ -372,6 +387,13 @@ function SolverCore.solve!( if !done if cviol > max(ctol, factor_primal_linear_improvement * cviol_old) mu *= factor_penalty_up + if cviol > cviol_tol + cviol_iter += 1 + end + end + if (fx + hx < diverging_obj_tol) || (norm(solver.x) > diverging_iterates_tol) + mu *= factor_penalty_up + diverging_iter = diverging_iter + 1 end update_μ!(solver.sub_problem.model, mu) cviol_old = cviol diff --git a/src/R2N.jl b/src/R2N.jl index acb05d94..cc80a94d 100644 --- a/src/R2N.jl +++ b/src/R2N.jl @@ -137,8 +137,11 @@ For advanced usage, first define a solver "R2NSolver" to preallocate the memory # Keyword arguments - `x::V = nlp.meta.x0`: the initial guess; -- `atol::T = √eps(T)`: absolute tolerance; -- `rtol::T = √eps(T)`: relative tolerance; +- `atol::T = eps(T)^(1/3)`: absolute tolerance +- `diverging_iterates_tol::T = eps(T)^(-1)`: diverging tolerance for the norm of the iterates (the norm should be lower than the tolerance); +- `diverging_obj_tol::T = -eps(T)^(-1)`: diverging tolerance for the objective function (the objective function should be higher than the tolerance); +- `diverging_max_iter::Int = 5`: maximum number of iteration at which `diverging_obj_tol` or `diverging_iterates_tol` is violated; +- `rtol::T = eps(T)^(1/3)`: relative tolerance; - `neg_tol::T = eps(T)^(1 / 4)`: negative tolerance; - `max_eval::Int = -1`: maximum number of evaluation of the objective function (negative number means unlimited); - `max_time::Float64 = 30.0`: maximum time limit in seconds; @@ -216,8 +219,11 @@ function SolverCore.solve!( qn_update_y!::Function = _qn_grad_update_y!, qn_copy!::Function = _qn_grad_copy!, x::V = reg_nlp.model.meta.x0, - atol::T = √eps(T), - rtol::T = √eps(T), + atol::T = eps(T)^(1/3), + diverging_iterates_tol::T = eps(T)^(-1), + diverging_obj_tol::T = -eps(T)^(-1), + diverging_max_iter::Int = 5, + rtol::T = eps(T)^(1/3), neg_tol::T = eps(T)^(1 / 4), verbose::Int = 0, max_iter::Int = 10000, @@ -299,6 +305,7 @@ function SolverCore.solve!( local ξ1::T local ρk::T = zero(T) local prox_evals::Int = 0 + local diverging_iter::Int = zero(Int) fk = compute_obj ? obj(nlp, xk) : stats.solver_specific[:smooth_obj] compute_grad && grad!(nlp, xk, ∇fk) @@ -368,9 +375,11 @@ function SolverCore.solve!( iter = stats.iter, optimal = solved, improper = improper, + diverging_iter = diverging_iter, max_eval = max_eval, max_time = max_time, max_iter = max_iter, + diverging_max_iter = diverging_max_iter, ), ) @@ -475,13 +484,18 @@ function SolverCore.solve!( set_step_status!(stats, :accepted) end - if η2 ≤ ρk < Inf - σk = max(σk/γ, σmin) - end - - if ρk < η1 || ρk == Inf + if (fk + hk < diverging_obj_tol) || (norm(xk) > diverging_iterates_tol) σk = σk * γ - set_step_status!(stats, :rejected) + diverging_iter = diverging_iter + 1 + else + diverging_iter = 0 + if η2 ≤ ρk < Inf + σk = max(σk / γ, σmin) + end + if ρk < η1 || ρk == Inf + σk = σk * γ + set_step_status!(stats, :rejected) + end end ν₁ = θ / (λmax + σk) @@ -515,9 +529,11 @@ function SolverCore.solve!( iter = stats.iter, optimal = solved, improper = improper, + diverging_iter = diverging_iter, max_eval = max_eval, max_time = max_time, max_iter = max_iter, + diverging_max_iter = diverging_max_iter, ), ) diff --git a/src/R2_alg.jl b/src/R2_alg.jl index 08b058d5..18c51d63 100644 --- a/src/R2_alg.jl +++ b/src/R2_alg.jl @@ -141,9 +141,12 @@ For advanced usage, first define a solver "R2Solver" to preallocate the memory u # Keyword arguments - `x::V = nlp.meta.x0`: the initial guess; -- `atol::T = √eps(T)`: absolute tolerance; -- `rtol::T = √eps(T)`: relative tolerance; -- `neg_tol::T = eps(T)^(1 / 4)`: negative tolerance +- `atol::T = eps(T)^(1/3)`: absolute tolerance +- `diverging_iterates_tol::T = eps(T)^(-1)`: diverging tolerance for the norm of the iterates (the norm should be lower than the tolerance); +- `diverging_obj_tol::T = -eps(T)^(-1)`: diverging tolerance for the objective function (the objective function should be higher than the tolerance); +- `diverging_max_iter::Int = 5`: maximum number of iteration at which `diverging_obj_tol` or `diverging_iterates_tol` is violated; +- `rtol::T = eps(T)^(1/3)`: relative tolerance; +- `neg_tol::T = eps(T)^(1 / 4)`: negative tolerance; - `max_eval::Int = -1`: maximum number of evaluation of the objective function (negative number means unlimited); - `max_time::Float64 = 30.0`: maximum time limit in seconds; - `max_iter::Int = 10000`: maximum number of iterations; @@ -313,8 +316,11 @@ function SolverCore.solve!( stats::GenericExecutionStats{T, V}; callback = (args...) -> nothing, x::V = reg_nlp.model.meta.x0, - atol::T = √eps(T), - rtol::T = √eps(T), + atol::T = eps(T)^(1/3), + diverging_iterates_tol::T = eps(T)^(-1), + diverging_obj_tol::T = -eps(T)^(-1), + diverging_max_iter::Int = 5, + rtol::T = eps(T)^(1/3), neg_tol::T = eps(T)^(1 / 4), verbose::Int = 0, max_iter::Int = 10000, @@ -386,6 +392,7 @@ function SolverCore.solve!( local ξ::T local ρk::T = zero(T) + local diverging_iter::Int = zero(Int) σk = max(1 / ν, σmin) ν = 1 / σk sqrt_ξ_νInv = one(T) @@ -420,14 +427,16 @@ function SolverCore.solve!( set_status!( stats, get_status( - reg_nlp, + reg_nlp; elapsed_time = stats.elapsed_time, iter = stats.iter, optimal = solved, improper = improper, + diverging_iter = diverging_iter, max_eval = max_eval, max_time = max_time, max_iter = max_iter, + diverging_max_iter = diverging_max_iter, ), ) @@ -476,12 +485,18 @@ function SolverCore.solve!( set_step_status!(stats, :accepted) end - if η2 ≤ ρk < Inf - σk = max(σk / γ, σmin) - end - if ρk < η1 || ρk == Inf + if (fk + hk < diverging_obj_tol) || (norm(xk) > diverging_iterates_tol) σk = σk * γ - set_step_status!(stats, :rejected) + diverging_iter = diverging_iter + 1 + else + diverging_iter = 0 + if η2 ≤ ρk < Inf + σk = max(σk / γ, σmin) + end + if ρk < η1 || ρk == Inf + σk = σk * γ + set_step_status!(stats, :rejected) + end end ν = 1 / σk @@ -506,14 +521,16 @@ function SolverCore.solve!( set_status!( stats, get_status( - reg_nlp, + reg_nlp; elapsed_time = stats.elapsed_time, iter = stats.iter, optimal = solved, improper = improper, + diverging_iter = diverging_iter, max_eval = max_eval, max_time = max_time, max_iter = max_iter, + diverging_max_iter = diverging_max_iter, ), ) diff --git a/src/utils.jl b/src/utils.jl index 15c53d9e..5bc03695 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -142,9 +142,13 @@ function get_status( iter = 0, optimal = false, improper = false, + diverging_iter = 0, + cviol_iter = 0, max_eval = Inf, max_time = Inf, max_iter = Inf, + diverging_max_iter = Inf, + cviol_max_iter = Inf, ) where {M <: AbstractRegularizedNLPModel} if optimal :first_order @@ -156,6 +160,10 @@ function get_status( :max_time elseif neval_obj(reg_nlp.model) >= max_eval && max_eval >= 0 :max_eval + elseif diverging_max_iter < diverging_iter + :unbounded + elseif cviol_max_iter < cviol_iter + :infeasible else :unknown end From 116d5ac11c0bd797023fa06becd50a8b154d29cf Mon Sep 17 00:00:00 2001 From: Benjamin Pineau Date: Tue, 5 May 2026 11:30:05 -0400 Subject: [PATCH 2/5] Update src/AL_alg.jl Co-authored-by: Dominique --- src/AL_alg.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AL_alg.jl b/src/AL_alg.jl index 21583a63..1f79873a 100644 --- a/src/AL_alg.jl +++ b/src/AL_alg.jl @@ -103,9 +103,9 @@ If adopted, the Hessian is accessed as an abstract operator and need not be the - `x::AbstractVector`: a primal initial guess (default: `reg_nlp.model.meta.x0`) - `y::AbstractVector`: a dual initial guess (default: `reg_nlp.model.meta.y0`) - `atol::T = eps(T)^(1/3)`: absolute tolerance -- `diverging_iterates_tol::T = eps(T)^(-1)`: diverging tolerance for the norm of the iterates (the norm should be lower than the tolerance); -- `diverging_obj_tol::T = -eps(T)^(-1)`: diverging tolerance for the objective function (the objective function should be higher than the tolerance); -- `cviol_tol::T = eps(T)^(-1)`: tolerance to determine whether the constraints are infeasible +- `diverging_iterates_tol::T = 1/eps(T)`: tolerance to detect divergence of the iterates (divergence occurs when the iterate norm exceeds the tolerance for `diverging_max_iter` consecutive iterations); +- `diverging_obj_tol::T = -1/eps(T)`: tolerance to detect unboundedness of the objective (unboundedness occurs when the objective value is less than the tolerance for `diverging_max_iter` consecutive iterations); +- `cviol_tol::T = 1/eps(T)`: tolerance to detect local infeasibility (local infeasibility occurs when ... [PLEASE COMPLETE] ...); - `diverging_max_iter::Int = 5`: maximum number of iteration at which `diverging_obj_tol` or `diverging_iterates_tol` is violated; - `cviol_max_iter::Int = 5`: maximum number of iteration at which the regularisation parameter is increasing and the constraints are still violated; - `ctol::T = atol`: absolute feasibility tolerance; From e8d46cfee76cf835e2dc8c392855b9cc0756b681 Mon Sep 17 00:00:00 2001 From: Benjamin Pineau Date: Tue, 5 May 2026 11:30:22 -0400 Subject: [PATCH 3/5] Update src/AL_alg.jl Co-authored-by: Dominique --- src/AL_alg.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AL_alg.jl b/src/AL_alg.jl index 1f79873a..cbe7a166 100644 --- a/src/AL_alg.jl +++ b/src/AL_alg.jl @@ -106,7 +106,7 @@ If adopted, the Hessian is accessed as an abstract operator and need not be the - `diverging_iterates_tol::T = 1/eps(T)`: tolerance to detect divergence of the iterates (divergence occurs when the iterate norm exceeds the tolerance for `diverging_max_iter` consecutive iterations); - `diverging_obj_tol::T = -1/eps(T)`: tolerance to detect unboundedness of the objective (unboundedness occurs when the objective value is less than the tolerance for `diverging_max_iter` consecutive iterations); - `cviol_tol::T = 1/eps(T)`: tolerance to detect local infeasibility (local infeasibility occurs when ... [PLEASE COMPLETE] ...); -- `diverging_max_iter::Int = 5`: maximum number of iteration at which `diverging_obj_tol` or `diverging_iterates_tol` is violated; +- `diverging_max_iter::Int = 5`: number of consecutive iterations used to detect divergence or unboundedness (see `diverging_iterates_tol` and `diverging_obj_tol`); - `cviol_max_iter::Int = 5`: maximum number of iteration at which the regularisation parameter is increasing and the constraints are still violated; - `ctol::T = atol`: absolute feasibility tolerance; - `verbose::Int = 0`: if > 0, display iteration details every `verbose` iteration; From 0e4d71033cf5e263cbf389ab42cc2ccfd813d2c3 Mon Sep 17 00:00:00 2001 From: Benjamin Pineau Date: Tue, 5 May 2026 11:30:31 -0400 Subject: [PATCH 4/5] Update src/AL_alg.jl Co-authored-by: Dominique --- src/AL_alg.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AL_alg.jl b/src/AL_alg.jl index cbe7a166..7da49404 100644 --- a/src/AL_alg.jl +++ b/src/AL_alg.jl @@ -215,9 +215,9 @@ function SolverCore.solve!( x::V = reg_nlp.model.meta.x0, y::V = reg_nlp.model.meta.y0, atol::T = eps(T)^(1/3), - diverging_iterates_tol::T = eps(T)^(-1), - diverging_obj_tol::T = -eps(T)^(-1), - cviol_tol::T = eps(T)^(-1), + diverging_iterates_tol::T = 1/eps(T), + diverging_obj_tol::T = -1/eps(T), + cviol_tol::T = 1/eps(T), diverging_max_iter::Int = 5, cviol_max_iter::Int = 5, verbose::Int = 0, From 7e88bd5addc18400af7c0c7246d48bbfebe5a881 Mon Sep 17 00:00:00 2001 From: Benjamin Pineau Date: Tue, 5 May 2026 11:30:41 -0400 Subject: [PATCH 5/5] Update src/AL_alg.jl Co-authored-by: Dominique --- src/AL_alg.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AL_alg.jl b/src/AL_alg.jl index 7da49404..da397cf1 100644 --- a/src/AL_alg.jl +++ b/src/AL_alg.jl @@ -235,8 +235,8 @@ function SolverCore.solve!( ) where {T, V} reset!(stats) - local diverging_iter::Int = zero(Int) - local cviol_iter::Int = zero(Int) + diverging_iter::Int = 0 + cviol_iter::Int = 0 # Retrieve workspace nlp = reg_nlp.model