Skip to content

Commit 87a2ee5

Browse files
authored
[FileFormats] fix default lower bound in LP reader (#1810)
1 parent 8e8006e commit 87a2ee5

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

src/FileFormats/LP/LP.jl

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,13 +377,15 @@ mutable struct _ReadCache
377377
constraint_name::String
378378
num_constraints::Int
379379
name_to_variable::Dict{String,MOI.VariableIndex}
380+
has_default_bound::Set{MOI.VariableIndex}
380381
function _ReadCache()
381382
return new(
382383
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}[], 0.0),
383384
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}[], 0.0),
384385
"",
385386
0,
386387
Dict{String,MOI.VariableIndex}(),
388+
Set{MOI.VariableIndex}(),
387389
)
388390
end
389391
end
@@ -403,6 +405,10 @@ function _get_variable_from_name(model::Model, cache::_ReadCache, name::String)
403405
end
404406
x = MOI.add_variable(model)
405407
MOI.set(model, MOI.VariableName(), x, name)
408+
# By default, all variables have a lower bound of 0 unless otherwise
409+
# specified.
410+
MOI.add_constraint(model, x, MOI.GreaterThan(0.0))
411+
push!(cache.has_default_bound, x)
406412
cache.name_to_variable[name] = x
407413
return x
408414
end
@@ -596,7 +602,9 @@ function _parse_section(
596602
)
597603
tokens = _tokenize(line)
598604
if length(tokens) == 2 && tokens[2] == "free"
599-
return # Do nothing. Variable is free
605+
x = _get_variable_from_name(model, cache, tokens[1])
606+
_delete_default_lower_bound_if_present(model, cache, x)
607+
return
600608
end
601609
lb, ub, name = -Inf, Inf, ""
602610
if length(tokens) == 5
@@ -629,17 +637,35 @@ function _parse_section(
629637
end
630638
x = _get_variable_from_name(model, cache, name)
631639
if lb == ub
640+
_delete_default_lower_bound_if_present(model, cache, x)
632641
MOI.add_constraint(model, x, MOI.EqualTo(lb))
633642
elseif -Inf < lb < ub < Inf
643+
_delete_default_lower_bound_if_present(model, cache, x)
634644
MOI.add_constraint(model, x, MOI.Interval(lb, ub))
635645
elseif -Inf < lb
646+
_delete_default_lower_bound_if_present(model, cache, x)
636647
MOI.add_constraint(model, x, MOI.GreaterThan(lb))
637648
else
649+
if ub < 0
650+
# We only need to delete the default lower bound if the upper bound
651+
# is less than 0.
652+
_delete_default_lower_bound_if_present(model, cache, x)
653+
end
638654
MOI.add_constraint(model, x, MOI.LessThan(ub))
639655
end
640656
return
641657
end
642658

659+
function _delete_default_lower_bound_if_present(model, cache, x)
660+
if !(x in cache.has_default_bound)
661+
return
662+
end
663+
c = MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{Float64}}(x.value)
664+
MOI.delete(model, c)
665+
delete!(cache.has_default_bound, x)
666+
return
667+
end
668+
643669
# _KW_INTEGER
644670

645671
function _parse_section(::typeof(_KW_INTEGER), model, cache, line)

test/FileFormats/LP/LP.jl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,42 @@ function test_read_maximum_length_error()
408408
return
409409
end
410410

411+
function test_default_bound()
412+
io = IOBuffer()
413+
write(io, "minimize\nobj: x + y")
414+
seekstart(io)
415+
model = LP.Model()
416+
MOI.read!(io, model)
417+
x = MOI.get(model, MOI.ListOfVariableIndices())
418+
F, S = MOI.VariableIndex, MOI.GreaterThan{Float64}
419+
c = [MOI.ConstraintIndex{F,S}(xi.value) for xi in x]
420+
sets = MOI.get.(model, MOI.ConstraintSet(), c)
421+
@test all(s -> s == MOI.GreaterThan(0.0), sets)
422+
@test length(sets) == 2
423+
return
424+
end
425+
426+
function test_default_bound_double_bound()
427+
io = IOBuffer()
428+
write(io, "minimize\nobj: x\nsubject to\nbounds\n x <= -1\n x >= -2")
429+
seekstart(io)
430+
model = LP.Model()
431+
MOI.read!(io, model)
432+
x = first(MOI.get(model, MOI.ListOfVariableIndices()))
433+
F = MOI.VariableIndex
434+
@test MOI.get(
435+
model,
436+
MOI.ConstraintSet(),
437+
MOI.ConstraintIndex{F,MOI.GreaterThan{Float64}}(x.value),
438+
) == MOI.GreaterThan(-2.0)
439+
@test MOI.get(
440+
model,
441+
MOI.ConstraintSet(),
442+
MOI.ConstraintIndex{F,MOI.LessThan{Float64}}(x.value),
443+
) == MOI.LessThan(-1.0)
444+
return
445+
end
446+
411447
function runtests()
412448
for name in names(@__MODULE__, all = true)
413449
if startswith("$(name)", "test_")

0 commit comments

Comments
 (0)