Skip to content

Commit 1c9cadd

Browse files
More nlp tests [WIP] (#5)
* hs10 oracle tests * add hs14_oracle * normalize constraints test * add more tests nlp * start checking jacobian and hessian * fix bug hessian * fix bg consistency
1 parent 796df85 commit 1c9cadd

File tree

7 files changed

+226
-28
lines changed

7 files changed

+226
-28
lines changed

src/moi_nlp_model.jl

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ function NLPModels.jac_nln_coord!(nlp::MathOptNLPModel, x::AbstractVector, vals:
312312
s.x[i] = x[f.variables[i].value]
313313
end
314314
nnz_oracle = length(s.set.jacobian_structure)
315-
s.set.eval_jacobian(view(values, (offset + 1):(offset + nnz_oracle)), s.x)
315+
s.set.eval_jacobian(view(vals, (offset + 1):(offset + nnz_oracle)), s.x)
316316
offset += nnz_oracle
317317
end
318318
end
@@ -350,8 +350,8 @@ function NLPModels.jac_coord!(nlp::MathOptNLPModel, x::AbstractVector, vals::Abs
350350
index += qcon.nnzg
351351
end
352352
end
353+
offset = nlp.lincon.nnzj + nlp.quadcon.nnzj
353354
if nlp.meta.nnln > nlp.quadcon.nquad
354-
offset = nlp.lincon.nnzj + nlp.quadcon.nnzj
355355
ind_nnln = (offset + 1):(offset + nlp.nlcon.nnzj)
356356
MOI.eval_constraint_jacobian(nlp.eval, view(vals, ind_nnln), x)
357357
offset += nlp.nlcon.nnzj
@@ -360,7 +360,7 @@ function NLPModels.jac_coord!(nlp::MathOptNLPModel, x::AbstractVector, vals::Abs
360360
s.x[i] = x[f.variables[i].value]
361361
end
362362
nnz_oracle = length(s.set.jacobian_structure)
363-
s.set.eval_jacobian(view(values, (offset + 1):(offset + nnz_oracle)), s.x)
363+
s.set.eval_jacobian(view(vals, (offset + 1):(offset + nnz_oracle)), s.x)
364364
offset += nnz_oracle
365365
end
366366
end
@@ -568,42 +568,55 @@ function NLPModels.hess_coord!(
568568
)
569569
increment!(nlp, :neval_hess)
570570

571-
# Running index over Hessian nonzeros we've filled so far.
572-
index = 0
573-
574-
# Objective Hessian block
571+
# 1. Quadratic objective block (if any)
575572
if nlp.obj.type == "QUADRATIC"
576-
view(vals, 1:(nlp.obj.nnzh)) .= obj_weight .* nlp.obj.hessian.vals
577-
index += nlp.obj.nnzh
573+
view(vals, 1:nlp.obj.nnzh) .= obj_weight .* nlp.obj.hessian.vals
578574
end
579-
# Nonlinear objective Hessian block
575+
576+
# 2. Nonlinear block (objective + JuMP @NLconstraint)
580577
if (nlp.obj.type == "NONLINEAR") || (nlp.meta.nnln > nlp.quadcon.nquad)
581-
λ = view(y, (nlp.meta.nlin + nlp.quadcon.nquad + 1):(nlp.meta.ncon))
578+
# Multipliers for the JuMP nonlinear constraints (not the oracles)
579+
λ = view(
580+
y,
581+
(nlp.meta.nlin + nlp.quadcon.nquad + 1):(nlp.meta.ncon),
582+
)
583+
584+
first_nl = nlp.obj.nnzh + nlp.quadcon.nnzh + 1
585+
last_nl = nlp.obj.nnzh + nlp.quadcon.nnzh + nlp.nlcon.nnzh
586+
582587
MOI.eval_hessian_lagrangian(
583588
nlp.eval,
584-
view(vals, (nlp.obj.nnzh + nlp.quadcon.nnzh + 1):(nlp.meta.nnzh)),
589+
view(vals, first_nl:last_nl),
585590
x,
586591
obj_weight,
587592
λ,
588593
)
589594
end
590-
# Quadratic constraint Hessian blocks
595+
596+
# 3. Quadratic constraint Hessian blocks
591597
if nlp.quadcon.nquad > 0
598+
index = nlp.obj.nnzh
592599
for i = 1:(nlp.quadcon.nquad)
593600
qcon = nlp.quadcon.constraints[i]
594-
view(vals, (index + 1):(index + qcon.nnzh)) .= y[nlp.meta.nlin + i] .* qcon.A.vals
601+
ind = (index + 1):(index + qcon.nnzh)
602+
view(vals, ind) .= y[nlp.meta.nlin + i] .* qcon.A.vals
595603
index += qcon.nnzh
596604
end
597605
end
598-
# Oracle Hessian blocks
606+
607+
# 4. Oracle Hessian blocks are appended at the very end
599608
if !isempty(nlp.oracles_data.oracles)
609+
# Multipliers for oracle constraints only
600610
λ_oracle_all = view(
601611
y,
602612
(nlp.meta.nlin + nlp.quadcon.nquad + nlp.nlcon.nnln + 1):
603613
(nlp.meta.nlin + nlp.quadcon.nquad + nlp.nlcon.nnln + nlp.oracles_data.noracle),
604614
)
605615

606616
λ_offset = 0
617+
# Start after obj + quadcon + nonlinear block
618+
index = nlp.obj.nnzh + nlp.quadcon.nnzh + nlp.nlcon.nnzh
619+
607620
for (f, s) in nlp.oracles_data.oracles
608621
# build local x for this oracle
609622
for i in 1:s.set.input_dimension
@@ -622,7 +635,6 @@ function NLPModels.hess_coord!(
622635
end
623636
end
624637

625-
@assert index == nlp.meta.nnzh
626638
return vals
627639
end
628640

test/nlp_consistency.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
for problem in Symbol.(lowercase.(nlp_problems extra_nlp_oracle_problems))
1+
for problem in [nlp_problems; extra_nlp_oracle_problems]
22
@testset "Problem $problem" begin
33
nlp_manual = eval(Symbol(problem))()
44
problem_f = eval(Symbol(lowercase(problem)))

test/nlp_problems/hs100.jl

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,93 @@ function hs100(args...; kwargs...)
2222

2323
return nlp
2424
end
25+
26+
"HS100 model with the 2 middle constraints as VectorNonlinearOracle"
27+
function hs100_oracle(args...; kwargs...)
28+
model = Model()
29+
x0 = [1, 2, 0, 4, 0, 1, 1]
30+
@variable(model, x[i = 1:7], start = x0[i])
31+
32+
# 1st constraint: keep as NLconstraint
33+
@NLconstraint(model,
34+
127 - 2 * x[1]^2 - 3 * x[2]^4 - x[3] - 4 * x[4]^2 - 5 * x[5] 0
35+
)
36+
37+
# 2nd constraint as oracle:
38+
# Original: 282 - 7x1 - 3x2 - 10x3^2 - x4 + x5 ≥ 0
39+
# Canonical: f2(x) = -7x1 - 3x2 - 10x3^2 - x4 + x5, l2 = -282, u2 = +∞
40+
set2 = MOI.VectorNonlinearOracle(;
41+
dimension = 5, # inputs: x1, x2, x3, x4, x5
42+
l = [-282.0],
43+
u = [Inf],
44+
eval_f = (ret, xv) -> begin
45+
# xv = [x1, x2, x3, x4, x5]
46+
ret[1] = -7.0 * xv[1] - 3.0 * xv[2] - 10.0 * xv[3]^2 - xv[4] + xv[5]
47+
end,
48+
# ∇f2 = [-7, -3, -20*x3, -1, 1]
49+
jacobian_structure = [(1, 1), (1, 2), (1, 3), (1, 4), (1, 5)],
50+
eval_jacobian = (ret, xv) -> begin
51+
ret[1] = -7.0 # ∂f2/∂x1
52+
ret[2] = -3.0 # ∂f2/∂x2
53+
ret[3] = -20.0 * xv[3] # ∂f2/∂x3
54+
ret[4] = -1.0 # ∂f2/∂x4
55+
ret[5] = 1.0 # ∂f2/∂x5
56+
end,
57+
# Hessian of f2: only (3,3) = -20
58+
hessian_lagrangian_structure = [(3, 3)],
59+
eval_hessian_lagrangian = (ret, xv, μ) -> begin
60+
# Hessian of μ[1] * f2(x)
61+
ret[1] = μ[1] * (-20.0) # (3,3)
62+
end,
63+
)
64+
@constraint(model, [x[1], x[2], x[3], x[4], x[5]] in set2)
65+
66+
# 3rd constraint as oracle:
67+
# Original: -196 + 23x1 + x2^2 + 6x6^2 - 8x7 ≤ 0
68+
# Canonical: f3(x) = 23x1 + x2^2 + 6x6^2 - 8x7, l3 = -∞, u3 = 196
69+
set3 = MOI.VectorNonlinearOracle(;
70+
dimension = 4, # inputs: x1, x2, x6, x7
71+
l = [-Inf],
72+
u = [196.0],
73+
eval_f = (ret, xv) -> begin
74+
# xv = [x1, x2, x6, x7]
75+
ret[1] = 23.0 * xv[1] + xv[2]^2 + 6.0 * xv[3]^2 - 8.0 * xv[4]
76+
end,
77+
# ∇f3 = [23, 2*x2, 12*x6, -8]
78+
jacobian_structure = [(1, 1), (1, 2), (1, 3), (1, 4)],
79+
eval_jacobian = (ret, xv) -> begin
80+
ret[1] = 23.0 # ∂f3/∂x1
81+
ret[2] = 2.0 * xv[2] # ∂f3/∂x2
82+
ret[3] = 12.0 * xv[3] # ∂f3/∂x6
83+
ret[4] = -8.0 # ∂f3/∂x7
84+
end,
85+
# Hessian of f3: (2,2) = 2, (3,3) = 12
86+
hessian_lagrangian_structure = [(2, 2), (3, 3)],
87+
eval_hessian_lagrangian = (ret, xv, μ) -> begin
88+
# Hessian of μ[1] * f3(x)
89+
ret[1] = μ[1] * 2.0 # (2,2)
90+
ret[2] = μ[1] * 12.0 # (3,3)
91+
end,
92+
)
93+
@constraint(model, [x[1], x[2], x[6], x[7]] in set3)
94+
95+
# 4th constraint: keep as standard nonlinear
96+
@constraint(model,
97+
-4 * x[1]^2 - x[2]^2 + 3 * x[1] * x[2] - 2 * x[3]^2 - 5 * x[6] + 11 * x[7] 0
98+
)
99+
100+
# Objective: same as original
101+
@NLobjective(
102+
model,
103+
Min,
104+
(x[1] - 10)^2 +
105+
5 * (x[2] - 12)^2 +
106+
x[3]^4 +
107+
3 * (x[4] - 11)^2 +
108+
10 * x[5]^6 +
109+
7 * x[6]^2 +
110+
x[7]^4 - 4 * x[6] * x[7] - 10 * x[6] - 8 * x[7]
111+
)
112+
113+
return model
114+
end

test/nlp_problems/hs14.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,4 @@ function hs14_oracle()
6666
@constraint(model, x[1] - 2 * x[2] + 1 == 0)
6767

6868
return model
69-
end
69+
end

test/nlp_problems/hs61.jl

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,76 @@ function hs61(args...; kwargs...)
1010

1111
return nlp
1212
end
13+
14+
"HS61 model with both constraints as VectorNonlinearOracle"
15+
function hs61_oracle(args...; kwargs...)
16+
model = Model()
17+
@variable(model, x[i = 1:3], start = 0)
18+
19+
@NLobjective(model, Min,
20+
4 * x[1]^2 + 2 * x[2]^2 + 2 * x[3]^2 -
21+
33 * x[1] + 16 * x[2] - 24 * x[3]
22+
)
23+
24+
# First equality: 3*x1 - 2*x2^2 - 7 == 0
25+
# Canonical form for MathOptNLPModel:
26+
# f1(x) = 3*x1 - 2*x2^2
27+
# l1 = u1 = 7
28+
set1 = MOI.VectorNonlinearOracle(;
29+
dimension = 2, # inputs: x1, x2
30+
l = [7.0],
31+
u = [7.0],
32+
eval_f = (ret, xv) -> begin
33+
# xv = [x1, x2]
34+
ret[1] = 3.0 * xv[1] - 2.0 * xv[2]^2
35+
end,
36+
# ∇f1 = [3, -4*x2]
37+
jacobian_structure = [(1, 1), (1, 2)],
38+
eval_jacobian = (ret, xv) -> begin
39+
ret[1] = 3.0 # d f1 / d x1
40+
ret[2] = -4.0 * xv[2] # d f1 / d x2
41+
end,
42+
# Hessian of f1:
43+
# ∂²f1/∂x1² = 0
44+
# ∂²f1/∂x2² = -4
45+
# (mixed derivatives are 0)
46+
hessian_lagrangian_structure = [(2, 2)],
47+
eval_hessian_lagrangian = (ret, xv, μ) -> begin
48+
# Hessian of μ[1] * f1(x)
49+
ret[1] = μ[1] * (-4.0) # (2,2)
50+
end,
51+
)
52+
53+
# Second equality: 4*x1 - x3^2 - 11 == 0
54+
# Canonical form:
55+
# f2(x) = 4*x1 - x3^2
56+
# l2 = u2 = 11
57+
set2 = MOI.VectorNonlinearOracle(;
58+
dimension = 2, # inputs: x1, x3
59+
l = [11.0],
60+
u = [11.0],
61+
eval_f = (ret, xv) -> begin
62+
# xv = [x1, x3]
63+
ret[1] = 4.0 * xv[1] - xv[2]^2
64+
end,
65+
# ∇f2 = [4, -2*x3]
66+
jacobian_structure = [(1, 1), (1, 2)],
67+
eval_jacobian = (ret, xv) -> begin
68+
ret[1] = 4.0 # d f2 / d x1
69+
ret[2] = -2.0 * xv[2] # d f2 / d x3
70+
end,
71+
# Hessian of f2:
72+
# ∂²f2/∂x1² = 0
73+
# ∂²f2/∂x3² = -2
74+
hessian_lagrangian_structure = [(2, 2)],
75+
eval_hessian_lagrangian = (ret, xv, μ) -> begin
76+
ret[1] = μ[1] * (-2.0) # (2,2)
77+
end,
78+
)
79+
80+
# Equality constraints as oracles
81+
@constraint(model, [x[1], x[2]] in set1)
82+
@constraint(model, [x[1], x[3]] in set2)
83+
84+
return model
85+
end

test/runtests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ nls_problems = NLPModelsTest.nls_problems
88
extra_nlp_problems = ["nohesspb", "hs61", "hs100", "hs219", "quadcon", "operatorspb", "nf"]
99
extra_nls_problems = ["nlsnohesspb", "HS30", "HS43", "MGH07", "nlsqc"]
1010

11-
extra_nlp_oracle_problems = ["hs10_oracle", "hs14_oracle"]
11+
extra_nlp_oracle_problems = ["hs10_oracle", "hs14_oracle", "hs61_oracle", "hs100_oracle"]
1212

1313
for problem in lowercase.(nlp_problems extra_nlp_problems)
1414
include(joinpath("nlp_problems", "$problem.jl"))

test/test_moi_nlp_oracle.jl

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,43 @@
22
prob_no_oracle = replace(prob, "_oracle" => "")
33
prob_fn_no_oracle = eval(Symbol(prob_no_oracle))
44
prob_fn = eval(Symbol(prob))
5-
nlp_no_oracle = MathOptNLPModel(prob_fn_no_oracle(), hessian = true)
6-
nlp_with_oracle = MathOptNLPModel(prob_fn(), hessian = true)
5+
6+
nlp_no_oracle = MathOptNLPModel(prob_fn_no_oracle(), hessian = true)
7+
nlp_with_oracle = MathOptNLPModel(prob_fn(), hessian = true)
8+
79
n = nlp_no_oracle.meta.nvar
810
m = nlp_no_oracle.meta.ncon
911
x = nlp_no_oracle.meta.x0
10-
fx_no_oracle = obj(nlp_no_oracle, x)
12+
13+
# Objective value
14+
fx_no_oracle = obj(nlp_no_oracle, x)
1115
fx_with_oracle = obj(nlp_with_oracle, x)
1216
@test isapprox(fx_no_oracle, fx_with_oracle; atol = 1e-8, rtol = 1e-8)
13-
ngx_no_oracle = grad(nlp_no_oracle, x)
17+
18+
# Gradient of objective
19+
ngx_no_oracle = grad(nlp_no_oracle, x)
1420
ngx_with_oracle = grad(nlp_with_oracle, x)
1521
@test isapprox(ngx_no_oracle, ngx_with_oracle; atol = 1e-8, rtol = 1e-8)
16-
if m > 0
17-
ncx_no_oracle = cons(nlp_no_oracle, x)
18-
ncx_with_oracle = cons(nlp_with_oracle, x)
19-
@test isapprox(ncx_no_oracle, ncx_with_oracle; atol = 1e-8, rtol = 1e-8)
20-
end
22+
23+
# Constraint values (up to ordering)
24+
ncx_no_oracle = cons(nlp_no_oracle, x)
25+
ncx_with_oracle = cons(nlp_with_oracle, x)
26+
@test isapprox(sort(ncx_no_oracle), sort(ncx_with_oracle);
27+
atol = 1e-8, rtol = 1e-8)
28+
29+
# Jacobian: compare J'J, which is invariant to row permutations
30+
J_no = jac(nlp_no_oracle, x)
31+
J_with = jac(nlp_with_oracle, x)
32+
33+
G_no = Matrix(J_no)' * Matrix(J_no)
34+
G_with = Matrix(J_with)' * Matrix(J_with)
35+
36+
@test isapprox(G_no, G_with; atol = 1e-8, rtol = 1e-8)
37+
38+
# Hessian of the objective: use y = 0 so constraints don't enter
39+
λ = zeros(m)
40+
H_no = hess(nlp_no_oracle, x, λ)
41+
H_with = hess(nlp_with_oracle, x, λ)
42+
43+
@test isapprox(Matrix(H_no), Matrix(H_with); atol = 1e-8, rtol = 1e-8)
2144
end

0 commit comments

Comments
 (0)