Skip to content

Commit b9ca268

Browse files
committed
Fix unhelpful error message for missing constraint bounds
When a constrained optimization problem is defined with `cons` but without `lcons` and `ucons`, the error message was "no method matching keys(::Nothing)" which was confusing. Now it provides a clear error message that explains the missing parameters and includes an example. Fixes #959 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 779fd17 commit b9ca268

File tree

3 files changed

+61
-2
lines changed

3 files changed

+61
-2
lines changed

src/auglag.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ 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
8387
eq_inds = [cache.lcons[i] == cache.ucons[i] for i in eachindex(cache.lcons)]
8488
ineq_inds = (!).(eq_inds)
8589

src/lbfgsb.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ 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
101105
eq_inds = [cache.lcons[i] == cache.ucons[i] for i in eachindex(cache.lcons)]
102106
ineq_inds = (!).(eq_inds)
103107

@@ -124,7 +128,8 @@ function SciMLBase.__solve(cache::OptimizationCache{
124128
cache.f.cons(cons_tmp, θ)
125129
cons_tmp[eq_inds] .= cons_tmp[eq_inds] - cache.lcons[eq_inds]
126130
cons_tmp[ineq_inds] .= cons_tmp[ineq_inds] .- cache.ucons[ineq_inds]
127-
opt_state = Optimization.OptimizationState(u = θ, objective = x[1], p = cache.p, iter = iter_count[])
131+
opt_state = Optimization.OptimizationState(
132+
u = θ, objective = x[1], p = cache.p, iter = iter_count[])
128133
if cache.callback(opt_state, x...)
129134
error("Optimization halted by callback.")
130135
end
@@ -212,7 +217,8 @@ function SciMLBase.__solve(cache::OptimizationCache{
212217
_loss = function (θ)
213218
x = cache.f(θ, cache.p)
214219
iter_count[] += 1
215-
opt_state = Optimization.OptimizationState(u = θ, objective = x[1], p = cache.p, iter = iter_count[])
220+
opt_state = Optimization.OptimizationState(
221+
u = θ, objective = x[1], p = cache.p, iter = iter_count[])
216222
if cache.callback(opt_state, x...)
217223
error("Optimization halted by callback.")
218224
end

test/native.jl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,52 @@ optf1 = OptimizationFunction(loss, AutoSparseForwardDiff())
6161
prob1 = OptimizationProblem(optf1, rand(5), data)
6262
sol1 = solve(prob1, OptimizationOptimisers.Adam(), maxiters = 1000, callback = callback)
6363
@test sol1.objective < l0
64+
65+
# Test constraint bounds validation (issue #959)
66+
@testset "Constraint bounds validation" begin
67+
# Test case from issue #959 - missing lcons and ucons should give helpful error
68+
rosenbrock_constrained(u, p) = (p[1] - u[1])^2 + p[2] * (u[2] - u[1]^2)^2
69+
70+
function cons_missing_bounds!(out, x, p)
71+
out[1] = sum(x)
72+
end
73+
74+
optf_missing = OptimizationFunction(
75+
rosenbrock_constrained, AutoForwardDiff(), cons = cons_missing_bounds!)
76+
prob_missing = OptimizationProblem(optf_missing, [-1, 1.0], [1.0, 100.0])
77+
78+
# Test LBFGS
79+
@test_throws ArgumentError solve(prob_missing, Optimization.LBFGS())
80+
81+
# Verify the error message is helpful
82+
try
83+
solve(prob_missing, Optimization.LBFGS())
84+
catch e
85+
@test isa(e, ArgumentError)
86+
@test occursin("lcons", e.msg)
87+
@test occursin("ucons", e.msg)
88+
@test occursin("OptimizationProblem", e.msg)
89+
@test occursin("Example:", e.msg)
90+
end
91+
92+
# Test AugLag
93+
@test_throws ArgumentError solve(prob_missing, Optimization.AugLag())
94+
95+
# Verify the error message is helpful for AugLag too
96+
try
97+
solve(prob_missing, Optimization.AugLag())
98+
catch e
99+
@test isa(e, ArgumentError)
100+
@test occursin("lcons", e.msg)
101+
@test occursin("ucons", e.msg)
102+
@test occursin("OptimizationProblem", e.msg)
103+
@test occursin("Example:", e.msg)
104+
end
105+
106+
# Test that it works when lcons and ucons are provided
107+
prob_with_bounds = OptimizationProblem(
108+
optf_missing, [-1, 1.0], [1.0, 100.0], lcons = [-Inf], ucons = [0.0])
109+
# This should not throw an error (though it may not converge)
110+
sol = solve(prob_with_bounds, Optimization.LBFGS(), maxiters = 10)
111+
@test !isnothing(sol)
112+
end

0 commit comments

Comments
 (0)