Skip to content

Commit a61f947

Browse files
committed
[FileFormats.NL] add support for reading MOI.Complements
1 parent 60e2e57 commit a61f947

5 files changed

Lines changed: 252 additions & 3 deletions

File tree

src/FileFormats/NL/NL.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,8 +571,13 @@ function Base.write(io::IO, model::Model)
571571
# Line 3: nonlinear constraints, objectives
572572
# Notes:
573573
# * We assume there is always one objective, even if it is just `min 0`.
574+
# * `Writing .nl Files` lies! There are four extra integers here
575+
# * Number of linear complementarity constraints
576+
# * Number of nonlinear complementarity constraints
577+
# * nd: I have no idea
578+
# * nzlb: I have no idea
574579
n_nlcon = length(model.g)
575-
println(io, " ", n_nlcon, " ", 1)
580+
println(io, " ", n_nlcon, " 1 0 0 0 0")
576581

577582
# Line 4: network constraints: nonlinear, linear
578583
# Notes:

src/FileFormats/NL/read.jl

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ mutable struct _CacheModel
1616
constraint_upper::Vector{Float64}
1717
objective::Expr
1818
sense::MOI.OptimizationSense
19+
complements_map::Dict{Int,Int}
20+
1921
function _CacheModel()
2022
return new(
2123
false,
@@ -29,6 +31,7 @@ mutable struct _CacheModel
2931
Float64[],
3032
:(),
3133
MOI.FEASIBILITY_SENSE,
34+
Dict{Int,Int}(),
3235
)
3336
end
3437
end
@@ -208,6 +211,7 @@ function _to_model(data::_CacheModel; use_nlp_block::Bool)
208211
MOI.set(model, MOI.ObjectiveSense(), data.sense)
209212
end
210213
if use_nlp_block
214+
@assert isempty(data.complements_map)
211215
nlp = MOI.Nonlinear.Model()
212216
if data.objective != :()
213217
MOI.Nonlinear.set_objective(nlp, data.objective)
@@ -234,6 +238,16 @@ function _to_model(data::_CacheModel; use_nlp_block::Bool)
234238
MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj)
235239
end
236240
for (i, expr) in enumerate(data.constraints)
241+
if haskey(data.complements_map, i)
242+
g = MOI.Utilities.operate(
243+
vcat,
244+
Float64,
245+
_expr_to_function(expr),
246+
x[data.complements_map[i]],
247+
)
248+
MOI.add_constraint(model, g, MOI.Complements(2))
249+
continue
250+
end
237251
lb, ub = data.constraint_lower[i], data.constraint_upper[i]
238252
f = _expr_to_function(expr)
239253
if lb == ub
@@ -551,11 +565,15 @@ function _parse_section(io::IO, ::Val{'r'}, model::_CacheModel)
551565
model.constraint_lower[i] = _next(Float64, io, model)
552566
elseif type == Cchar('3')
553567
# Free constraint
554-
else
555-
@assert type == Cchar('4')
568+
elseif type == Cchar('4')
556569
value = _next(Float64, io, model)
557570
model.constraint_lower[i] = value
558571
model.constraint_upper[i] = value
572+
else
573+
@assert type == Cchar('5')
574+
_ = _next(Int, io, model) # k
575+
j = _next(Int, io, model) # variable i-1
576+
push!(model.complements_map, i => j)
559577
end
560578
_read_til_newline(io, model)
561579
end
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
g3 1 1 0 # problem josephy
2+
4 4 0 0 0 # vars, constraints, objectives, ranges, eqns
3+
4 0 0 4 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb
4+
0 0 # network constraints: nonlinear, linear
5+
2 0 0 # nonlinear vars in constraints, objectives, both
6+
0 0 0 1 # linear network variables; functions; arith, flags
7+
0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o)
8+
16 0 # nonzeros in Jacobian, gradients
9+
0 0 # max name lengths: constraints, variables
10+
0 0 0 0 0 # common exprs: b,c,o,c1,o1
11+
b
12+
2 0
13+
2 0
14+
2 0
15+
2 0
16+
r
17+
5 1 1
18+
5 1 2
19+
5 1 3
20+
5 1 4
21+
C0
22+
o54
23+
4
24+
o2
25+
o2
26+
n3
27+
v0
28+
v0
29+
o2
30+
o2
31+
n2
32+
v0
33+
v1
34+
o2
35+
o2
36+
n2
37+
v1
38+
v1
39+
n-6
40+
C1
41+
o54
42+
3
43+
o2
44+
o2
45+
n2
46+
v0
47+
v0
48+
o2
49+
v1
50+
v1
51+
n-2
52+
C2
53+
o54
54+
4
55+
o2
56+
o2
57+
n3
58+
v0
59+
v0
60+
o2
61+
v0
62+
v1
63+
o2
64+
o2
65+
n2
66+
v1
67+
v1
68+
n-1
69+
C3
70+
o54
71+
3
72+
o2
73+
v0
74+
v0
75+
o2
76+
o2
77+
n3
78+
v1
79+
v1
80+
n-3
81+
k3
82+
4
83+
8
84+
12
85+
J0 4
86+
0 0
87+
1 0
88+
2 1
89+
3 3
90+
J1 4
91+
0 1
92+
1 0
93+
2 3
94+
3 2
95+
J2 4
96+
0 0
97+
1 0
98+
2 2
99+
3 3
100+
J3 4
101+
0 0
102+
1 0
103+
2 2
104+
3 3

test/FileFormats/NL/data/sample.nl

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
g3 0 1 0 # problem sample
2+
8 8 0 0 4 # vars, constraints, objectives, ranges, eqns
3+
0 0 4 0 0 4 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb
4+
0 0 # network constraints: nonlinear, linear
5+
0 0 0 # nonlinear vars in constraints, objectives, both
6+
0 0 0 1 # linear network variables; functions; arith, flags
7+
0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o)
8+
20 0 # nonzeros in Jacobian, gradients
9+
0 0 # max name lengths: constraints, variables
10+
0 0 0 0 0 # common exprs: b,c,o,c1,o1
11+
b
12+
0 0 10
13+
0 0 10
14+
0 0 10
15+
0 0 10
16+
2 -2
17+
2 -2
18+
2 2
19+
2 6
20+
r
21+
4 0
22+
5 1 5
23+
4 0
24+
5 1 6
25+
4 0
26+
5 1 7
27+
4 0
28+
5 1 8
29+
C0
30+
n0
31+
C1
32+
n0
33+
C2
34+
n0
35+
C3
36+
n0
37+
C4
38+
n0
39+
C5
40+
n0
41+
C6
42+
n0
43+
C7
44+
n0
45+
k7
46+
3
47+
6
48+
11
49+
16
50+
17
51+
18
52+
19
53+
J0 3
54+
2 -1
55+
3 -1
56+
4 -1
57+
J1 1
58+
0 1
59+
J2 3
60+
2 1
61+
3 -2
62+
5 -1
63+
J3 1
64+
1 1
65+
J4 5
66+
0 1
67+
1 -1
68+
2 2
69+
3 -2
70+
6 -1
71+
J5 1
72+
2 1
73+
J6 5
74+
0 1
75+
1 2
76+
2 -2
77+
3 4
78+
7 -1
79+
J7 1
80+
3 1

test/FileFormats/NL/read.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,48 @@ function test_try_scalar_affine_function()
10901090
return
10911091
end
10921092

1093+
function test_complements_sample()
1094+
model = NL.Model(; use_nlp_block = false)
1095+
open(joinpath(@__DIR__, "data", "sample.nl"), "r") do io
1096+
return read!(io, model)
1097+
end
1098+
F, S = MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}
1099+
@test MOI.get(model, MOI.NumberOfConstraints{F,S}()) == 4
1100+
x = MOI.get(model, MOI.ListOfVariableIndices())
1101+
@test length(x) == 8
1102+
F, S = MOI.VectorAffineFunction{Float64}, MOI.Complements
1103+
c = MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
1104+
@test length(c) == 4
1105+
for i in 1:4
1106+
@test isapprox(
1107+
MOI.get(model, MOI.ConstraintFunction(), c[i]),
1108+
MOI.Utilities.vectorize([1.0 * x[i], 1.0 * x[i+4]]),
1109+
)
1110+
end
1111+
return
1112+
end
1113+
1114+
function test_complements_josephy()
1115+
model = NL.Model(; use_nlp_block = false)
1116+
open(joinpath(@__DIR__, "data", "josephy.nl"), "r") do io
1117+
return read!(io, model)
1118+
end
1119+
x = MOI.get(model, MOI.ListOfVariableIndices())
1120+
@test length(x) == 4
1121+
F, S = MOI.VectorNonlinearFunction, MOI.Complements
1122+
c = MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
1123+
@test length(c) == 4
1124+
for i in 1:4
1125+
f = MOI.get(model, MOI.ConstraintFunction(), c[i])
1126+
@test isapprox(f.rows[2], MOI.ScalarNonlinearFunction(:+, Any[x[i]]))
1127+
end
1128+
ret = MOI.get(model, MOI.ListOfConstraintTypesPresent())
1129+
@test length(ret) == 2
1130+
@test (MOI.VariableIndex, MOI.GreaterThan{Float64}) in ret
1131+
@test (F, S) in ret
1132+
return
1133+
end
1134+
10931135
end
10941136

10951137
TestNonlinearRead.runtests()

0 commit comments

Comments
 (0)