Skip to content

[FileFormats] fix default lower bound in LP reader #1810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion src/FileFormats/LP/LP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -377,13 +377,15 @@ mutable struct _ReadCache
constraint_name::String
num_constraints::Int
name_to_variable::Dict{String,MOI.VariableIndex}
has_default_bound::Set{MOI.VariableIndex}
function _ReadCache()
return new(
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}[], 0.0),
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}[], 0.0),
"",
0,
Dict{String,MOI.VariableIndex}(),
Set{MOI.VariableIndex}(),
)
end
end
Expand All @@ -403,6 +405,10 @@ function _get_variable_from_name(model::Model, cache::_ReadCache, name::String)
end
x = MOI.add_variable(model)
MOI.set(model, MOI.VariableName(), x, name)
# By default, all variables have a lower bound of 0 unless otherwise
# specified.
MOI.add_constraint(model, x, MOI.GreaterThan(0.0))
push!(cache.has_default_bound, x)
cache.name_to_variable[name] = x
return x
end
Expand Down Expand Up @@ -596,7 +602,9 @@ function _parse_section(
)
tokens = _tokenize(line)
if length(tokens) == 2 && tokens[2] == "free"
return # Do nothing. Variable is free
x = _get_variable_from_name(model, cache, tokens[1])
_delete_default_lower_bound_if_present(model, cache, x)
return
end
lb, ub, name = -Inf, Inf, ""
if length(tokens) == 5
Expand Down Expand Up @@ -629,17 +637,35 @@ function _parse_section(
end
x = _get_variable_from_name(model, cache, name)
if lb == ub
_delete_default_lower_bound_if_present(model, cache, x)
MOI.add_constraint(model, x, MOI.EqualTo(lb))
elseif -Inf < lb < ub < Inf
_delete_default_lower_bound_if_present(model, cache, x)
MOI.add_constraint(model, x, MOI.Interval(lb, ub))
elseif -Inf < lb
_delete_default_lower_bound_if_present(model, cache, x)
MOI.add_constraint(model, x, MOI.GreaterThan(lb))
else
if ub < 0
# We only need to delete the default lower bound if the upper bound
# is less than 0.
_delete_default_lower_bound_if_present(model, cache, x)
end
MOI.add_constraint(model, x, MOI.LessThan(ub))
end
return
end

function _delete_default_lower_bound_if_present(model, cache, x)
if !(x in cache.has_default_bound)
return
end
c = MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{Float64}}(x.value)
MOI.delete(model, c)
delete!(cache.has_default_bound, x)
return
end

# _KW_INTEGER

function _parse_section(::typeof(_KW_INTEGER), model, cache, line)
Expand Down
36 changes: 36 additions & 0 deletions test/FileFormats/LP/LP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,42 @@ function test_read_maximum_length_error()
return
end

function test_default_bound()
io = IOBuffer()
write(io, "minimize\nobj: x + y")
seekstart(io)
model = LP.Model()
MOI.read!(io, model)
x = MOI.get(model, MOI.ListOfVariableIndices())
F, S = MOI.VariableIndex, MOI.GreaterThan{Float64}
c = [MOI.ConstraintIndex{F,S}(xi.value) for xi in x]
sets = MOI.get.(model, MOI.ConstraintSet(), c)
@test all(s -> s == MOI.GreaterThan(0.0), sets)
@test length(sets) == 2
return
end

function test_default_bound_double_bound()
io = IOBuffer()
write(io, "minimize\nobj: x\nsubject to\nbounds\n x <= -1\n x >= -2")
seekstart(io)
model = LP.Model()
MOI.read!(io, model)
x = first(MOI.get(model, MOI.ListOfVariableIndices()))
F = MOI.VariableIndex
@test MOI.get(
model,
MOI.ConstraintSet(),
MOI.ConstraintIndex{F,MOI.GreaterThan{Float64}}(x.value),
) == MOI.GreaterThan(-2.0)
@test MOI.get(
model,
MOI.ConstraintSet(),
MOI.ConstraintIndex{F,MOI.LessThan{Float64}}(x.value),
) == MOI.LessThan(-1.0)
return
end

function runtests()
for name in names(@__MODULE__, all = true)
if startswith("$(name)", "test_")
Expand Down