From 2dcd886413de2fd8664c501f470e80531be01a5f Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 21 Apr 2022 16:14:19 +1200 Subject: [PATCH 1/5] [FileFormats] fix default lower bound in LP reader --- src/FileFormats/LP/LP.jl | 25 +++++++++++++++++++++++++ test/FileFormats/LP/LP.jl | 15 +++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/FileFormats/LP/LP.jl b/src/FileFormats/LP/LP.jl index efce4b908f..8cde7c98b9 100644 --- a/src/FileFormats/LP/LP.jl +++ b/src/FileFormats/LP/LP.jl @@ -377,6 +377,7 @@ 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), @@ -384,6 +385,7 @@ mutable struct _ReadCache "", 0, Dict{String,MOI.VariableIndex}(), + Set{MOI.VariableIndex}(), ) end end @@ -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 @@ -629,17 +635,36 @@ 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) + MOI.delete(model, c) + 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) diff --git a/test/FileFormats/LP/LP.jl b/test/FileFormats/LP/LP.jl index ad8b986c03..8efd1b55ca 100644 --- a/test/FileFormats/LP/LP.jl +++ b/test/FileFormats/LP/LP.jl @@ -408,6 +408,21 @@ 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 runtests() for name in names(@__MODULE__, all = true) if startswith("$(name)", "test_") From 32e503b0c8f97c4b5e214ec2488cf6d6949b14e6 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 21 Apr 2022 16:18:16 +1200 Subject: [PATCH 2/5] Fix formatting --- test/FileFormats/LP/LP.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/FileFormats/LP/LP.jl b/test/FileFormats/LP/LP.jl index 8efd1b55ca..28b498b321 100644 --- a/test/FileFormats/LP/LP.jl +++ b/test/FileFormats/LP/LP.jl @@ -415,7 +415,7 @@ function test_default_bound() model = LP.Model() MOI.read!(io, model) x = MOI.get(model, MOI.ListOfVariableIndices()) - F, S = MOI.VariableIndex,MOI.GreaterThan{Float64} + 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) From d98515c5b057841e90e32d96d1a7bff4d6454948 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 21 Apr 2022 20:24:16 +1200 Subject: [PATCH 3/5] Update src/FileFormats/LP/LP.jl --- src/FileFormats/LP/LP.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/FileFormats/LP/LP.jl b/src/FileFormats/LP/LP.jl index 8cde7c98b9..ac0cf13347 100644 --- a/src/FileFormats/LP/LP.jl +++ b/src/FileFormats/LP/LP.jl @@ -648,7 +648,6 @@ function _parse_section( # 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) - MOI.delete(model, c) end MOI.add_constraint(model, x, MOI.LessThan(ub)) end From 9759edfc4e2407ceabadf9686a872ab3e6abab8c Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 21 Apr 2022 20:47:16 +1200 Subject: [PATCH 4/5] Fix --- src/FileFormats/LP/LP.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/FileFormats/LP/LP.jl b/src/FileFormats/LP/LP.jl index 8cde7c98b9..55d48c8985 100644 --- a/src/FileFormats/LP/LP.jl +++ b/src/FileFormats/LP/LP.jl @@ -602,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 From a29b8116b72c077e560bb457db2481eb35c9c455 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 22 Apr 2022 13:15:53 +1200 Subject: [PATCH 5/5] Improve coverage --- test/FileFormats/LP/LP.jl | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/FileFormats/LP/LP.jl b/test/FileFormats/LP/LP.jl index 28b498b321..ab0c263f7c 100644 --- a/test/FileFormats/LP/LP.jl +++ b/test/FileFormats/LP/LP.jl @@ -423,6 +423,27 @@ function test_default_bound() 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_")