Skip to content

Commit d6db16b

Browse files
authored
[FileFormats.NL] add support for reading defined variables (#2938)
1 parent 9f3cfdb commit d6db16b

2 files changed

Lines changed: 76 additions & 60 deletions

File tree

src/FileFormats/NL/read.jl

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mutable struct _CacheModel
1717
objective::Expr
1818
sense::MOI.OptimizationSense
1919
complements_map::Dict{Int,Int}
20+
defined_variables::Dict{Int,Expr}
2021

2122
function _CacheModel()
2223
return new(
@@ -32,6 +33,7 @@ mutable struct _CacheModel
3233
:(),
3334
MOI.FEASIBILITY_SENSE,
3435
Dict{Int,Int}(),
36+
Dict{Int,Expr}(),
3537
)
3638
end
3739
end
@@ -179,7 +181,7 @@ function _parse_expr(io::IO, model::_CacheModel)
179181
elseif char == 'v'
180182
index = _next(Int, io, model)
181183
_read_til_newline(io, model)
182-
return MOI.VariableIndex(index + 1)
184+
return _to_variable(model, index)
183185
else
184186
@assert char == 'n'
185187
ret = _next(Float64, io, model)
@@ -188,6 +190,13 @@ function _parse_expr(io::IO, model::_CacheModel)
188190
end
189191
end
190192

193+
function _to_variable(model::_CacheModel, index::Int)
194+
if index >= length(model.variable_primal)
195+
return model.defined_variables[index]
196+
end
197+
return MOI.VariableIndex(index + 1)
198+
end
199+
191200
function _to_model(data::_CacheModel; use_nlp_block::Bool)
192201
model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
193202
x = MOI.add_variables(model, length(data.variable_primal))
@@ -394,12 +403,7 @@ function _parse_header(io::IO, model::_CacheModel)
394403
# them
395404
_read_til_newline(io, model)
396405
# Line 10
397-
# We don't support reading common subexpressions
398-
for _ in 1:5
399-
if _next(Int, io, model) > 0
400-
error("Unable to parse NL file : we don't support common exprs")
401-
end
402-
end
406+
# We support subexpressions, but we don't need to know the details yet.
403407
_read_til_newline(io, model)
404408
# ==========================================================================
405409
# Deal with the integrality of variables. This is quite complicated, so go
@@ -478,13 +482,24 @@ function _parse_section(io::IO, ::Val{'S'}, model::_CacheModel)
478482
return
479483
end
480484

481-
function _parse_section(::IO, ::Val{'V'}, ::_CacheModel)
482-
return error(
483-
"Unable to parse NL file: defined variable definitions ('V' sections)" *
484-
" are not yet supported. To request support, please open an issue at " *
485-
"https://github.com/jump-dev/MathOptInterface.jl with a reproducible " *
486-
"example.",
487-
)
485+
function _parse_section(io::IO, ::Val{'V'}, model::_CacheModel)
486+
i = _next(Int, io, model)
487+
j = _next(Int, io, model)
488+
k = _next(Int, io, model)
489+
_read_til_newline(io, model)
490+
affine_terms = Expr(:call, :+)
491+
for l in 1:j
492+
p_l = _to_variable(model, _next(Int, io, model))
493+
c_l = _next(Float64, io, model)
494+
_read_til_newline(io, model)
495+
push!(affine_terms.args, Expr(:call, :*, c_l, p_l))
496+
end
497+
expr = _parse_expr(io, model)
498+
if j > 0
499+
expr = Expr(:call, :+, affine_terms, expr)
500+
end
501+
model.defined_variables[i] = expr
502+
return
488503
end
489504

490505
function _parse_section(::IO, ::Val{'L'}, ::_CacheModel)

test/FileFormats/NL/test_read.jl

Lines changed: 47 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ end
5959

6060
function test_parse_expr()
6161
model = NL._CacheModel()
62+
NL._resize_variables(model, 4)
6263
io = IOBuffer()
6364
write(io, "o2\nv0\no2\nn2\no2\nv3\nv1\n")
6465
# (* x1 (* 2 (* x4 x2)))
@@ -72,6 +73,7 @@ end
7273

7374
function test_parse_expr_nary()
7475
model = NL._CacheModel()
76+
NL._resize_variables(model, 4)
7577
io = IOBuffer()
7678
write(io, "o54\n4\no5\nv0\nn2\no5\nv2\nn2\no5\nv3\nn2\no5\nv1\nn2\n")
7779
seekstart(io)
@@ -84,6 +86,7 @@ end
8486

8587
function test_parse_expr_minimum()
8688
model = NL._CacheModel()
89+
NL._resize_variables(model, 3)
8790
io = IOBuffer()
8891
write(io, "o11\n3\nv0\nv1\nv2\n")
8992
seekstart(io)
@@ -95,6 +98,7 @@ end
9598

9699
function test_parse_expr_maximum()
97100
model = NL._CacheModel()
101+
NL._resize_variables(model, 3)
98102
io = IOBuffer()
99103
write(io, "o12\n3\nv0\nv1\nv2\n")
100104
seekstart(io)
@@ -119,6 +123,7 @@ end
119123

120124
function test_parse_expr_atan2()
121125
model = NL._CacheModel()
126+
NL._resize_variables(model, 2)
122127
io = IOBuffer()
123128
write(io, "o48\nv0\nv1\n")
124129
seekstart(io)
@@ -130,6 +135,7 @@ end
130135

131136
function test_parse_expr_atan()
132137
model = NL._CacheModel()
138+
NL._resize_variables(model, 1)
133139
io = IOBuffer()
134140
write(io, "o49\nv0\n")
135141
seekstart(io)
@@ -160,26 +166,6 @@ function test_parse_header_assertion_errors()
160166
return
161167
end
162168

163-
function test_parse_header_common_expressions()
164-
model = NL._CacheModel()
165-
err = ErrorException(
166-
"Unable to parse NL file : we don't support common exprs",
167-
)
168-
for header in [
169-
"g3 1 1 0\n4 2 1 0 1 0\n2 1\n0 0\n4 0 0\n0 0 0 1\n0 0 0 2 0\n8 4\n0 0\n1 0 0 0 0\n",
170-
"g3 1 1 0\n4 2 1 0 1 0\n2 1\n0 0\n4 0 0\n0 0 0 1\n0 0 0 2 0\n8 4\n0 0\n0 1 0 0 0\n",
171-
"g3 1 1 0\n4 2 1 0 1 0\n2 1\n0 0\n4 0 0\n0 0 0 1\n0 0 0 2 0\n8 4\n0 0\n0 0 1 0 0\n",
172-
"g3 1 1 0\n4 2 1 0 1 0\n2 1\n0 0\n4 0 0\n0 0 0 1\n0 0 0 2 0\n8 4\n0 0\n0 0 0 1 0\n",
173-
"g3 1 1 0\n4 2 1 0 1 0\n2 1\n0 0\n4 0 0\n0 0 0 1\n0 0 0 2 0\n8 4\n0 0\n0 0 0 0 1\n",
174-
]
175-
io = IOBuffer()
176-
write(io, header)
177-
seekstart(io)
178-
@test_throws(err, NL._parse_header(io, model))
179-
end
180-
return
181-
end
182-
183169
function test_parse_y_error()
184170
model = NL._CacheModel()
185171
NL._resize_variables(model, 4)
@@ -378,19 +364,20 @@ end
378364

379365
function test_parse_C_J()
380366
model = NL._CacheModel()
367+
NL._resize_variables(model, 2)
381368
NL._resize_constraints(model, 1)
382369
io = IOBuffer()
383370
write(
384371
io,
385372
"""
386-
C0
387-
o2
388-
v0
389-
v1
390-
J0 2
391-
0 1.1
392-
1 2.2
393-
""",
373+
C0
374+
o2
375+
v0
376+
v1
377+
J0 2
378+
0 1.1
379+
1 2.2
380+
""",
394381
)
395382
seekstart(io)
396383
NL._parse_section(io, model)
@@ -403,19 +390,20 @@ end
403390

404391
function test_parse_J_C()
405392
model = NL._CacheModel()
393+
NL._resize_variables(model, 2)
406394
NL._resize_constraints(model, 1)
407395
io = IOBuffer()
408396
write(
409397
io,
410398
"""
411-
J0 2
412-
0 1.1
413-
1 2.2
414-
C0
415-
o2
416-
v0
417-
v1
418-
""",
399+
J0 2
400+
0 1.1
401+
1 2.2
402+
C0
403+
o2
404+
v0
405+
v1
406+
""",
419407
)
420408
seekstart(io)
421409
NL._parse_section(io, model)
@@ -535,18 +523,31 @@ end
535523

536524
function test_parse_V()
537525
model = NL._CacheModel()
526+
NL._resize_variables(model, 9)
538527
io = IOBuffer()
539-
write(io, "V")
540-
seekstart(io)
541-
@test_throws(
542-
ErrorException(
543-
"Unable to parse NL file: defined variable definitions ('V' sections)" *
544-
" are not yet supported. To request support, please open an issue at " *
545-
"https://github.com/jump-dev/MathOptInterface.jl with a reproducible " *
546-
"example.",
547-
),
548-
NL._parse_section(io, model),
528+
write(
529+
io,
530+
"""
531+
V9 0 0 #nl(t[2])
532+
o5 #^
533+
v0 #x[2]
534+
n2
535+
V10 2 0 #t[2]
536+
3 10
537+
4 11
538+
o0 # +
539+
v9 #nl(t[2])
540+
n1
541+
""",
549542
)
543+
seekstart(io)
544+
NL._parse_section(io, model)
545+
NL._parse_section(io, model)
546+
v = MOI.VariableIndex.(1:9)
547+
t1 = :($(v[1]) ^ 2.0)
548+
@test model.defined_variables[9] == t1
549+
@test model.defined_variables[10] ==
550+
:((10.0 * $(v[4]) + 11.0 * $(v[5])) + ($t1 + 1.0))
550551
return
551552
end
552553

0 commit comments

Comments
 (0)