Skip to content
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
675 changes: 308 additions & 367 deletions src/FileFormats/LP/LP.jl

Large diffs are not rendered by default.

225 changes: 122 additions & 103 deletions test/FileFormats/LP/LP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import MathOptInterface
using Test

const MOI = MathOptInterface
const MOIU = MOI.Utilities
const LP = MOI.FileFormats.LP
const LP_TEST_FILE = "test.lp"

function test_show()
@test sprint(show, LP.Model()) == "A .LP-file model"
return
end

function test_comprehensive_write()
model = LP.Model()
MOIU.loadfromstring!(
MOI.Utilities.loadfromstring!(
model,
"""
variables: a, x, y, z
Expand Down Expand Up @@ -177,7 +177,7 @@ end

function test_name_sanitization_other()
model = LP.Model()
MOIU.loadfromstring!(
MOI.Utilities.loadfromstring!(
model,
"""
variables: x
Expand All @@ -196,7 +196,7 @@ end

function test_free_variables()
model = LP.Model()
MOIU.loadfromstring!(
MOI.Utilities.loadfromstring!(
model,
"""
variables: x, y, z
Expand Down Expand Up @@ -224,7 +224,7 @@ function test_quadratic_objective()
model = LP.Model()
@test_throws(
MOI.UnsupportedAttribute,
MOIU.loadfromstring!(
MOI.Utilities.loadfromstring!(
model,
"""
variables: x
Expand All @@ -234,6 +234,22 @@ minobjective: 1.0*x*x
)
end

###
### Read tests
###

function test_read_invalid()
models = joinpath(@__DIR__, "models")
for filename in filter(f -> startswith(f, "invalid_"), readdir(models))
model = LP.Model()
@test_throws(
ErrorException,
MOI.read_from_file(model, joinpath(models, filename)),
)
end
return
end

function test_read_example_lo1()
model = LP.Model()
MOI.read_from_file(model, joinpath(@__DIR__, "models", "example_lo1.lp"))
Expand All @@ -247,7 +263,77 @@ function test_read_example_lo1()
constraints
@test (MOI.VariableIndex, MOI.GreaterThan{Float64}) in constraints
@test (MOI.VariableIndex, MOI.Interval{Float64}) in constraints
return nothing
io = IOBuffer()
write(io, model)
seekstart(io)
file = read(io, String)
@test occursin("maximize", file)
@test occursin("obj: 3 x1 + 1 x2 + 5 x3 + 1 x4", file)
@test occursin("c1: 3 x1 + 1 x2 + 2 x3 = 30", file)
@test occursin("c2: 2 x1 + 1 x2 + 3 x3 + 1 x4 >= 15", file)
@test occursin("c3: 2 x2 + 3 x4 <= 25", file)
@test occursin("x1 >= 0", file)
@test occursin("0 <= x2 <= 10", file)
@test occursin("x3 >= 0", file)
@test occursin("x4 >= 0", file)
return
end

function test_read_model1_tricky()
model = LP.Model()
MOI.read_from_file(model, joinpath(@__DIR__, "models", "model1_tricky.lp"))
@test MOI.get(model, MOI.NumberOfVariables()) == 8
var_names = MOI.get.(model, MOI.VariableName(), MOI.VariableIndex.(1:8))
@test Set(var_names) ==
Set(["Var4", "V5", "V1", "V2", "V3", "V6", "V7", "V8"])
io = IOBuffer()
write(io, model)
seekstart(io)
file = read(io, String)
@test occursin("maximize", file)
@test occursin("obj: -1 Var4 + 1 V5", file)
@test occursin("CON3: 1 V3 <= 2.5", file)
@test occursin("CON4: 1 V5 + 1 V6 + 1 V7 <= 1", file)
@test occursin("CON1: 1 V1 >= 0", file)
@test occursin("R1: 1 V2 >= 2", file)
@test occursin("V1 <= 3", file)
@test occursin("Var4 >= 5.5", file)
@test occursin("V3 >= -3", file)
@test occursin("V5 = 1", file)
@test occursin("0 <= V2 <= 3", file)
@test occursin("0 <= V7 <= 1", file)
@test occursin("0 <= V8 <= 1", file)
@test occursin("V6 free", file)
@test occursin("\nVar4\n", file)
@test occursin("\nV5\n", file)
@test occursin("\nV6\n", file)
@test occursin("Binary\nV8\n", file)
return
end

function test_read_model1()
model = LP.Model()
MOI.read_from_file(model, joinpath(@__DIR__, "models", "model1.lp"))
constraints = MOI.get(model, MOI.ListOfConstraintTypesPresent())
@test (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) in
constraints
@test (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) in
constraints
@test (MOI.VariableIndex, MOI.GreaterThan{Float64}) in constraints
@test (MOI.VariableIndex, MOI.Interval{Float64}) in constraints
@test (MathOptInterface.VariableIndex, MathOptInterface.Integer) in
constraints
@test (MathOptInterface.VariableIndex, MathOptInterface.ZeroOne) in
constraints
@test (
MathOptInterface.VectorOfVariables,
MathOptInterface.SOS1{Float64},
) in constraints
@test (
MathOptInterface.VectorOfVariables,
MathOptInterface.SOS2{Float64},
) in constraints
return
end

function test_read_model2()
Expand All @@ -272,114 +358,47 @@ function test_read_model2()
obj_type = MOI.get(model, MOI.ObjectiveFunctionType())
obj_func = MOI.get(model, MOI.ObjectiveFunction{obj_type}())
@test obj_func.constant == 2.5
return nothing
end

function test_read_model1_tricky()
model = LP.Model()
MOI.read_from_file(model, joinpath(@__DIR__, "models", "model1_tricky.lp"))
@test MOI.get(model, MOI.NumberOfVariables()) == 8
var_names = MOI.get.(model, MOI.VariableName(), MOI.VariableIndex.(1:8))
@test Set(var_names) ==
Set(["Var4", "V5", "V1", "V2", "V3", "V6", "V7", "V8"])
return nothing
end

function test_read_corrupt()
model = LP.Model()
@test_throws ErrorException MOI.read_from_file(
model,
joinpath(@__DIR__, "models", "corrupt.lp"),
)
return nothing
end

function test_read_invalid_variable_name()
model = LP.Model()
@test_throws ErrorException MOI.read_from_file(
model,
joinpath(@__DIR__, "models", "invalid_variable_name.lp"),
)
return nothing
end

function test_read_invalid_affine_term_objective()
model = LP.Model()
@test_throws ErrorException MOI.read_from_file(
model,
joinpath(@__DIR__, "models", "invalid_affine_term_objective.lp"),
)
return nothing
end

function test_read_invalid_affine_term_constraint()
model = LP.Model()
@test_throws ErrorException MOI.read_from_file(
model,
joinpath(@__DIR__, "models", "invalid_affine_term_constraint.lp"),
)
return nothing
end

function test_read_invalid_sos_set()
model = LP.Model()
@test_throws ErrorException MOI.read_from_file(
model,
joinpath(@__DIR__, "models", "invalid_sos_set.lp"),
)
return nothing
return
end

function test_read_invalid_sos_constraint()
function test_read_objective_sense()
model = LP.Model()
@test_throws ErrorException MOI.read_from_file(
model,
joinpath(@__DIR__, "models", "invalid_sos_constraint.lp"),
cases = Dict(
"max" => MOI.MAX_SENSE,
"maximize" => MOI.MAX_SENSE,
"maximise" => MOI.MAX_SENSE,
"maximum" => MOI.MAX_SENSE,
"min" => MOI.MIN_SENSE,
"minimize" => MOI.MIN_SENSE,
"minimise" => MOI.MIN_SENSE,
"minimum" => MOI.MIN_SENSE,
)
return nothing
for (sense, result) in cases
LP._set_objective_sense(LP._KW_OBJECTIVE, model, sense)
@test MOI.get(model, MOI.ObjectiveSense()) == result
end
return
end

function test_read_invalid_bound()
function test_read_nonempty_model()
filename = joinpath(@__DIR__, "models", "model2.lp")
model = LP.Model()
@test_throws ErrorException MOI.read_from_file(
model,
joinpath(@__DIR__, "models", "invalid_bound.lp"),
MOI.read_from_file(model, filename)
@test_throws(
ErrorException("Cannot read in file because model is not empty."),
MOI.read_from_file(model, filename),
)
return nothing
return
end

function test_read_invalid_constraint()
model = LP.Model()
@test_throws ErrorException MOI.read_from_file(
model,
joinpath(@__DIR__, "models", "invalid_constraint.lp"),
function test_read_maximum_length_error()
filename = joinpath(@__DIR__, "models", "model2.lp")
model = LP.Model(; maximum_length = 1)
@test_throws(
ErrorException("Name exceeds maximum length: V4"),
MOI.read_from_file(model, filename),
)
return nothing
end

function test_read_model1()
model = LP.Model()
MOI.read_from_file(model, joinpath(@__DIR__, "models", "model1.lp"))
constraints = MOI.get(model, MOI.ListOfConstraintTypesPresent())
@test (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) in
constraints
@test (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) in
constraints
@test (MOI.VariableIndex, MOI.GreaterThan{Float64}) in constraints
@test (MOI.VariableIndex, MOI.Interval{Float64}) in constraints
@test (MathOptInterface.VariableIndex, MathOptInterface.Integer) in
constraints
@test (MathOptInterface.VariableIndex, MathOptInterface.ZeroOne) in
constraints
@test (
MathOptInterface.VectorOfVariables,
MathOptInterface.SOS1{Float64},
) in constraints
@test (
MathOptInterface.VectorOfVariables,
MathOptInterface.SOS2{Float64},
) in constraints
return nothing
return
end

function runtests()
Expand Down
2 changes: 1 addition & 1 deletion test/FileFormats/LP/models/example_lo1.lp
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ bounds
0 <= x2 <= 10
0 <= x3 <= +infinity
0 <= x4 <= +infinity
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ bounds
0 <= x2 <= 10
0 <= x3 <= +infinity
0 <= x4 <= +infinity
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ bounds
0 <= x2 <= 10
0 <= x3 <= +infinity
0 <= x4 <= +infinity
end
end
2 changes: 1 addition & 1 deletion test/FileFormats/LP/models/invalid_bound.lp
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ bounds
0 >= x2 <= 10
0 <= x3 <= +infinity
0 <= x4 <= +infinity
end
end
7 changes: 7 additions & 0 deletions test/FileFormats/LP/models/invalid_bound_2.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
maximize
obj: x1
subject to
c1: x <= 11
bounds
x1 != 10
end
2 changes: 1 addition & 1 deletion test/FileFormats/LP/models/invalid_constraint.lp
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ bounds
0 <= x2 <= 10
0 <= x3 <= +infinity
0 <= x4 <= +infinity
end
end
2 changes: 1 addition & 1 deletion test/FileFormats/LP/models/invalid_variable_name.lp
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ bounds
0 <= x2 <= 10
0 <= x3 <= +infinity
0 <= x4 <= +infinity
end
end
8 changes: 5 additions & 3 deletions test/FileFormats/LP/models/model1_tricky.lp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
\ its a terrible idea
Max \ this problem is a maximisation!

obj: -1 Var4
obj:
-1 Var4
+ 1 V5
Subject To
CON1: 1 V1 >= 0.0
CON1:
1 V1 >= 0.0
1 V2 >= 2.0 \ not named
CON3: 1 V3 <= 2.5
CON4: 1 V5 + 1 V6 \ split constraint. we know it hasn't ended as missing operator
Expand All @@ -16,7 +18,7 @@ Bounds
-inf <= V1 <= 3
V2 <= 3
V3 >= -3
5.5 <= Var4 <= +inf
+inf >= Var4 >= 5.5
V5 = 1 \ fixed variable

V6 free
Expand Down