Skip to content

Commit c584a44

Browse files
committed
Move constraint validation to common utility function
Following user feedback, the constraint validation is now done uniformly across all solvers through a common utility function `_check_constrained_problem` in utils.jl. This ensures consistent error messages and behavior across all constraint-supporting solvers. Changes: - Added _check_constrained_problem utility function in utils.jl - Updated LBFGS and AugLag to use the utility function - Added validation to all other constraint-supporting solvers: - OptimizationNLopt - OptimizationOptimJL (IPNewton) - OptimizationPRIMA (COBYLA, LINCOA) - OptimizationEvolutionary - OptimizationSciPy (ScipyMinimize, ScipyShgo) - OptimizationMOI (NLP) - OptimizationNOMAD This provides a more maintainable solution that ensures all solvers give the same helpful error message when constraint bounds are missing. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent b9ca268 commit c584a44

File tree

11 files changed

+61
-14
lines changed

11 files changed

+61
-14
lines changed

Project.toml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
77
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
88
ConsoleProgressMonitor = "88cd18e8-d9cc-4ea6-8889-5259c0d15c8b"
99
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
10+
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
1011
LBFGSB = "5be7bae1-8223-5378-bac3-9e7378a2f6e6"
1112
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1213
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
@@ -43,6 +44,7 @@ Lux = "1.12.4"
4344
MLUtils = "0.4"
4445
ModelingToolkit = "9"
4546
Optim = ">= 1.4.1"
47+
Optimisers = ">= 0.2.5"
4648
OptimizationBase = "2"
4749
OptimizationMOI = "0.5"
4850
OptimizationOptimJL = "0.4"
@@ -51,7 +53,7 @@ OrdinaryDiffEqTsit5 = "1"
5153
Pkg = "1"
5254
Printf = "1.10"
5355
ProgressLogging = "0.1"
54-
Random = "1.10"
56+
Random = "1.10"
5557
Reexport = "1.2"
5658
ReverseDiff = "1"
5759
SafeTestsets = "0.1"
@@ -63,7 +65,6 @@ Symbolics = "6"
6365
TerminalLoggers = "0.1"
6466
Test = "1.10"
6567
Tracker = "0.2"
66-
Optimisers = ">= 0.2.5"
6768
Zygote = "0.6, 0.7"
6869
julia = "1.10"
6970

@@ -101,7 +102,4 @@ Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c"
101102
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
102103

103104
[targets]
104-
test = ["Aqua", "BenchmarkTools", "Boltz", "ComponentArrays", "DiffEqFlux", "Enzyme", "FiniteDiff", "Flux", "ForwardDiff",
105-
"Ipopt", "IterTools", "Lux", "MLUtils", "ModelingToolkit", "Optim", "OptimizationMOI", "OptimizationOptimJL", "OptimizationOptimisers",
106-
"OrdinaryDiffEqTsit5", "Pkg", "Random", "ReverseDiff", "SafeTestsets", "SciMLSensitivity", "SparseArrays", "SparseDiffTools",
107-
"Symbolics", "Test", "Tracker", "Zygote"]
105+
test = ["Aqua", "BenchmarkTools", "Boltz", "ComponentArrays", "DiffEqFlux", "Enzyme", "FiniteDiff", "Flux", "ForwardDiff", "Ipopt", "IterTools", "Lux", "MLUtils", "ModelingToolkit", "Optim", "OptimizationMOI", "OptimizationOptimJL", "OptimizationOptimisers", "OrdinaryDiffEqTsit5", "Pkg", "Random", "ReverseDiff", "SafeTestsets", "SciMLSensitivity", "SparseArrays", "SparseDiffTools", "Symbolics", "Test", "Tracker", "Zygote"]

lib/OptimizationEvolutionary/src/OptimizationEvolutionary.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ function SciMLBase.__solve(cache::OptimizationCache{
9797
P,
9898
C
9999
}
100+
# Check constraint validation if constraints are present
101+
if !isnothing(cache.f.cons)
102+
Optimization._check_constrained_problem(cache)
103+
end
104+
100105
local x, cur, state
101106

102107
function _cb(trace)

lib/OptimizationMOI/src/nlp.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,14 @@ function _add_moi_variables!(opt_setup, evaluator::MOIOptimizationNLPEvaluator)
518518
end
519519

520520
function SciMLBase.__solve(cache::MOIOptimizationNLPCache)
521+
# Check constraint validation for MOI NLP optimizer
522+
if !isnothing(cache.evaluator.f.cons)
523+
if isnothing(cache.evaluator.lcons) || isnothing(cache.evaluator.ucons)
524+
throw(ArgumentError("Constrained optimization problem requires both `lcons` and `ucons` to be provided to OptimizationProblem. " *
525+
"Example: OptimizationProblem(optf, u0, p; lcons=[-Inf], ucons=[0.0])"))
526+
end
527+
end
528+
521529
maxiters = Optimization._check_and_convert_maxiters(cache.solver_args.maxiters)
522530
maxtime = Optimization._check_and_convert_maxtime(cache.solver_args.maxtime)
523531
opt_setup = __map_optimizer_args(cache,

lib/OptimizationNLopt/src/OptimizationNLopt.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ function SciMLBase.__solve(cache::OptimizationCache{
152152
P,
153153
C
154154
}
155+
# Check constraint validation if this solver supports constraints
156+
if SciMLBase.allowsconstraints(cache.opt)
157+
Optimization._check_constrained_problem(cache)
158+
end
159+
155160
local x
156161

157162
_loss = function (θ)

lib/OptimizationNOMAD/src/OptimizationNOMAD.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ function SciMLBase.__solve(prob::OptimizationProblem, opt::NOMADOpt;
5252
reltol::Union{Number, Nothing} = nothing,
5353
cons_method = ExtremeBarrierMethod,
5454
kwargs...)
55+
# Check constraint validation for NOMAD
56+
if !isnothing(prob.f.cons)
57+
if isnothing(prob.lcons) || isnothing(prob.ucons)
58+
throw(ArgumentError("Constrained optimization problem requires both `lcons` and `ucons` to be provided to OptimizationProblem. " *
59+
"Example: OptimizationProblem(optf, u0, p; lcons=[-Inf], ucons=[0.0])"))
60+
end
61+
end
62+
5563
local x
5664

5765
maxiters = Optimization._check_and_convert_maxiters(maxiters)

lib/OptimizationOptimJL/src/OptimizationOptimJL.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,9 @@ function SciMLBase.__solve(cache::OptimizationCache{
344344
D,
345345
P
346346
}
347+
# Check constraint validation for constrained optimizers
348+
Optimization._check_constrained_problem(cache)
349+
347350
local x, cur, state
348351

349352
function _cb(trace)

lib/OptimizationPRIMA/src/OptimizationPRIMA.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ function SciMLBase.__solve(cache::Optimization.OptimizationCache{
129129
P,
130130
C
131131
}
132+
# Check constraint validation if this solver supports constraints
133+
if SciMLBase.allowsconstraints(cache.opt)
134+
Optimization._check_constrained_problem(cache)
135+
end
136+
132137
iter = 0
133138
_loss = function (θ)
134139
x = cache.f(θ, cache.p)

lib/OptimizationSciPy/src/OptimizationSciPy.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,11 @@ end
308308

309309
function SciMLBase.__solve(cache::OptimizationCache{F,RC,LB,UB,LC,UC,S,O,D,P,C}) where
310310
{F,RC,LB,UB,LC,UC,S,O<:ScipyMinimize,D,P,C}
311+
# Check constraint validation if this solver supports constraints
312+
if SciMLBase.allowsconstraints(cache.opt)
313+
Optimization._check_constrained_problem(cache)
314+
end
315+
311316
local cons_cache = nothing
312317
if !isnothing(cache.f.cons) && !isnothing(cache.lcons)
313318
cons_cache = zeros(eltype(cache.u0), length(cache.lcons))
@@ -1193,6 +1198,9 @@ end
11931198

11941199
function SciMLBase.__solve(cache::OptimizationCache{F,RC,LB,UB,LC,UC,S,O,D,P,C}) where
11951200
{F,RC,LB,UB,LC,UC,S,O<:ScipyShgo,D,P,C}
1201+
# Check constraint validation for ScipyShgo
1202+
Optimization._check_constrained_problem(cache)
1203+
11961204
local cons_cache = nothing
11971205
if !isnothing(cache.f.cons) && !isnothing(cache.lcons)
11981206
cons_cache = zeros(eltype(cache.u0), length(cache.lcons))

src/auglag.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,7 @@ function SciMLBase.__solve(cache::OptimizationCache{
8080
solver_kwargs = __map_optimizer_args(cache, cache.opt; maxiters, cache.solver_args...)
8181

8282
if !isnothing(cache.f.cons)
83-
if isnothing(cache.lcons) || isnothing(cache.ucons)
84-
throw(ArgumentError("Constrained optimization problem requires both `lcons` and `ucons` to be provided to OptimizationProblem. " *
85-
"Example: OptimizationProblem(optf, u0, p; lcons=[-Inf], ucons=[0.0])"))
86-
end
83+
_check_constrained_problem(cache)
8784
eq_inds = [cache.lcons[i] == cache.ucons[i] for i in eachindex(cache.lcons)]
8885
ineq_inds = (!).(eq_inds)
8986

src/lbfgsb.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,7 @@ function SciMLBase.__solve(cache::OptimizationCache{
9898
solver_kwargs = __map_optimizer_args(cache, cache.opt; maxiters, cache.solver_args...)
9999

100100
if !isnothing(cache.f.cons)
101-
if isnothing(cache.lcons) || isnothing(cache.ucons)
102-
throw(ArgumentError("Constrained optimization problem requires both `lcons` and `ucons` to be provided to OptimizationProblem. " *
103-
"Example: OptimizationProblem(optf, u0, p; lcons=[-Inf], ucons=[0.0])"))
104-
end
101+
_check_constrained_problem(cache)
105102
eq_inds = [cache.lcons[i] == cache.ucons[i] for i in eachindex(cache.lcons)]
106103
ineq_inds = (!).(eq_inds)
107104

0 commit comments

Comments
 (0)