diff --git a/test/FileFormats/CBF/CBF.jl b/test/FileFormats/CBF/CBF.jl index 91f9cb571b..055cd39a9f 100644 --- a/test/FileFormats/CBF/CBF.jl +++ b/test/FileFormats/CBF/CBF.jl @@ -1,3 +1,5 @@ +module TestCBF + import MathOptInterface using Test @@ -7,7 +9,7 @@ const CBF = MOI.FileFormats.CBF const CBF_TEST_FILE = "test.cbf" const MODELS_DIR = joinpath(@__DIR__, "models") -function set_var_and_con_names(model::MOI.ModelLike) +function _set_var_and_con_names(model::MOI.ModelLike) variable_names = String[] for j in MOI.get(model, MOI.ListOfVariableIndices()) var_name_j = "v" * string(j.value) @@ -60,15 +62,15 @@ function Base.isapprox( return isapprox(MOI.VectorAffineFunction{Float64}(f), g) end -function test_write_then_read(model_string::String) +function _test_write_then_read(model_string::String) model1 = CBF.Model() MOIU.loadfromstring!(model1, model_string) - (variable_names, constraint_names) = set_var_and_con_names(model1) + (variable_names, constraint_names) = _set_var_and_con_names(model1) MOI.write_to_file(model1, CBF_TEST_FILE) model2 = CBF.Model() MOI.read_from_file(model2, CBF_TEST_FILE) - set_var_and_con_names(model2) + _set_var_and_con_names(model2) return MOIU.test_models_equal( model1, @@ -78,14 +80,14 @@ function test_write_then_read(model_string::String) ) end -function test_read(filename::String, model_string::String) +function _test_read(filename::String, model_string::String) model1 = CBF.Model() MOIU.loadfromstring!(model1, model_string) - (variable_names, constraint_names) = set_var_and_con_names(model1) + (variable_names, constraint_names) = _set_var_and_con_names(model1) model2 = CBF.Model() MOI.read_from_file(model2, filename) - set_var_and_con_names(model2) + _set_var_and_con_names(model2) return MOIU.test_models_equal( model1, @@ -95,10 +97,12 @@ function test_read(filename::String, model_string::String) ) end -@test sprint(show, CBF.Model()) == "A Conic Benchmark Format (CBF) model" +function test_show() + @test sprint(show, CBF.Model()) == "A Conic Benchmark Format (CBF) model" +end -@testset "Support errors" begin - @testset "$set variable bound" for set in [ +function test_support_errors() + for set in [ MOI.EqualTo(1.0), MOI.LessThan(1.0), MOI.GreaterThan(1.0), @@ -118,25 +122,25 @@ end end end -@testset "Read errors" begin - @testset "Non-empty model" begin - model = CBF.Model() - MOI.add_variable(model) - @test_throws Exception MOI.read_from_file( - model, - joinpath(MODELS_DIR, "example1.cbf"), - ) - end +function test_read_nonempty() + model = CBF.Model() + MOI.add_variable(model) + @test_throws Exception MOI.read_from_file( + model, + joinpath(MODELS_DIR, "example1.cbf"), + ) +end - @testset "Incompatible version" begin - model = CBF.Model() - @test_throws Exception MOI.read_from_file( - model, - joinpath(MODELS_DIR, "incompatible_version.cbf"), - ) - end +function test_read_incompatible() + model = CBF.Model() + @test_throws Exception MOI.read_from_file( + model, + joinpath(MODELS_DIR, "incompatible_version.cbf"), + ) +end - @testset "$filename" for filename in [ +function test_read_badcones() + for filename in [ "bad_cone_string_A.cbf", "bad_cone_string_B.cbf", "bad_cone_string_C.cbf", @@ -148,17 +152,20 @@ end joinpath(MODELS_DIR, filename), ) end +end - @testset "$filename" for filename in - ["bad_power_dim_A.cbf", "bad_power_dim_B.cbf"] +function test_read_badpowerdim() + for filename in ["bad_power_dim_A.cbf", "bad_power_dim_B.cbf"] model = CBF.Model() @test_throws Exception MOI.read_from_file( model, joinpath(MODELS_DIR, filename), ) end +end - @testset "$filename" for filename in [ +function test_read_corrupt() + for filename in [ "corrupt_line_A.cbf", "corrupt_line_B.cbf", "corrupt_line_C.cbf", @@ -173,23 +180,19 @@ end end end -@testset "Write errors" begin - @testset "Quadratic objective" begin - model = CBF.Model() - MOIU.loadfromstring!( - model, - """ - variables: x - minobjective: 1 * x * x -""", - ) - @test_throws Exception MOI.write_to_file(model, CBF_TEST_FILE) - end - - # TODO NLP not supported test. +function test_write_quadratic() + model = CBF.Model() + MOIU.loadfromstring!( + model, + """ + variables: x + minobjective: 1 * x * x + """, + ) + @test_throws Exception MOI.write_to_file(model, CBF_TEST_FILE) end -write_read_models = [ +const _WRITE_READ_MODELS = [ ( "min SingleVariable", """ @@ -403,12 +406,14 @@ write_read_models = [ """, ), ] -@testset "Write/read $model_name" for (model_name, model_string) in - write_read_models - test_write_then_read(model_string) + +function test_write_read_models() + for (model_name, model_string) in _WRITE_READ_MODELS + _test_write_then_read(model_string) + end end -example_models = [ +const _EXAMPLE_MODELS = [ ( "example_A.cbf", """ @@ -458,12 +463,27 @@ example_models = [ """, ), ] -@testset "Read and write/read $model_name" for (model_name, model_string) in - example_models - test_read(joinpath(MODELS_DIR, model_name), model_string) - test_write_then_read(model_string) + +function test_example_models() + for (model_name, model_string) in _EXAMPLE_MODELS + _test_read(joinpath(MODELS_DIR, model_name), model_string) + _test_write_then_read(model_string) + end +end + +function runtests() + for name in names(@__MODULE__, all = true) + if startswith("$(name)", "test_") + @testset "$name" begin + getfield(@__MODULE__, name)() + end + end + end + sleep(1.0) # Allow time for unlink to happen. + rm(CBF_TEST_FILE, force = true) + return +end + end -# Clean up. -sleep(1.0) # Allow time for unlink to happen. -rm(CBF_TEST_FILE, force = true) +TestCBF.runtests() diff --git a/test/FileFormats/FileFormats.jl b/test/FileFormats/FileFormats.jl index 7e4d02afed..0ff94c923e 100644 --- a/test/FileFormats/FileFormats.jl +++ b/test/FileFormats/FileFormats.jl @@ -1,100 +1,135 @@ +module TestFileFormats + using MathOptInterface using Test const MOI = MathOptInterface -@testset "MOI.FileFormats tests" begin - @testset "$(file)" for file in ["CBF", "LP", "MOF", "MPS", "NL", "SDPA"] - include(joinpath(@__DIR__, file, "$(file).jl")) - end +function test_CBF() + return include(joinpath(@__DIR__, "CBF", "CBF.jl")) +end - @testset "Copying options" begin - models = [ - MOI.FileFormats.CBF.Model, - MOI.FileFormats.LP.Model, - MOI.FileFormats.MOF.Model, - MOI.FileFormats.MPS.Model, - MOI.FileFormats.SDPA.Model, - ] - for src in models - model_src = src() - for dest in models - model_dest = dest() - MOI.copy_to(model_dest, model_src) - @test !isempty(sprint(write, model_dest)) - end - model_dest = - MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) +function test_LP() + return include(joinpath(@__DIR__, "LP", "LP.jl")) +end + +function test_MOF() + return include(joinpath(@__DIR__, "MOF", "MOF.jl")) +end + +function test_MPS() + return include(joinpath(@__DIR__, "MPS", "MPS.jl")) +end + +function test_NL() + return include(joinpath(@__DIR__, "NL", "NL.jl")) +end + +function test_SDPA() + return include(joinpath(@__DIR__, "SDPA", "SDPA.jl")) +end + +function test_copying() + models = [ + MOI.FileFormats.CBF.Model, + MOI.FileFormats.LP.Model, + MOI.FileFormats.MOF.Model, + MOI.FileFormats.MPS.Model, + MOI.FileFormats.SDPA.Model, + ] + for src in models + model_src = src() + for dest in models + model_dest = dest() MOI.copy_to(model_dest, model_src) + @test !isempty(sprint(write, model_dest)) end + model_dest = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) + MOI.copy_to(model_dest, model_src) end +end - @testset "Calling MOF.compressed_open" begin - for cs in [MOI.FileFormats.Bzip2(), MOI.FileFormats.Gzip()] - for open_type in ["a", "r+", "w+", "a+"] - @test_throws ArgumentError MOI.FileFormats.compressed_open( - (x) -> nothing, - "dummy.gz", - open_type, - cs, - ) - end +function test_compressed_open() + for cs in [MOI.FileFormats.Bzip2(), MOI.FileFormats.Gzip()] + for open_type in ["a", "r+", "w+", "a+"] + @test_throws ArgumentError MOI.FileFormats.compressed_open( + (x) -> nothing, + "dummy.gz", + open_type, + cs, + ) end end +end - @testset "Provided compression schemes" begin - filename = joinpath(@__DIR__, "MPS", "free_integer.mps") - model = MOI.FileFormats.Model(filename = filename) - MOI.read_from_file(model, filename) - filename = joinpath(@__DIR__, "free_integer.mps") - MOI.write_to_file(model, filename * ".garbage") - for ext in ["", ".bz2", ".gz"] - MOI.write_to_file(model, filename * ext) - model2 = MOI.FileFormats.Model(filename = filename * ext) - MOI.read_from_file(model2, filename) - end +function test_compression() + filename = joinpath(@__DIR__, "MPS", "free_integer.mps") + model = MOI.FileFormats.Model(filename = filename) + MOI.read_from_file(model, filename) + filename = joinpath(@__DIR__, "free_integer.mps") + MOI.write_to_file(model, filename * ".garbage") + for ext in ["", ".bz2", ".gz"] + MOI.write_to_file(model, filename * ext) + model2 = MOI.FileFormats.Model(filename = filename * ext) + MOI.read_from_file(model2, filename) + end - sleep(1.0) # Allow time for unlink to happen. - for ext in ["", ".garbage", ".bz2", ".gz"] - rm(filename * ext, force = true) - end + sleep(1.0) # Allow time for unlink to happen. + for ext in ["", ".garbage", ".bz2", ".gz"] + rm(filename * ext, force = true) end + return +end - @testset "Model" begin - for (format, model) in [ - (MOI.FileFormats.FORMAT_CBF, MOI.FileFormats.CBF.Model()), - (MOI.FileFormats.FORMAT_LP, MOI.FileFormats.LP.Model()), - (MOI.FileFormats.FORMAT_MOF, MOI.FileFormats.MOF.Model()), - (MOI.FileFormats.FORMAT_MPS, MOI.FileFormats.MPS.Model()), - (MOI.FileFormats.FORMAT_NL, MOI.FileFormats.NL.Model()), - (MOI.FileFormats.FORMAT_SDPA, MOI.FileFormats.SDPA.Model()), - ] - @test typeof( - MOI.FileFormats.Model(format = format, filename = "foo.bar"), - ) == typeof(model) - end - @test_throws( - ErrorException( - "When `format==FORMAT_AUTOMATIC` you must pass a `filename`.", - ), - MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_AUTOMATIC) - ) - for (ext, model) in [ - (".cbf", MOI.FileFormats.CBF.Model()), - (".lp", MOI.FileFormats.LP.Model()), - (".mof.json", MOI.FileFormats.MOF.Model()), - (".mps", MOI.FileFormats.MPS.Model()), - (".nl", MOI.FileFormats.NL.Model()), - (".sdpa", MOI.FileFormats.SDPA.Model()), - ] - @test typeof(MOI.FileFormats.Model(filename = "a$(ext)")) == - typeof(model) - @test typeof(MOI.FileFormats.Model(filename = "a$(ext).gz")) == - typeof(model) +function test_Model() + for (format, model) in [ + (MOI.FileFormats.FORMAT_CBF, MOI.FileFormats.CBF.Model()), + (MOI.FileFormats.FORMAT_LP, MOI.FileFormats.LP.Model()), + (MOI.FileFormats.FORMAT_MOF, MOI.FileFormats.MOF.Model()), + (MOI.FileFormats.FORMAT_MPS, MOI.FileFormats.MPS.Model()), + (MOI.FileFormats.FORMAT_NL, MOI.FileFormats.NL.Model()), + (MOI.FileFormats.FORMAT_SDPA, MOI.FileFormats.SDPA.Model()), + ] + @test typeof( + MOI.FileFormats.Model(format = format, filename = "foo.bar"), + ) == typeof(model) + end + @test_throws( + ErrorException( + "When `format==FORMAT_AUTOMATIC` you must pass a `filename`.", + ), + MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_AUTOMATIC) + ) + for (ext, model) in [ + (".cbf", MOI.FileFormats.CBF.Model()), + (".lp", MOI.FileFormats.LP.Model()), + (".mof.json", MOI.FileFormats.MOF.Model()), + (".mps", MOI.FileFormats.MPS.Model()), + (".nl", MOI.FileFormats.NL.Model()), + (".sdpa", MOI.FileFormats.SDPA.Model()), + ] + @test typeof(MOI.FileFormats.Model(filename = "a$(ext)")) == + typeof(model) + @test typeof(MOI.FileFormats.Model(filename = "a$(ext).gz")) == + typeof(model) + end + @test_throws( + ErrorException("Unable to automatically detect format of a.b."), + MOI.FileFormats.Model(filename = "a.b") + ) +end + +function runtests() + for name in names(@__MODULE__, all = true) + if startswith("$(name)", "test_") + @testset "$name" begin + getfield(@__MODULE__, name)() + end end - @test_throws( - ErrorException("Unable to automatically detect format of a.b."), - MOI.FileFormats.Model(filename = "a.b") - ) end + return end + +end + +TestFileFormats.runtests() diff --git a/test/FileFormats/LP/LP.jl b/test/FileFormats/LP/LP.jl index 0749ef5f95..a5caee4bc6 100644 --- a/test/FileFormats/LP/LP.jl +++ b/test/FileFormats/LP/LP.jl @@ -1,3 +1,5 @@ +module TestLP + import MathOptInterface using Test @@ -6,14 +8,15 @@ const MOIU = MOI.Utilities const LP = MOI.FileFormats.LP const LP_TEST_FILE = "test.lp" -@test sprint(show, LP.Model()) == "A .LP-file model" +function test_show() + @test sprint(show, LP.Model()) == "A .LP-file model" +end -@testset "write_to_file" begin - @testset "comprehensive write" begin - model = LP.Model() - MOIU.loadfromstring!( - model, - """ +function test_comprehensive_write() + model = LP.Model() + MOIU.loadfromstring!( + model, + """ variables: a, x, y, z minobjective: x c1: x >= -1.0 @@ -26,221 +29,234 @@ c7: 1.5a + 1.6 == 0.2 c8: 1.7a + 1.8 in Interval(0.3, 0.4) c9: x in ZeroOne() c10: y in Integer() +""", + ) + MOI.write_to_file(model, LP_TEST_FILE) + @test read(LP_TEST_FILE, String) == + "minimize\n" * + "obj: x\n" * + "subject to\n" * + "c5: 1.1 x <= 5.1\n" * + "c6: -1.4 + 1.3 x >= -0.1\n" * + "c7: 1.6 + 1.5 a = 0.2\n" * + "c8: 0.3 <= 1.8 + 1.7 a <= 0.4\n" * + "Bounds\n" * + "x <= 2\n" * + "x >= -1\n" * + "y = 3\n" * + "4 <= z <= 5\n" * + "a free\n" * + "General\n" * + "y\n" * + "Binary\n" * + "x\n" * + "End\n" + + @test !MOI.is_empty(model) + MOI.empty!(model) + @test MOI.is_empty(model) +end + +function test_name_sanitization_start() + for starting_letter in [".", "0", "E", "e"] + model = LP.Model() + MOI.Utilities.loadfromstring!( + model, + """ +variables: x +minobjective: x +c1: 1.0 * x >= -1.0 """, ) + x = MOI.get(model, MOI.VariableIndex, "x") + MOI.set(model, MOI.VariableName(), x, starting_letter * "x") + c = MOI.get(model, MOI.ConstraintIndex, "c1") + MOI.set(model, MOI.ConstraintName(), c, starting_letter * "c1") MOI.write_to_file(model, LP_TEST_FILE) @test read(LP_TEST_FILE, String) == "minimize\n" * - "obj: x\n" * + "obj: _$(starting_letter)x\n" * "subject to\n" * - "c5: 1.1 x <= 5.1\n" * - "c6: -1.4 + 1.3 x >= -0.1\n" * - "c7: 1.6 + 1.5 a = 0.2\n" * - "c8: 0.3 <= 1.8 + 1.7 a <= 0.4\n" * + "_$(starting_letter)c1: 1 _$(starting_letter)x >= -1\n" * "Bounds\n" * - "x <= 2\n" * - "x >= -1\n" * - "y = 3\n" * - "4 <= z <= 5\n" * - "a free\n" * - "General\n" * - "y\n" * - "Binary\n" * - "x\n" * + "_$(starting_letter)x free\n" * "End\n" - - @test !MOI.is_empty(model) - MOI.empty!(model) - @test MOI.is_empty(model) end - @testset "Name sanitization" begin - @testset "Start name sanitisation" begin - for starting_letter in [".", "0", "E", "e"] - model = LP.Model() - MOI.Utilities.loadfromstring!( - model, - """ -variables: x -minobjective: x -c1: 1.0 * x >= -1.0 -""", - ) - x = MOI.get(model, MOI.VariableIndex, "x") - MOI.set(model, MOI.VariableName(), x, starting_letter * "x") - c = MOI.get(model, MOI.ConstraintIndex, "c1") - MOI.set(model, MOI.ConstraintName(), c, starting_letter * "c1") - MOI.write_to_file(model, LP_TEST_FILE) - @test read(LP_TEST_FILE, String) == - "minimize\n" * - "obj: _$(starting_letter)x\n" * - "subject to\n" * - "_$(starting_letter)c1: 1 _$(starting_letter)x >= -1\n" * - "Bounds\n" * - "_$(starting_letter)x free\n" * - "End\n" - end - end - @testset "Illegal character sanitisation" begin - for illegal_letter in ["[", "]", "*", "^"] - model = LP.Model() - MOI.Utilities.loadfromstring!( - model, - """ +end + +function test_name_sanitization_illegal() + for illegal_letter in ["[", "]", "*", "^"] + model = LP.Model() + MOI.Utilities.loadfromstring!( + model, + """ variables: x minobjective: x c1: 1.0 * x >= -1.0 """, - ) - x = MOI.get(model, MOI.VariableIndex, "x") - MOI.set(model, MOI.VariableName(), x, "x$(illegal_letter)y") - c = MOI.get(model, MOI.ConstraintIndex, "c1") - MOI.set(model, MOI.ConstraintName(), c, "c$(illegal_letter)d") - MOI.write_to_file(model, LP_TEST_FILE) - @test read(LP_TEST_FILE, String) == - "minimize\n" * - "obj: x_y\n" * - "subject to\n" * - "c_d: 1 x_y >= -1\n" * - "Bounds\n" * - "x_y free\n" * - "End\n" - end - end - @testset "Duplicate names after sanitization" begin - model = LP.Model() - MOI.Utilities.loadfromstring!( - model, - """ + ) + x = MOI.get(model, MOI.VariableIndex, "x") + MOI.set(model, MOI.VariableName(), x, "x$(illegal_letter)y") + c = MOI.get(model, MOI.ConstraintIndex, "c1") + MOI.set(model, MOI.ConstraintName(), c, "c$(illegal_letter)d") + MOI.write_to_file(model, LP_TEST_FILE) + @test read(LP_TEST_FILE, String) == + "minimize\n" * + "obj: x_y\n" * + "subject to\n" * + "c_d: 1 x_y >= -1\n" * + "Bounds\n" * + "x_y free\n" * + "End\n" + end +end + +function test_name_sanitization_duplicate() + model = LP.Model() + MOI.Utilities.loadfromstring!( + model, + """ variables: a, b, c, d minobjective: a + b + c + d c1: a + b + c + d >= -1.0 """, - ) - variables = MOI.get(model, MOI.ListOfVariableIndices()) - MOI.set.( - model, - MOI.VariableName(), - variables, - ["a[", "a]", "a*", "a^"], - ) - MOI.write_to_file(model, LP_TEST_FILE) - @test read(LP_TEST_FILE, String) == - "minimize\n" * - "obj: 1 a_ + 1 a__1 + 1 a__2 + 1 a__3\n" * - "subject to\n" * - "c1: 1 a_ + 1 a__1 + 1 a__2 + 1 a__3 >= -1\n" * - "Bounds\n" * - "a_ free\n" * - "a__1 free\n" * - "a__2 free\n" * - "a__3 free\n" * - "End\n" - end - @testset "Length sanitisation" begin - model = LP.Model(maximum_length = 3) - x = MOI.add_variable(model) - MOI.set(model, MOI.VariableName(), x, "abcdefg") - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.SingleVariable}(), - MOI.SingleVariable(x), - ) - MOI.write_to_file(model, LP_TEST_FILE) - @test read(LP_TEST_FILE, String) == - "minimize\n" * - "obj: abc\n" * - "subject to\n" * - "Bounds\n" * - "abc free\n" * - "End\n" - end - @testset "Too long duplicate names after sanitization" begin - model = LP.Model(maximum_length = 3) - MOI.Utilities.loadfromstring!( - model, - """ + ) + variables = MOI.get(model, MOI.ListOfVariableIndices()) + MOI.set.(model, MOI.VariableName(), variables, ["a[", "a]", "a*", "a^"]) + MOI.write_to_file(model, LP_TEST_FILE) + @test read(LP_TEST_FILE, String) == + "minimize\n" * + "obj: 1 a_ + 1 a__1 + 1 a__2 + 1 a__3\n" * + "subject to\n" * + "c1: 1 a_ + 1 a__1 + 1 a__2 + 1 a__3 >= -1\n" * + "Bounds\n" * + "a_ free\n" * + "a__1 free\n" * + "a__2 free\n" * + "a__3 free\n" * + "End\n" +end + +function test_name_sanitization_length() + model = LP.Model(maximum_length = 3) + x = MOI.add_variable(model) + MOI.set(model, MOI.VariableName(), x, "abcdefg") + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.SingleVariable}(), + MOI.SingleVariable(x), + ) + MOI.write_to_file(model, LP_TEST_FILE) + @test read(LP_TEST_FILE, String) == + "minimize\n" * + "obj: abc\n" * + "subject to\n" * + "Bounds\n" * + "abc free\n" * + "End\n" +end + +function test_name_sanitization_too_long() + model = LP.Model(maximum_length = 3) + MOI.Utilities.loadfromstring!( + model, + """ variables: abcd, abce minobjective: abcd + abce c1: abcd + abce >= -1.0 """, - ) - MOI.write_to_file(model, LP_TEST_FILE) - @test read(LP_TEST_FILE, String) == - "minimize\n" * - "obj: 1 abc + 1 abc_1\n" * - "subject to\n" * - "c1: 1 abc + 1 abc_1 >= -1\n" * - "Bounds\n" * - "abc free\n" * - "abc_1 free\n" * - "End\n" - end - end - @testset "other features" begin - model = LP.Model() - MOIU.loadfromstring!( - model, - """ + ) + MOI.write_to_file(model, LP_TEST_FILE) + @test read(LP_TEST_FILE, String) == + "minimize\n" * + "obj: 1 abc + 1 abc_1\n" * + "subject to\n" * + "c1: 1 abc + 1 abc_1 >= -1\n" * + "Bounds\n" * + "abc free\n" * + "abc_1 free\n" * + "End\n" +end + +function test_name_sanitization_other() + model = LP.Model() + MOIU.loadfromstring!( + model, + """ variables: x maxobjective: 2.0 * x + -1.0 """, - ) - MOI.write_to_file(model, LP_TEST_FILE) - @test read(LP_TEST_FILE, String) == - "maximize\n" * - "obj: -1 + 2 x\n" * - "subject to\n" * - "Bounds\n" * - "x free\n" * - "End\n" - end - @testset "free variables" begin - model = LP.Model() - MOIU.loadfromstring!( - model, - """ + ) + MOI.write_to_file(model, LP_TEST_FILE) + @test read(LP_TEST_FILE, String) == + "maximize\n" * + "obj: -1 + 2 x\n" * + "subject to\n" * + "Bounds\n" * + "x free\n" * + "End\n" +end + +function test_free_variables() + model = LP.Model() + MOIU.loadfromstring!( + model, + """ variables: x, y, z maxobjective: x c1: x in ZeroOne() c2: y in Integer() +""", + ) + MOI.write_to_file(model, LP_TEST_FILE) + @test read(LP_TEST_FILE, String) == + "maximize\n" * + "obj: x\n" * + "subject to\n" * + "Bounds\n" * + "y free\n" * + "z free\n" * + "General\n" * + "y\n" * + "Binary\n" * + "x\n" * + "End\n" +end + +function test_quadratic_objective() + model = LP.Model() + @test_throws( + MOI.UnsupportedAttribute, + MOIU.loadfromstring!( + model, + """ +variables: x +minobjective: 1.0*x*x """, ) - MOI.write_to_file(model, LP_TEST_FILE) - @test read(LP_TEST_FILE, String) == - "maximize\n" * - "obj: x\n" * - "subject to\n" * - "Bounds\n" * - "y free\n" * - "z free\n" * - "General\n" * - "y\n" * - "Binary\n" * - "x\n" * - "End\n" - end - @testset "Quadratic Objective" begin - model = LP.Model() - @test_throws( - MOI.UnsupportedAttribute, - MOIU.loadfromstring!( - model, - """ - variables: x - minobjective: 1.0*x*x - """, - ) - ) - end + ) end -@testset "read_from_file" begin +function test_read() model = LP.Model() exception = ErrorException("read! is not implemented for LP files.") @test_throws exception MOI.read_from_file(model, LP_TEST_FILE) end -# Clean up -sleep(1.0) # Allow time for unlink to happen. -rm(LP_TEST_FILE, force = true) +function runtests() + for name in names(@__MODULE__, all = true) + if startswith("$(name)", "test_") + @testset "$name" begin + getfield(@__MODULE__, name)() + end + end + end + sleep(1.0) # Allow time for unlink to happen. + rm(LP_TEST_FILE, force = true) + return +end + +end + +TestLP.runtests() diff --git a/test/FileFormats/MOF/MOF.jl b/test/FileFormats/MOF/MOF.jl index 85e72c348d..2873537e13 100644 --- a/test/FileFormats/MOF/MOF.jl +++ b/test/FileFormats/MOF/MOF.jl @@ -1,3 +1,5 @@ +module TestMOF + import JSON import JSONSchema import MathOptInterface @@ -9,8 +11,6 @@ const MOF = MOI.FileFormats.MOF const TEST_MOF_FILE = "test.mof.json" -@test sprint(show, MOF.Model()) == "A MathOptFormat Model" - const SCHEMA = JSONSchema.Schema(JSON.parsefile(MOI.FileFormats.MOF.SCHEMA_PATH)) @@ -32,12 +32,10 @@ function _validate(filename::String) end end -include("nonlinear.jl") - struct UnsupportedSet <: MOI.AbstractSet end struct UnsupportedFunction <: MOI.AbstractFunction end -function test_model_equality(model_string, variables, constraints; suffix = "") +function _test_model_equality(model_string, variables, constraints; suffix = "") model = MOF.Model() MOIU.loadfromstring!(model, model_string) MOI.write_to_file(model, TEST_MOF_FILE * suffix) @@ -47,584 +45,823 @@ function test_model_equality(model_string, variables, constraints; suffix = "") return _validate(TEST_MOF_FILE * suffix) end -@testset "Error handling: read_from_file" begin - failing_models_dir = joinpath(@__DIR__, "failing_models") +# hs071 +# min x1 * x4 * (x1 + x2 + x3) + x3 +# st x1 * x2 * x3 * x4 >= 25 +# x1^2 + x2^2 + x3^2 + x4^2 = 40 +# 1 <= x1, x2, x3, x4 <= 5 +struct ExprEvaluator <: MOI.AbstractNLPEvaluator + objective::Expr + constraints::Vector{Expr} +end +MOI.features_available(::ExprEvaluator) = [:ExprGraph] +MOI.initialize(::ExprEvaluator, features) = nothing +MOI.objective_expr(evaluator::ExprEvaluator) = evaluator.objective +MOI.constraint_expr(evaluator::ExprEvaluator, i::Int) = evaluator.constraints[i] - @testset "Non-empty model" begin - model = MOF.Model(warn = true) - MOI.add_variable(model) - @test !MOI.is_empty(model) - exception = ErrorException( - "Cannot read model from file as destination model is not empty.", - ) - @test_throws exception MOI.read_from_file( - model, - joinpath(@__DIR__, "empty_model.mof.json"), - ) - options = MOF.get_options(model) - @test options.warn - MOI.empty!(model) - @test MOI.is_empty(model) - MOI.read_from_file(model, joinpath(@__DIR__, "empty_model.mof.json")) - options2 = MOF.get_options(model) - @test options2.warn +function HS071(x::Vector{MOI.VariableIndex}) + x1, x2, x3, x4 = x + return MOI.NLPBlockData( + MOI.NLPBoundsPair.([25, 40], [Inf, 40]), + ExprEvaluator( + :(x[$x1] * x[$x4] * (x[$x1] + x[$x2] + x[$x3]) + x[$x3]), + [ + :(x[$x1] * x[$x2] * x[$x3] * x[$x4] >= 25), + :(x[$x1]^2 + x[$x2]^2 + x[$x3]^2 + x[$x4]^2 == 40), + ], + ), + true, + ) +end + +function test_HS071() + model = MOF.Model() + x = MOI.add_variables(model, 4) + for (index, variable) in enumerate(x) + MOI.set(model, MOI.VariableName(), variable, "var_$(index)") + end + MOI.add_constraints( + model, + MOI.SingleVariable.(x), + Ref(MOI.Interval(1.0, 5.0)), + ) + MOI.set(model, MOI.NLPBlock(), HS071(x)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.write_to_file(model, TEST_MOF_FILE) + @test replace(read(TEST_MOF_FILE, String), '\r' => "") == + replace(read(joinpath(@__DIR__, "nlp.mof.json"), String), '\r' => "") + return _validate(TEST_MOF_FILE) +end + +function test_nonlinear_error_handling() + node_list = MOF.Object[] + string_to_variable = Dict{String,MOI.VariableIndex}() + variable_to_string = Dict{MOI.VariableIndex,String}() + # Test unsupported function for Expr -> MOF. + @test_throws Exception MOF.convert_expr_to_mof( + :(not_supported_function(x)), + node_list, + variable_to_string, + ) + # Test unsupported function for MOF -> Expr. + @test_throws Exception MOF.convert_mof_to_expr( + MOF.OrderedObject("type" => "not_supported_function", "value" => 1), + node_list, + string_to_variable, + ) + # Test n-ary function with no arguments. + @test_throws Exception MOF.convert_expr_to_mof( + :(min()), + node_list, + variable_to_string, + ) + # Test unary function with two arguments. + @test_throws Exception MOF.convert_expr_to_mof( + :(sin(x, y)), + node_list, + variable_to_string, + ) + # Test binary function with one arguments. + @test_throws Exception MOF.convert_expr_to_mof( + :(^(x)), + node_list, + variable_to_string, + ) + # An expression with something other than :call as the head. + @test_throws Exception MOF.convert_expr_to_mof( + :(a <= b <= c), + node_list, + variable_to_string, + ) + # Hit the default fallback with an un-interpolated complex number. + @test_throws Exception MOF.convert_expr_to_mof( + :(1 + 2im), + node_list, + variable_to_string, + ) + # Invalid number of variables. + @test_throws Exception MOF.substitute_variables( + :(x[1] * x[2]), + [MOI.VariableIndex(1)], + ) + # Function-in-Set + @test_throws Exception MOF.extract_function_and_set(:(foo in set)) + # Not a constraint. + @test_throws Exception MOF.extract_function_and_set(:(x^2)) + # Two-sided constraints + @test MOF.extract_function_and_set(:(1 <= x <= 2)) == + MOF.extract_function_and_set(:(2 >= x >= 1)) == + (:x, MOI.Interval(1, 2)) + # Less-than constraint. + @test MOF.extract_function_and_set(:(x <= 2)) == (:x, MOI.LessThan(2)) +end + +function test_Roundtrip_nonlinear_expressions() + x = MOI.VariableIndex(123) + y = MOI.VariableIndex(456) + z = MOI.VariableIndex(789) + string_to_var = Dict{String,MOI.VariableIndex}("x" => x, "y" => y, "z" => z) + var_to_string = Dict{MOI.VariableIndex,String}(x => "x", y => "y", z => "z") + for expr in [ + 2, + 2.34, + 2 + 3im, + x, + :(1 + $x), + :($x - 1), + :($x + $y), + :($x + $y - $z), + :(2 * $x), + :($x * $y), + :($x / 2), + :(2 / $x), + :($x / $y), + :($x / $y / $z), + :(2^$x), + :($x^2), + :($x^$y), + :($x^(2 * $y + 1)), + :(sin($x)), + :(sin($x + $y)), + :(2 * $x + sin($x)^2 + $y), + :(sin($(3im))^2 + cos($(3im))^2), + :($(1 + 2im) * $x), + :(ceil($x)), + :(floor($x)), + :($x < $y), + :($x <= $y), + :($x > $y), + :($x >= $y), + :($x == $y), + :($x != $y), + # :($x && $y), :($x || $y), + :(ifelse($x > 0, 1, $y)), + ] + node_list = MOF.OrderedObject[] + object = MOF.convert_expr_to_mof(expr, node_list, var_to_string) + @test MOF.convert_mof_to_expr(object, node_list, string_to_var) == expr end +end +function test_nonlinear_readingwriting() + # Write to file. + model = MOF.Model() + (x, y) = MOI.add_variables(model, 2) + MOI.set(model, MOI.VariableName(), x, "var_x") + MOI.set(model, MOI.VariableName(), y, "y") + con = MOI.add_constraint( + model, + MOF.Nonlinear(:(2 * $x + sin($x)^2 - $y)), + MOI.EqualTo(1.0), + ) + MOI.set(model, MOI.ConstraintName(), con, "con") + MOI.write_to_file(model, TEST_MOF_FILE) + # Read the model back in. + model2 = MOF.Model() + MOI.read_from_file(model2, TEST_MOF_FILE) + con2 = MOI.get(model2, MOI.ConstraintIndex, "con") + foo2 = MOI.get(model2, MOI.ConstraintFunction(), con2) + # Test that we recover the constraint. + @test foo2.expr == :(2 * $x + sin($x)^2 - $y) + @test MOI.get(model, MOI.ConstraintSet(), con) == + MOI.get(model2, MOI.ConstraintSet(), con2) + return _validate(TEST_MOF_FILE) +end + +function test_show() + @test sprint(show, MOF.Model()) == "A MathOptFormat Model" +end + +function test_nonempty_model() + model = MOF.Model(warn = true) + MOI.add_variable(model) + @test !MOI.is_empty(model) + exception = ErrorException( + "Cannot read model from file as destination model is not empty.", + ) + @test_throws exception MOI.read_from_file( + model, + joinpath(@__DIR__, "empty_model.mof.json"), + ) + options = MOF.get_options(model) + @test options.warn + MOI.empty!(model) + @test MOI.is_empty(model) + MOI.read_from_file(model, joinpath(@__DIR__, "empty_model.mof.json")) + options2 = MOF.get_options(model) + @test options2.warn +end + +function test_failing_models() @testset "$(filename)" for filename in filter( f -> endswith(f, ".mof.json"), - readdir(failing_models_dir), + readdir(joinpath(@__DIR__, "failing_models")), ) @test_throws Exception MOI.read_from_file( MOF.Model(), - joinpath(failing_models_dir, filename), + joinpath(@__DIR__, "failing_models", filename), ) end end -@testset "Names" begin - @testset "Blank variable name" begin - model = MOF.Model() - variable = MOI.add_variable(model) - @test_throws Exception MOF.moi_to_object(variable, model) - MOI.FileFormats.create_unique_names(model, warn = true) - @test MOF.moi_to_object(variable, model) == - MOF.OrderedObject("name" => "x1") - end - @testset "Duplicate variable name" begin - model = MOF.Model() - x = MOI.add_variable(model) - MOI.set(model, MOI.VariableName(), x, "x") - y = MOI.add_variable(model) - MOI.set(model, MOI.VariableName(), y, "x") - @test MOF.moi_to_object(x, model) == MOF.OrderedObject("name" => "x") - @test MOF.moi_to_object(y, model) == MOF.OrderedObject("name" => "x") - MOI.FileFormats.create_unique_names(model, warn = true) - @test MOF.moi_to_object(x, model) == MOF.OrderedObject("name" => "x") - @test MOF.moi_to_object(y, model) == MOF.OrderedObject("name" => "x_1") - end - @testset "Blank constraint name" begin - model = MOF.Model() - x = MOI.add_variable(model) - MOI.set(model, MOI.VariableName(), x, "x") - c = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.ZeroOne()) - name_map = Dict(x => "x") - MOI.FileFormats.create_unique_names(model, warn = true) - @test MOF.moi_to_object(c, model, name_map)["name"] == "c1" - end - @testset "Duplicate constraint name" begin - model = MOF.Model() - x = MOI.add_variable(model) - MOI.set(model, MOI.VariableName(), x, "x") - c1 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(1.0)) - c2 = MOI.add_constraint( - model, - MOI.SingleVariable(x), - MOI.GreaterThan(0.0), - ) - MOI.set(model, MOI.ConstraintName(), c1, "c") - MOI.set(model, MOI.ConstraintName(), c2, "c") - name_map = Dict(x => "x") - @test MOF.moi_to_object(c1, model, name_map)["name"] == "c" - @test MOF.moi_to_object(c2, model, name_map)["name"] == "c" - MOI.FileFormats.create_unique_names(model, warn = true) - @test MOF.moi_to_object(c1, model, name_map)["name"] == "c_1" - @test MOF.moi_to_object(c2, model, name_map)["name"] == "c" - end + +function test_Blank_variable_name() + model = MOF.Model() + variable = MOI.add_variable(model) + @test_throws Exception MOF.moi_to_object(variable, model) + MOI.FileFormats.create_unique_names(model, warn = true) + @test MOF.moi_to_object(variable, model) == + MOF.OrderedObject("name" => "x1") end -@testset "round trips" begin - @testset "Empty model" begin - model = MOF.Model() - MOI.write_to_file(model, TEST_MOF_FILE) - model_2 = MOF.Model() - MOI.read_from_file(model_2, TEST_MOF_FILE) - MOIU.test_models_equal(model, model_2, String[], String[]) - end - @testset "FEASIBILITY_SENSE" begin - model = MOF.Model() - x = MOI.add_variable(model) - MOI.set(model, MOI.VariableName(), x, "x") - MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) - MOI.write_to_file(model, TEST_MOF_FILE) - model_2 = MOF.Model() - MOI.read_from_file(model_2, TEST_MOF_FILE) - MOIU.test_models_equal(model, model_2, ["x"], String[]) - end - @testset "Empty function term" begin - model = MOF.Model() - x = MOI.add_variable(model) - MOI.set(model, MOI.VariableName(), x, "x") - c = MOI.add_constraint( - model, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}[], 0.0), - MOI.GreaterThan(1.0), - ) - MOI.set(model, MOI.ConstraintName(), c, "c") - MOI.write_to_file(model, TEST_MOF_FILE) - model_2 = MOF.Model() - MOI.read_from_file(model_2, TEST_MOF_FILE) - MOIU.test_models_equal(model, model_2, ["x"], ["c"]) - end - @testset "min objective" begin - test_model_equality( - """ - variables: x - minobjective: x -""", - ["x"], - String[], - ) - end - @testset "max objective" begin - test_model_equality( - """ - variables: x - maxobjective: x -""", - ["x"], - String[], - suffix = ".gz", - ) - end - @testset "min scalaraffine" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x + 0.5 -""", - ["x"], - String[], - ) - end - @testset "max scalaraffine" begin - test_model_equality( - """ - variables: x - maxobjective: 1.2x + 0.5 -""", - ["x"], - String[], - suffix = ".gz", - ) - end - @testset "singlevariable-in-lower" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x + 0.5 - c1: x >= 1.0 -""", - ["x"], - ["c1"], - ) - end - @testset "singlevariable-in-upper" begin - test_model_equality( - """ - variables: x - maxobjective: 1.2x + 0.5 - c1: x <= 1.0 -""", - ["x"], - ["c1"], - suffix = ".gz", - ) - end - @testset "singlevariable-in-interval" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x + 0.5 - c1: x in Interval(1.0, 2.0) -""", - ["x"], - ["c1"], - ) - end - @testset "singlevariable-in-equalto" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x + 0.5 - c1: x == 1.0 -""", - ["x"], - ["c1"], - ) - end - @testset "singlevariable-in-zeroone" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x + 0.5 - c1: x in ZeroOne() -""", - ["x"], - ["c1"], - ) - end - @testset "singlevariable-in-integer" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x + 0.5 - c1: x in Integer() -""", - ["x"], - ["c1"], - ) - end - @testset "singlevariable-in-Semicontinuous" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x + 0.5 - c1: x in Semicontinuous(1.0, 2.0) -""", - ["x"], - ["c1"], - ) - end - @testset "singlevariable-in-Semiinteger" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x + 0.5 - c1: x in Semiinteger(1.0, 2.0) -""", - ["x"], - ["c1"], - ) - end - @testset "scalarquadratic-objective" begin - test_model_equality( - """ - variables: x - minobjective: 1.0*x*x + -2.0x + 1.0 -""", - ["x"], - String[], - ) - end - @testset "SOS1" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in SOS1([1.0, 2.0, 3.0]) -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "SOS2" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in SOS2([1.0, 2.0, 3.0]) -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "Reals" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in Reals(3) -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "Zeros" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in Zeros(3) -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "Nonnegatives" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in Nonnegatives(3) -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "Nonpositives" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in Nonpositives(3) -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "PowerCone" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in PowerCone(2.0) -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "DualPowerCone" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in DualPowerCone(0.5) -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "GeometricMeanCone" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in GeometricMeanCone(3) -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "Complements" begin - test_model_equality( - "variables: x, y\nc1: [x, y] in Complements(2)", - ["x", "y"], - ["c1"], - ) - end - @testset "vectoraffine-in-zeros" begin - test_model_equality( - """ - variables: x, y - minobjective: x - c1: [1.0x + -3.0, 2.0y + -4.0] in Zeros(2) -""", - ["x", "y"], - ["c1"], - ) - end - @testset "vectorquadratic-in-nonnegatives" begin - test_model_equality( - """ - variables: x, y - minobjective: x - c1: [1.0*x*x + -2.0x + 1.0, 2.0y + -4.0] in Nonnegatives(2) -""", - ["x", "y"], - ["c1"], - ) - end - @testset "ExponentialCone" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in ExponentialCone() -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "DualExponentialCone" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in DualExponentialCone() -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "SecondOrderCone" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in SecondOrderCone(3) -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "RotatedSecondOrderCone" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in RotatedSecondOrderCone(3) -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "PositiveSemidefiniteConeTriangle" begin - test_model_equality( - """ - variables: x1, x2, x3 - minobjective: x1 - c1: [x1, x2, x3] in PositiveSemidefiniteConeTriangle(2) -""", - ["x1", "x2", "x3"], - ["c1"], - ) - end - @testset "PositiveSemidefiniteConeSquare" begin - test_model_equality( - """ - variables: x1, x2, x3, x4 - minobjective: x1 - c1: [x1, x2, x3, x4] in PositiveSemidefiniteConeSquare(2) -""", - ["x1", "x2", "x3", "x4"], - ["c1"], - ) - end - @testset "LogDetConeTriangle" begin - test_model_equality( - """ - variables: t, u, x1, x2, x3 - minobjective: x1 - c1: [t, u, x1, x2, x3] in LogDetConeTriangle(2) -""", - ["t", "u", "x1", "x2", "x3"], - ["c1"], - ) - end - @testset "LogDetConeSquare" begin - test_model_equality( - """ - variables: t, u, x1, x2, x3, x4 - minobjective: x1 - c1: [t, u, x1, x2, x3, x4] in LogDetConeSquare(2) -""", - ["t", "u", "x1", "x2", "x3", "x4"], - ["c1"], - ) - end - @testset "RootDetConeTriangle" begin - test_model_equality( - """ - variables: t, x1, x2, x3 - minobjective: x1 - c1: [t, x1, x2, x3] in RootDetConeTriangle(2) -""", - ["t", "x1", "x2", "x3"], - ["c1"], - ) - end - @testset "RootDetConeSquare" begin - test_model_equality( - """ - variables: t, x1, x2, x3, x4 - minobjective: x1 - c1: [t, x1, x2, x3, x4] in RootDetConeSquare(2) -""", - ["t", "x1", "x2", "x3", "x4"], - ["c1"], - ) - end - @testset "IndicatorSet" begin - test_model_equality( - """ - variables: x, y - minobjective: x - c1: [x, y] in IndicatorSet{ACTIVATE_ON_ONE}(GreaterThan(1.0)) - c2: x >= 0.0 -""", - ["x", "y"], - ["c1", "c2"], - ) - test_model_equality( - """ - variables: x, y - minobjective: x - c1: [x, y] in IndicatorSet{ACTIVATE_ON_ZERO}(GreaterThan(1.0)) - c2: x >= 0.0 -""", - ["x", "y"], - ["c1", "c2"], - ) - end - @testset "NormOneCone" begin - test_model_equality( - """ - variables: x, y - minobjective: x - c1: [x, y] in NormOneCone(2) - c2: x >= 0.0 -""", - ["x", "y"], - ["c1", "c2"], - ) - end - @testset "NormInfinityCone" begin - test_model_equality( - """ - variables: x, y - minobjective: x - c1: [x, y] in NormInfinityCone(2) - c2: x >= 0.0 -""", - ["x", "y"], - ["c1", "c2"], - ) - end - @testset "RelativeEntropyCone" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in RelativeEntropyCone(3) - c2: x >= 0.0 -""", - ["x", "y", "z"], - ["c1", "c2"], - ) - end - @testset "NormSpectralCone" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in NormSpectralCone(1, 2) -""", - ["x", "y", "z"], - ["c1"], - ) - end - @testset "NormNuclearCone" begin - test_model_equality( - """ - variables: x, y, z - minobjective: x - c1: [x, y, z] in NormNuclearCone(1, 2) -""", - ["x", "y", "z"], - ["c1"], - ) +function test_Duplicate_variable_name() + model = MOF.Model() + x = MOI.add_variable(model) + MOI.set(model, MOI.VariableName(), x, "x") + y = MOI.add_variable(model) + MOI.set(model, MOI.VariableName(), y, "x") + @test MOF.moi_to_object(x, model) == MOF.OrderedObject("name" => "x") + @test MOF.moi_to_object(y, model) == MOF.OrderedObject("name" => "x") + MOI.FileFormats.create_unique_names(model, warn = true) + @test MOF.moi_to_object(x, model) == MOF.OrderedObject("name" => "x") + @test MOF.moi_to_object(y, model) == MOF.OrderedObject("name" => "x_1") +end + +function test_Blank_constraint_name() + model = MOF.Model() + x = MOI.add_variable(model) + MOI.set(model, MOI.VariableName(), x, "x") + c = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.ZeroOne()) + name_map = Dict(x => "x") + MOI.FileFormats.create_unique_names(model, warn = true) + @test MOF.moi_to_object(c, model, name_map)["name"] == "c1" +end + +function test_Duplicate_constraint_name() + model = MOF.Model() + x = MOI.add_variable(model) + MOI.set(model, MOI.VariableName(), x, "x") + c1 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(1.0)) + c2 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(0.0)) + MOI.set(model, MOI.ConstraintName(), c1, "c") + MOI.set(model, MOI.ConstraintName(), c2, "c") + name_map = Dict(x => "x") + @test MOF.moi_to_object(c1, model, name_map)["name"] == "c" + @test MOF.moi_to_object(c2, model, name_map)["name"] == "c" + MOI.FileFormats.create_unique_names(model, warn = true) + @test MOF.moi_to_object(c1, model, name_map)["name"] == "c_1" + @test MOF.moi_to_object(c2, model, name_map)["name"] == "c" +end + +function test_empty_model() + model = MOF.Model() + MOI.write_to_file(model, TEST_MOF_FILE) + model_2 = MOF.Model() + MOI.read_from_file(model_2, TEST_MOF_FILE) + return MOIU.test_models_equal(model, model_2, String[], String[]) +end + +function test_FEASIBILITY_SENSE() + model = MOF.Model() + x = MOI.add_variable(model) + MOI.set(model, MOI.VariableName(), x, "x") + MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) + MOI.write_to_file(model, TEST_MOF_FILE) + model_2 = MOF.Model() + MOI.read_from_file(model_2, TEST_MOF_FILE) + return MOIU.test_models_equal(model, model_2, ["x"], String[]) +end + +function test_empty_function_term() + model = MOF.Model() + x = MOI.add_variable(model) + MOI.set(model, MOI.VariableName(), x, "x") + c = MOI.add_constraint( + model, + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}[], 0.0), + MOI.GreaterThan(1.0), + ) + MOI.set(model, MOI.ConstraintName(), c, "c") + MOI.write_to_file(model, TEST_MOF_FILE) + model_2 = MOF.Model() + MOI.read_from_file(model_2, TEST_MOF_FILE) + return MOIU.test_models_equal(model, model_2, ["x"], ["c"]) +end + +function test_min_objective() + return _test_model_equality( + """ +variables: x +minobjective: x +""", + ["x"], + String[], + ) +end + +function test_max_objective() + return _test_model_equality( + """ +variables: x +maxobjective: x +""", + ["x"], + String[], + suffix = ".gz", + ) +end + +function test_min_scalaraffine() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x + 0.5 +""", + ["x"], + String[], + ) +end + +function test_max_scalaraffine() + return _test_model_equality( + """ +variables: x +maxobjective: 1.2x + 0.5 +""", + ["x"], + String[], + suffix = ".gz", + ) +end + +function test_singlevariable_in_lower() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x + 0.5 +c1: x >= 1.0 +""", + ["x"], + ["c1"], + ) +end + +function test_singlevariable_in_upper() + return _test_model_equality( + """ +variables: x +maxobjective: 1.2x + 0.5 +c1: x <= 1.0 +""", + ["x"], + ["c1"], + suffix = ".gz", + ) +end + +function test_singlevariable_in_interval() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x + 0.5 +c1: x in Interval(1.0, 2.0) +""", + ["x"], + ["c1"], + ) +end + +function test_singlevariable_in_equalto() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x + 0.5 +c1: x == 1.0 +""", + ["x"], + ["c1"], + ) +end + +function test_singlevariable_in_zeroone() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x + 0.5 +c1: x in ZeroOne() +""", + ["x"], + ["c1"], + ) +end + +function test_singlevariable_in_integer() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x + 0.5 +c1: x in Integer() +""", + ["x"], + ["c1"], + ) +end + +function test_singlevariable_in_Semicontinuous() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x + 0.5 +c1: x in Semicontinuous(1.0, 2.0) +""", + ["x"], + ["c1"], + ) +end + +function test_singlevariable_in_Semiinteger() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x + 0.5 +c1: x in Semiinteger(1.0, 2.0) +""", + ["x"], + ["c1"], + ) +end + +function test_scalarquadratic_objective() + return _test_model_equality( + """ +variables: x +minobjective: 1.0*x*x + -2.0x + 1.0 +""", + ["x"], + String[], + ) +end + +function test_SOS1() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in SOS1([1.0, 2.0, 3.0]) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_SOS2() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in SOS2([1.0, 2.0, 3.0]) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_Reals() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in Reals(3) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_Zeros() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in Zeros(3) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_Nonnegatives() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in Nonnegatives(3) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_Nonpositives() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in Nonpositives(3) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_PowerCone() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in PowerCone(2.0) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_DualPowerCone() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in DualPowerCone(0.5) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_GeometricMeanCone() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in GeometricMeanCone(3) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_Complements() + return _test_model_equality( + "variables: x, y\nc1: [x, y] in Complements(2)", + ["x", "y"], + ["c1"], + ) +end + +function test_vectoraffine_in_zeros() + return _test_model_equality( + """ +variables: x, y +minobjective: x +c1: [1.0x + -3.0, 2.0y + -4.0] in Zeros(2) +""", + ["x", "y"], + ["c1"], + ) +end + +function test_vectorquadratic_in_nonnegatives() + return _test_model_equality( + """ +variables: x, y +minobjective: x +c1: [1.0*x*x + -2.0x + 1.0, 2.0y + -4.0] in Nonnegatives(2) +""", + ["x", "y"], + ["c1"], + ) +end + +function test_ExponentialCone() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in ExponentialCone() +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_DualExponentialCone() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in DualExponentialCone() +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_SecondOrderCone() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in SecondOrderCone(3) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_RotatedSecondOrderCone() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in RotatedSecondOrderCone(3) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_PositiveSemidefiniteConeTriangle() + return _test_model_equality( + """ +variables: x1, x2, x3 +minobjective: x1 +c1: [x1, x2, x3] in PositiveSemidefiniteConeTriangle(2) +""", + ["x1", "x2", "x3"], + ["c1"], + ) +end + +function test_PositiveSemidefiniteConeSquare() + return _test_model_equality( + """ +variables: x1, x2, x3, x4 +minobjective: x1 +c1: [x1, x2, x3, x4] in PositiveSemidefiniteConeSquare(2) +""", + ["x1", "x2", "x3", "x4"], + ["c1"], + ) +end + +function test_LogDetConeTriangle() + return _test_model_equality( + """ +variables: t, u, x1, x2, x3 +minobjective: x1 +c1: [t, u, x1, x2, x3] in LogDetConeTriangle(2) +""", + ["t", "u", "x1", "x2", "x3"], + ["c1"], + ) +end + +function test_LogDetConeSquare() + return _test_model_equality( + """ +variables: t, u, x1, x2, x3, x4 +minobjective: x1 +c1: [t, u, x1, x2, x3, x4] in LogDetConeSquare(2) +""", + ["t", "u", "x1", "x2", "x3", "x4"], + ["c1"], + ) +end + +function test_RootDetConeTriangle() + return _test_model_equality( + """ +variables: t, x1, x2, x3 +minobjective: x1 +c1: [t, x1, x2, x3] in RootDetConeTriangle(2) +""", + ["t", "x1", "x2", "x3"], + ["c1"], + ) +end + +function test_RootDetConeSquare() + return _test_model_equality( + """ +variables: t, x1, x2, x3, x4 +minobjective: x1 +c1: [t, x1, x2, x3, x4] in RootDetConeSquare(2) +""", + ["t", "x1", "x2", "x3", "x4"], + ["c1"], + ) +end + +function test_IndicatorSet() + _test_model_equality( + """ +variables: x, y +minobjective: x +c1: [x, y] in IndicatorSet{ACTIVATE_ON_ONE}(GreaterThan(1.0)) +c2: x >= 0.0 +""", + ["x", "y"], + ["c1", "c2"], + ) + + return _test_model_equality( + """ +variables: x, y +minobjective: x +c1: [x, y] in IndicatorSet{ACTIVATE_ON_ZERO}(GreaterThan(1.0)) +c2: x >= 0.0 +""", + ["x", "y"], + ["c1", "c2"], + ) +end + +function test_NormOneCone() + return _test_model_equality( + """ +variables: x, y +minobjective: x +c1: [x, y] in NormOneCone(2) +c2: x >= 0.0 +""", + ["x", "y"], + ["c1", "c2"], + ) +end + +function test_NormInfinityCone() + return _test_model_equality( + """ +variables: x, y +minobjective: x +c1: [x, y] in NormInfinityCone(2) +c2: x >= 0.0 +""", + ["x", "y"], + ["c1", "c2"], + ) +end + +function test_RelativeEntropyCone() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in RelativeEntropyCone(3) +c2: x >= 0.0 +""", + ["x", "y", "z"], + ["c1", "c2"], + ) +end + +function test_NormSpectralCone() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in NormSpectralCone(1, 2) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function test_NormNuclearCone() + return _test_model_equality( + """ +variables: x, y, z +minobjective: x +c1: [x, y, z] in NormNuclearCone(1, 2) +""", + ["x", "y", "z"], + ["c1"], + ) +end + +function runtests() + for name in names(@__MODULE__, all = true) + if startswith("$(name)", "test_") + @testset "$name" begin + getfield(@__MODULE__, name)() + end + end end - # Clean up sleep(1.0) # allow time for unlink to happen rm(TEST_MOF_FILE, force = true) rm(TEST_MOF_FILE * ".gz", force = true) + return +end + end + +TestMOF.runtests() diff --git a/test/FileFormats/MOF/nonlinear.jl b/test/FileFormats/MOF/nonlinear.jl deleted file mode 100644 index 9609e9b5d0..0000000000 --- a/test/FileFormats/MOF/nonlinear.jl +++ /dev/null @@ -1,193 +0,0 @@ -function roundtrip_nonlinear_expression( - expr, - variable_to_string, - string_to_variable, -) - node_list = MOF.OrderedObject[] - object = MOF.convert_expr_to_mof(expr, node_list, variable_to_string) - @test MOF.convert_mof_to_expr(object, node_list, string_to_variable) == expr -end - -# hs071 -# min x1 * x4 * (x1 + x2 + x3) + x3 -# st x1 * x2 * x3 * x4 >= 25 -# x1^2 + x2^2 + x3^2 + x4^2 = 40 -# 1 <= x1, x2, x3, x4 <= 5 -struct ExprEvaluator <: MOI.AbstractNLPEvaluator - objective::Expr - constraints::Vector{Expr} -end -MOI.features_available(::ExprEvaluator) = [:ExprGraph] -MOI.initialize(::ExprEvaluator, features) = nothing -MOI.objective_expr(evaluator::ExprEvaluator) = evaluator.objective -MOI.constraint_expr(evaluator::ExprEvaluator, i::Int) = evaluator.constraints[i] - -function HS071(x::Vector{MOI.VariableIndex}) - x1, x2, x3, x4 = x - return MOI.NLPBlockData( - MOI.NLPBoundsPair.([25, 40], [Inf, 40]), - ExprEvaluator( - :(x[$x1] * x[$x4] * (x[$x1] + x[$x2] + x[$x3]) + x[$x3]), - [ - :(x[$x1] * x[$x2] * x[$x3] * x[$x4] >= 25), - :(x[$x1]^2 + x[$x2]^2 + x[$x3]^2 + x[$x4]^2 == 40), - ], - ), - true, - ) -end - -@testset "Nonlinear functions" begin - @testset "HS071 via MOI" begin - model = MOF.Model() - x = MOI.add_variables(model, 4) - for (index, variable) in enumerate(x) - MOI.set(model, MOI.VariableName(), variable, "var_$(index)") - end - MOI.add_constraints( - model, - MOI.SingleVariable.(x), - Ref(MOI.Interval(1.0, 5.0)), - ) - MOI.set(model, MOI.NLPBlock(), HS071(x)) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.write_to_file(model, TEST_MOF_FILE) - @test replace(read(TEST_MOF_FILE, String), '\r' => "") == replace( - read(joinpath(@__DIR__, "nlp.mof.json"), String), - '\r' => "", - ) - return _validate(TEST_MOF_FILE) - end - @testset "Error handling" begin - node_list = MOF.Object[] - string_to_variable = Dict{String,MOI.VariableIndex}() - variable_to_string = Dict{MOI.VariableIndex,String}() - # Test unsupported function for Expr -> MOF. - @test_throws Exception MOF.convert_expr_to_mof( - :(not_supported_function(x)), - node_list, - variable_to_string, - ) - # Test unsupported function for MOF -> Expr. - @test_throws Exception MOF.convert_mof_to_expr( - MOF.OrderedObject("type" => "not_supported_function", "value" => 1), - node_list, - string_to_variable, - ) - # Test n-ary function with no arguments. - @test_throws Exception MOF.convert_expr_to_mof( - :(min()), - node_list, - variable_to_string, - ) - # Test unary function with two arguments. - @test_throws Exception MOF.convert_expr_to_mof( - :(sin(x, y)), - node_list, - variable_to_string, - ) - # Test binary function with one arguments. - @test_throws Exception MOF.convert_expr_to_mof( - :(^(x)), - node_list, - variable_to_string, - ) - # An expression with something other than :call as the head. - @test_throws Exception MOF.convert_expr_to_mof( - :(a <= b <= c), - node_list, - variable_to_string, - ) - # Hit the default fallback with an un-interpolated complex number. - @test_throws Exception MOF.convert_expr_to_mof( - :(1 + 2im), - node_list, - variable_to_string, - ) - # Invalid number of variables. - @test_throws Exception MOF.substitute_variables( - :(x[1] * x[2]), - [MOI.VariableIndex(1)], - ) - # Function-in-Set - @test_throws Exception MOF.extract_function_and_set(:(foo in set)) - # Not a constraint. - @test_throws Exception MOF.extract_function_and_set(:(x^2)) - # Two-sided constraints - @test MOF.extract_function_and_set(:(1 <= x <= 2)) == - MOF.extract_function_and_set(:(2 >= x >= 1)) == - (:x, MOI.Interval(1, 2)) - # Less-than constraint. - @test MOF.extract_function_and_set(:(x <= 2)) == (:x, MOI.LessThan(2)) - end - @testset "Roundtrip nonlinear expressions" begin - x = MOI.VariableIndex(123) - y = MOI.VariableIndex(456) - z = MOI.VariableIndex(789) - string_to_var = - Dict{String,MOI.VariableIndex}("x" => x, "y" => y, "z" => z) - var_to_string = - Dict{MOI.VariableIndex,String}(x => "x", y => "y", z => "z") - for expr in [ - 2, - 2.34, - 2 + 3im, - x, - :(1 + $x), - :($x - 1), - :($x + $y), - :($x + $y - $z), - :(2 * $x), - :($x * $y), - :($x / 2), - :(2 / $x), - :($x / $y), - :($x / $y / $z), - :(2^$x), - :($x^2), - :($x^$y), - :($x^(2 * $y + 1)), - :(sin($x)), - :(sin($x + $y)), - :(2 * $x + sin($x)^2 + $y), - :(sin($(3im))^2 + cos($(3im))^2), - :($(1 + 2im) * $x), - :(ceil($x)), - :(floor($x)), - :($x < $y), - :($x <= $y), - :($x > $y), - :($x >= $y), - :($x == $y), - :($x != $y), - # :($x && $y), :($x || $y), - :(ifelse($x > 0, 1, $y)), - ] - roundtrip_nonlinear_expression(expr, var_to_string, string_to_var) - end - end - @testset "Reading and Writing" begin - # Write to file. - model = MOF.Model() - (x, y) = MOI.add_variables(model, 2) - MOI.set(model, MOI.VariableName(), x, "var_x") - MOI.set(model, MOI.VariableName(), y, "y") - con = MOI.add_constraint( - model, - MOF.Nonlinear(:(2 * $x + sin($x)^2 - $y)), - MOI.EqualTo(1.0), - ) - MOI.set(model, MOI.ConstraintName(), con, "con") - MOI.write_to_file(model, TEST_MOF_FILE) - # Read the model back in. - model2 = MOF.Model() - MOI.read_from_file(model2, TEST_MOF_FILE) - con2 = MOI.get(model2, MOI.ConstraintIndex, "con") - foo2 = MOI.get(model2, MOI.ConstraintFunction(), con2) - # Test that we recover the constraint. - @test foo2.expr == :(2 * $x + sin($x)^2 - $y) - @test MOI.get(model, MOI.ConstraintSet(), con) == - MOI.get(model2, MOI.ConstraintSet(), con2) - return _validate(TEST_MOF_FILE) - end -end diff --git a/test/FileFormats/MPS/MPS.jl b/test/FileFormats/MPS/MPS.jl index f3bfad456f..d8525cc512 100644 --- a/test/FileFormats/MPS/MPS.jl +++ b/test/FileFormats/MPS/MPS.jl @@ -1,3 +1,5 @@ +module TestMPS + import MathOptInterface using Test @@ -6,10 +8,7 @@ const MOIU = MOI.Utilities const MPS = MOI.FileFormats.MPS const MPS_TEST_FILE = "test.mps" -@test sprint(show, MPS.Model()) == - "A Mathematical Programming System (MPS) model" - -function test_model_equality(model_string, variables, constraints) +function _test_model_equality(model_string, variables, constraints) model = MPS.Model() MOIU.loadfromstring!(model, model_string) MOI.write_to_file(model, MPS_TEST_FILE) @@ -18,49 +17,52 @@ function test_model_equality(model_string, variables, constraints) return MOIU.test_models_equal(model, model_2, variables, constraints) end -@testset "Errors" begin - failing_models_dir = joinpath(@__DIR__, "failing_models") - - @testset "Quadratic Objective" begin - model = MPS.Model() - @test_throws( - MOI.UnsupportedAttribute, - MOIU.loadfromstring!( - model, - """ - variables: x - minobjective: 1.0*x*x - """, - ) - ) - end +function test_show() + @test sprint(show, MPS.Model()) == + "A Mathematical Programming System (MPS) model" +end - @testset "Non-empty model" begin - model = MPS.Model() - @test MOI.is_empty(model) - MOI.add_variable(model) - @test !MOI.is_empty(model) - MOI.empty!(model) - @test MOI.is_empty(model) - MOI.add_variable(model) - @test_throws Exception MOI.read_from_file( +function test_quadratic() + model = MPS.Model() + @test_throws( + MOI.UnsupportedAttribute, + MOIU.loadfromstring!( model, - joinpath(failing_models_dir, "bad_name.mps"), + """ +variables: x +minobjective: 1.0*x*x +""", ) - end + ) +end +function test_nonempty() + model = MPS.Model() + @test MOI.is_empty(model) + MOI.add_variable(model) + @test !MOI.is_empty(model) + MOI.empty!(model) + @test MOI.is_empty(model) + MOI.add_variable(model) + @test_throws Exception MOI.read_from_file( + model, + joinpath(@__DIR__, "failing_models", "bad_name.mps"), + ) +end + +function test_failing_models() @testset "$(filename)" for filename in filter( f -> endswith(f, ".mps"), - readdir(failing_models_dir), + readdir(joinpath(@__DIR__, "failing_models")), ) @test_throws Exception MOI.read_from_file( MPS.Model(), - joinpath(failing_models_dir, filename), + joinpath(@__DIR__, "failing_models", filename), ) end end -@testset "ROWS - empty ROW name" begin +function test_empty_row_name() model = MPS.Model() x = MOI.add_variable(model) MOI.add_constraint( @@ -71,7 +73,7 @@ end @test_throws Exception sprint(MPS.write_rows, model) end -@testset "SOS" begin +function test_sos() model = MPS.Model() x = MOI.add_variables(model, 3) names = Dict{MOI.VariableIndex,String}() @@ -101,7 +103,7 @@ end " x3 3.25\n" end -@testset "Maximization problems" begin +function test_maximization() model = MPS.Model() x = MOI.add_variable(model) MOI.set(model, MOI.VariableName(), x, "x") @@ -115,7 +117,7 @@ end "COLUMNS\n x OBJ -1\n" end -@testset "stacked_data" begin +function test_stacked_data() model = MPS.Model() MOI.read_from_file(model, joinpath(@__DIR__, "stacked_data.mps")) MOI.set( @@ -164,7 +166,7 @@ con7: z in ZeroOne() """, ) MOI.set(model_2, MOI.Name(), "stacked_data") - MOIU.test_models_equal( + return MOIU.test_models_equal( model, model_2, ["x", "y", "z"], @@ -172,7 +174,7 @@ con7: z in ZeroOne() ) end -@testset "free_integer" begin +function test_free_integer() model = MPS.Model() MOI.read_from_file(model, joinpath(@__DIR__, "free_integer.mps")) MOI.set( @@ -194,283 +196,307 @@ con1: 1.0 * x >= 1.0 con2: x in Integer() """, ) - MOIU.test_models_equal(model, model_2, ["x"], ["con1", "con2"]) + return MOIU.test_models_equal(model, model_2, ["x"], ["con1", "con2"]) end -@testset "Round trips" begin - @testset "min objective" begin - test_model_equality( - """ +function test_min_objective() + return _test_model_equality( + """ variables: x minobjective: x """, - ["x"], - String[], - ) - end - @testset "default RHS >=" begin - test_model_equality( - """ - variables: x - minobjective: x - c1: 2.0 * x >= 0.0 + ["x"], + String[], + ) +end + +function test_default_rhs_greater() + return _test_model_equality( + """ +variables: x +minobjective: x +c1: 2.0 * x >= 0.0 """, - ["x"], - ["c1"], - ) - end - @testset "default RHS <=" begin - test_model_equality( - """ + ["x"], + ["c1"], + ) +end + +function test_default_rhs_less() + return _test_model_equality( + """ variables: x minobjective: x c1: 2.0 * x <= 0.0 """, - ["x"], - ["c1"], - ) - end - @testset "default RHS ==" begin - test_model_equality( - """ - variables: x - minobjective: x - c1: 2.0 * x == 0.0 + ["x"], + ["c1"], + ) +end + +function test_default_rhs_equal() + return _test_model_equality( + """ +variables: x +minobjective: x +c1: 2.0 * x == 0.0 """, - ["x"], - ["c1"], - ) - end - @testset "min scalaraffine" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x + ["x"], + ["c1"], + ) +end + +function test_min_scalaraffine() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x """, - ["x"], - String[], - ) - end + ["x"], + String[], + ) +end - @testset "ScalarAffine-in-GreaterThan" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x - c1: 1.1 * x >= 2.0 +function test_scalaraffine_greaterthan() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x +c1: 1.1 * x >= 2.0 """, - ["x"], - ["c1"], - ) - end - @testset "ScalarAffine-in-LessThan" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x - c1: 1.1 * x <= 2.0 + ["x"], + ["c1"], + ) +end + +function test_scalaraffine_lessthan() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x +c1: 1.1 * x <= 2.0 """, - ["x"], - ["c1"], - ) - end - @testset "ScalarAffine-in-EqualTo" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x - c1: 1.1 * x == 2.0 + ["x"], + ["c1"], + ) +end + +function test_scalaraffine_equalto() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x +c1: 1.1 * x == 2.0 """, - ["x"], - ["c1"], - ) - end - @testset "ScalarAffine-in-Interval" begin - test_model_equality( - """ - variables: x - minobjective: 1.2x - c1: 1.1 * x in Interval(1.0, 2.0) + ["x"], + ["c1"], + ) +end + +function test_scalaraffine_interval() + return _test_model_equality( + """ +variables: x +minobjective: 1.2x +c1: 1.1 * x in Interval(1.0, 2.0) """, - ["x"], - ["c1"], - ) - end - @testset "MARKER INT" begin - model = MPS.Model() - MOIU.loadfromstring!( - model, - """ - variables: x, y, z - minobjective: x + y + z - c1: x in Integer() - c2: 2 * x + -1.0 * z <= 1.0 - c3: z in ZeroOne() - c4: x >= 1.0 + ["x"], + ["c1"], + ) +end + +function test_MARKER_INT() + model = MPS.Model() + MOIU.loadfromstring!( + model, + """ +variables: x, y, z +minobjective: x + y + z +c1: x in Integer() +c2: 2 * x + -1.0 * z <= 1.0 +c3: z in ZeroOne() +c4: x >= 1.0 """, - ) - MOI.write_to_file(model, MPS_TEST_FILE) - model_2 = MPS.Model() - MOI.read_from_file(model_2, MPS_TEST_FILE) - for (set_type, constraint_name) in [ - (MOI.Integer, "c1"), - (MOI.ZeroOne, "c3"), - (MOI.GreaterThan{Float64}, "c4"), - ] - MOI.set( - model_2, - MOI.ConstraintName(), - MOI.get( - model_2, - MOI.ListOfConstraintIndices{MOI.SingleVariable,set_type}(), - )[1], - constraint_name, - ) - end - MOIU.test_models_equal( - model, + ) + MOI.write_to_file(model, MPS_TEST_FILE) + model_2 = MPS.Model() + MOI.read_from_file(model_2, MPS_TEST_FILE) + for (set_type, constraint_name) in [ + (MOI.Integer, "c1"), + (MOI.ZeroOne, "c3"), + (MOI.GreaterThan{Float64}, "c4"), + ] + MOI.set( model_2, - ["x", "y", "z"], - ["c1", "c2", "c3", "c4"], + MOI.ConstraintName(), + MOI.get( + model_2, + MOI.ListOfConstraintIndices{MOI.SingleVariable,set_type}(), + )[1], + constraint_name, ) end - @testset "Zero variable bounds" begin - model = MPS.Model() - MOIU.loadfromstring!( - model, - """ - variables: x, y, z - minobjective: x + y + z - c1: x >= 0.0 - c2: y <= 0.0 + return MOIU.test_models_equal( + model, + model_2, + ["x", "y", "z"], + ["c1", "c2", "c3", "c4"], + ) +end + +function test_zero_variable_bounds() + model = MPS.Model() + MOIU.loadfromstring!( + model, + """ +variables: x, y, z +minobjective: x + y + z +c1: x >= 0.0 +c2: y <= 0.0 """, - ) - MOI.write_to_file(model, MPS_TEST_FILE) - model_2 = MPS.Model() - MOI.read_from_file(model_2, MPS_TEST_FILE) - for (set_type, constraint_name) in - [(MOI.GreaterThan{Float64}, "c1"), (MOI.LessThan{Float64}, "c2")] - MOI.set( + ) + MOI.write_to_file(model, MPS_TEST_FILE) + model_2 = MPS.Model() + MOI.read_from_file(model_2, MPS_TEST_FILE) + for (set_type, constraint_name) in + [(MOI.GreaterThan{Float64}, "c1"), (MOI.LessThan{Float64}, "c2")] + MOI.set( + model_2, + MOI.ConstraintName(), + MOI.get( model_2, - MOI.ConstraintName(), - MOI.get( - model_2, - MOI.ListOfConstraintIndices{MOI.SingleVariable,set_type}(), - )[1], - constraint_name, - ) - end - MOIU.test_models_equal(model, model_2, ["x", "y", "z"], ["c1", "c2"]) + MOI.ListOfConstraintIndices{MOI.SingleVariable,set_type}(), + )[1], + constraint_name, + ) end - @testset "Non-zero variable bounds" begin - model = MPS.Model() - MOIU.loadfromstring!( - model, - """ - variables: w, x, y, z - minobjective: w + x + y + z - c1: x == 1.0 - c2: y >= 2.0 - c3: z <= 3.0 - c4: w in Interval(4.0, 5.0) + return MOIU.test_models_equal(model, model_2, ["x", "y", "z"], ["c1", "c2"]) +end + +function test_nonzero_variable_bounds() + model = MPS.Model() + MOIU.loadfromstring!( + model, + """ +variables: w, x, y, z +minobjective: w + x + y + z +c1: x == 1.0 +c2: y >= 2.0 +c3: z <= 3.0 +c4: w in Interval(4.0, 5.0) """, - ) - MOI.write_to_file(model, MPS_TEST_FILE) - model_2 = MPS.Model() - MOI.read_from_file(model_2, MPS_TEST_FILE) - for (set_type, constraint_name) in [ - (MOI.EqualTo{Float64}, "c1"), - (MOI.GreaterThan{Float64}, "c2"), - (MOI.LessThan{Float64}, "c3"), - (MOI.Interval{Float64}, "c4"), - ] - MOI.set( - model_2, - MOI.ConstraintName(), - MOI.get( - model_2, - MOI.ListOfConstraintIndices{MOI.SingleVariable,set_type}(), - )[1], - constraint_name, - ) - end - MOIU.test_models_equal( - model, + ) + MOI.write_to_file(model, MPS_TEST_FILE) + model_2 = MPS.Model() + MOI.read_from_file(model_2, MPS_TEST_FILE) + for (set_type, constraint_name) in [ + (MOI.EqualTo{Float64}, "c1"), + (MOI.GreaterThan{Float64}, "c2"), + (MOI.LessThan{Float64}, "c3"), + (MOI.Interval{Float64}, "c4"), + ] + MOI.set( model_2, - ["w", "x", "y", "z"], - ["c1", "c2", "c3", "c4"], + MOI.ConstraintName(), + MOI.get( + model_2, + MOI.ListOfConstraintIndices{MOI.SingleVariable,set_type}(), + )[1], + constraint_name, ) end - @testset "Multiple variable bounds" begin - model = MPS.Model() - MOIU.loadfromstring!( - model, - """ - variables: a_really_long_name - minobjective: a_really_long_name - c1: a_really_long_name >= 1.0 - c2: a_really_long_name <= 2.0 + return MOIU.test_models_equal( + model, + model_2, + ["w", "x", "y", "z"], + ["c1", "c2", "c3", "c4"], + ) +end + +function test_multiple_variable_bounds() + model = MPS.Model() + MOIU.loadfromstring!( + model, + """ +variables: a_really_long_name +minobjective: a_really_long_name +c1: a_really_long_name >= 1.0 +c2: a_really_long_name <= 2.0 """, - ) - MOI.write_to_file(model, MPS_TEST_FILE) - @test read(MPS_TEST_FILE, String) == - "NAME \n" * - "ROWS\n" * - " N OBJ\n" * - "COLUMNS\n" * - " a_really_long_name OBJ 1\n" * - "RHS\n" * - "RANGES\n" * - "BOUNDS\n" * - " LO bounds a_really_long_name 1\n" * - " UP bounds a_really_long_name 2\n" * - "ENDATA\n" - end - @testset "Un-used variable" begin - # In this test, `x` will not be written to the file since it does not - # appear in the objective or in the constriants. - model = MPS.Model() - MOIU.loadfromstring!( - model, - """ - variables: x, y - minobjective: y - c1: 2.0 * y >= 1.0 - c2: x >= 0.0 + ) + MOI.write_to_file(model, MPS_TEST_FILE) + @test read(MPS_TEST_FILE, String) == + "NAME \n" * + "ROWS\n" * + " N OBJ\n" * + "COLUMNS\n" * + " a_really_long_name OBJ 1\n" * + "RHS\n" * + "RANGES\n" * + "BOUNDS\n" * + " LO bounds a_really_long_name 1\n" * + " UP bounds a_really_long_name 2\n" * + "ENDATA\n" +end + +function test_unused_variable() + # In this test, `x` will not be written to the file since it does not + # appear in the objective or in the constriants. + model = MPS.Model() + MOIU.loadfromstring!( + model, + """ +variables: x, y +minobjective: y +c1: 2.0 * y >= 1.0 +c2: x >= 0.0 """, - ) - MOI.write_to_file(model, MPS_TEST_FILE) - @test MOI.get(model, MOI.NumberOfVariables()) == 2 - model2 = MPS.Model() - MOI.read_from_file(model2, MPS_TEST_FILE) - @test MOI.get(model2, MOI.NumberOfVariables()) == 1 - end - @testset "Names with spaces" begin - model = MPS.Model() - x = MOI.add_variable(model) - MOI.set(model, MOI.VariableName(), x, "x[1, 2]") - c = MOI.add_constraint( - model, - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), - MOI.EqualTo(1.0), - ) - MOI.set(model, MOI.ConstraintName(), c, "c c") - @test sprint(write, model) == - "NAME \n" * - "ROWS\n" * - " N OBJ\n" * - " E c_c\n" * - "COLUMNS\n" * - " x[1,_2] c_c 1\n" * - "RHS\n" * - " rhs c_c 1\n" * - "RANGES\n" * - "BOUNDS\n" * - " FR bounds x[1,_2]\n" * - "ENDATA\n" + ) + MOI.write_to_file(model, MPS_TEST_FILE) + @test MOI.get(model, MOI.NumberOfVariables()) == 2 + model2 = MPS.Model() + MOI.read_from_file(model2, MPS_TEST_FILE) + @test MOI.get(model2, MOI.NumberOfVariables()) == 1 +end + +function test_names_with_spaces() + model = MPS.Model() + x = MOI.add_variable(model) + MOI.set(model, MOI.VariableName(), x, "x[1, 2]") + c = MOI.add_constraint( + model, + MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), + MOI.EqualTo(1.0), + ) + MOI.set(model, MOI.ConstraintName(), c, "c c") + @test sprint(write, model) == + "NAME \n" * + "ROWS\n" * + " N OBJ\n" * + " E c_c\n" * + "COLUMNS\n" * + " x[1,_2] c_c 1\n" * + "RHS\n" * + " rhs c_c 1\n" * + "RANGES\n" * + "BOUNDS\n" * + " FR bounds x[1,_2]\n" * + "ENDATA\n" +end + +function runtests() + for name in names(@__MODULE__, all = true) + if startswith("$(name)", "test_") + @testset "name" begin + getfield(@__MODULE__, name)() + end + end end + sleep(1.0) # Allow time for unlink to happen. + rm(MPS_TEST_FILE, force = true) + return +end + end -# Clean up -sleep(1.0) # Allow time for unlink to happen. -rm(MPS_TEST_FILE, force = true) +TestMPS.runtests() diff --git a/test/FileFormats/SDPA/SDPA.jl b/test/FileFormats/SDPA/SDPA.jl index dbaf0781f7..5343fdd78c 100644 --- a/test/FileFormats/SDPA/SDPA.jl +++ b/test/FileFormats/SDPA/SDPA.jl @@ -1,3 +1,5 @@ +module TestSDPA + import MathOptInterface using Test @@ -7,7 +9,7 @@ const SDPA = MOI.FileFormats.SDPA const SDPA_TEST_FILE = "test.sdpa" const SDPA_MODELS_DIR = joinpath(@__DIR__, "models") -function set_var_and_con_names(model::MOI.ModelLike) +function _set_var_and_con_names(model::MOI.ModelLike) variable_names = String[] for j in MOI.get(model, MOI.ListOfVariableIndices()) var_name_j = "v" * string(j.value) @@ -46,15 +48,15 @@ function set_var_and_con_names(model::MOI.ModelLike) return (variable_names, constraint_names) end -function test_write_then_read(model_string::String) +function _test_write_then_read(model_string::String) model1 = SDPA.Model() MOIU.loadfromstring!(model1, model_string) - (variable_names, constraint_names) = set_var_and_con_names(model1) + (variable_names, constraint_names) = _set_var_and_con_names(model1) MOI.write_to_file(model1, SDPA_TEST_FILE) model2 = SDPA.Model() MOI.read_from_file(model2, SDPA_TEST_FILE) - set_var_and_con_names(model2) + _set_var_and_con_names(model2) if MOI.get(model1, MOI.ObjectiveSense()) == MOI.MAX_SENSE MOI.set(model2, MOI.ObjectiveSense(), MOI.MAX_SENSE) attr = MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}() @@ -70,14 +72,14 @@ function test_write_then_read(model_string::String) ) end -function test_read(filename::String, model_string::String) +function _test_read(filename::String, model_string::String) model1 = MOI.FileFormats.Model(filename = filename) MOIU.loadfromstring!(model1, model_string) - (variable_names, constraint_names) = set_var_and_con_names(model1) + (variable_names, constraint_names) = _set_var_and_con_names(model1) model2 = SDPA.Model() MOI.read_from_file(model2, filename) - set_var_and_con_names(model2) + _set_var_and_con_names(model2) return MOIU.test_models_equal( model1, @@ -87,11 +89,13 @@ function test_read(filename::String, model_string::String) ) end -@test sprint(show, SDPA.Model()) == - "A SemiDefinite Programming Algorithm Format (SDPA) model" +function test_show() + @test sprint(show, SDPA.Model()) == + "A SemiDefinite Programming Algorithm Format (SDPA) model" +end -@testset "Support errors" begin - @testset "$set variable bound" for set in [ +function test_support() + for set in [ MOI.EqualTo(1.0), MOI.LessThan(1.0), MOI.GreaterThan(1.0), @@ -112,116 +116,114 @@ end end end -@testset "Deleted variables error with $T" for T in [Int, Float64] - model = SDPA.Model(; number_type = T) - x = MOI.add_variable(model) - MOI.delete(model, x) - y = MOI.add_variable(model) - fy = MOI.SingleVariable(y) - MOI.add_constraint( - model, - MOIU.vectorize([one(T) * fy]), - MOI.Nonnegatives(1), - ) - err = ErrorException( - "Non-contiguous variable indices not supported. This might be due to deleted variables.", - ) - @test_throws err MOI.write_to_file(model, SDPA_TEST_FILE) -end - -@testset "Objective function with $T" for T in [Int, Float64] - model = SDPA.Model(; number_type = T) - @test !MOI.supports(model, MOI.ObjectiveFunction{MOI.SingleVariable}()) - @test !MOI.supports( - model, - MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}(), - ) -end - -@testset "Read errors" begin - @testset "Non-empty model" begin - model = SDPA.Model() - MOI.add_variable(model) - err = ErrorException("Cannot read in file because model is not empty.") - @test_throws err MOI.read_from_file( +function test_delete() + for T in [Int, Float64] + model = SDPA.Model(; number_type = T) + x = MOI.add_variable(model) + MOI.delete(model, x) + y = MOI.add_variable(model) + fy = MOI.SingleVariable(y) + MOI.add_constraint( model, - joinpath(SDPA_MODELS_DIR, "example_A.dat-s"), + MOIU.vectorize([one(T) * fy]), + MOI.Nonnegatives(1), ) - end - - @testset "Bad number of blocks" begin - model = SDPA.Model() err = ErrorException( - "The number of blocks (3) does not match the length of the list of blocks dimensions (2).", - ) - @test_throws err MOI.read_from_file( - model, - joinpath(SDPA_MODELS_DIR, "bad_blocks.sdpa"), + "Non-contiguous variable indices not supported. This might be due to deleted variables.", ) + @test_throws err MOI.write_to_file(model, SDPA_TEST_FILE) end +end - @testset "Bad number of variables" begin - model = SDPA.Model() - err = ErrorException( - "The number of variables (3) does not match the length of the list of coefficients for the objective function vector of coefficients (2).", - ) - @test_throws err MOI.read_from_file( +function test_objective() + for T in [Int, Float64] + model = SDPA.Model(; number_type = T) + @test !MOI.supports(model, MOI.ObjectiveFunction{MOI.SingleVariable}()) + @test !MOI.supports( model, - joinpath(SDPA_MODELS_DIR, "bad_vars.sdpa"), + MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}(), ) end +end - @testset "Wrong number of values in entry" begin - model = SDPA.Model() - err = ErrorException( - "Invalid line specifying entry: 0 1 2 2. There are 4 values instead of 5.", - ) - @test_throws err MOI.read_from_file( - model, - joinpath(SDPA_MODELS_DIR, "bad_entry.sdpa"), - ) - end +function test_nonempty() + model = SDPA.Model() + MOI.add_variable(model) + err = ErrorException("Cannot read in file because model is not empty.") + @test_throws err MOI.read_from_file( + model, + joinpath(SDPA_MODELS_DIR, "example_A.dat-s"), + ) +end - @testset "Non-diagonal entry in diagonal block" begin - model = SDPA.Model() - err = ErrorException( - "Invalid line specifying entry: 0 1 1 2 1.0. `1 != 2` while block 1 has dimension 2 so it is a diagonal block.", - ) - @test_throws err MOI.read_from_file( - model, - joinpath(SDPA_MODELS_DIR, "bad_diag.sdpa"), - ) - end +function test_bad_blocks() + model = SDPA.Model() + err = ErrorException( + "The number of blocks (3) does not match the length of the list of blocks dimensions (2).", + ) + @test_throws err MOI.read_from_file( + model, + joinpath(SDPA_MODELS_DIR, "bad_blocks.sdpa"), + ) end -@testset "Write errors" begin - @testset "Nonzero constant in objective" begin - model = SDPA.Model() - MOIU.loadfromstring!( - model, - """ - variables: x - minobjective: x + 1 -""", - ) - err = ErrorException( - "Nonzero constant in objective function not supported. Note that " * - "the constant may be added by the substitution of a bridged variable.", - ) - @test_throws err MOI.write_to_file(model, SDPA_TEST_FILE) - end +function test_bad_number_variables() + model = SDPA.Model() + err = ErrorException( + "The number of variables (3) does not match the length of the list of coefficients for the objective function vector of coefficients (2).", + ) + @test_throws err MOI.read_from_file( + model, + joinpath(SDPA_MODELS_DIR, "bad_vars.sdpa"), + ) +end + +function test_wrong_number_of_values_in_entry() + model = SDPA.Model() + err = ErrorException( + "Invalid line specifying entry: 0 1 2 2. There are 4 values instead of 5.", + ) + @test_throws err MOI.read_from_file( + model, + joinpath(SDPA_MODELS_DIR, "bad_entry.sdpa"), + ) +end + +function test_nondiagonal_entry() + model = SDPA.Model() + err = ErrorException( + "Invalid line specifying entry: 0 1 1 2 1.0. `1 != 2` while block 1 has dimension 2 so it is a diagonal block.", + ) + @test_throws err MOI.read_from_file( + model, + joinpath(SDPA_MODELS_DIR, "bad_diag.sdpa"), + ) +end - # TODO NLP not supported test. +function test_nonzero_in_objective() + model = SDPA.Model() + MOIU.loadfromstring!( + model, + """ +variables: x +minobjective: x + 1 +""", + ) + err = ErrorException( + "Nonzero constant in objective function not supported. Note that " * + "the constant may be added by the substitution of a bridged variable.", + ) + @test_throws err MOI.write_to_file(model, SDPA_TEST_FILE) end -@testset "Model name" begin +function test_model_name() model = SDPA.Model() MOI.set(model, MOI.Name(), "FooBar") MOI.write_to_file(model, SDPA_TEST_FILE) @test readlines(SDPA_TEST_FILE) == ["\"FooBar", "0", "0", "", ""] end -write_read_models = [ +const _WRITE_READ_MODELS = [ ( "min ScalarAffine", """ @@ -253,12 +255,14 @@ write_read_models = [ """, ), ] -@testset "Write/read $model_name" for (model_name, model_string) in - write_read_models - test_write_then_read(model_string) + +function test_write_read_models() + for (model_name, model_string) in _WRITE_READ_MODELS + _test_write_then_read(model_string) + end end -example_models = [ +const _EXAMPLE_MODELS = [ ( "example_A.dat-s", """ @@ -290,12 +294,27 @@ example_models = [ """, ), ] -@testset "Read and write/read $model_name" for (model_name, model_string) in - example_models - test_read(joinpath(SDPA_MODELS_DIR, model_name), model_string) - test_write_then_read(model_string) + +function test_examples() + for (model_name, model_string) in _EXAMPLE_MODELS + _test_read(joinpath(SDPA_MODELS_DIR, model_name), model_string) + _test_write_then_read(model_string) + end +end + +function runtests() + for name in names(@__MODULE__, all = true) + if startswith("$(name)", "test_") + @testset "name" begin + getfield(@__MODULE__, name)() + end + end + end + sleep(1.0) # Allow time for unlink to happen. + rm(SDPA_TEST_FILE, force = true) + return +end + end -# Clean up. -sleep(1.0) # Allow time for unlink to happen. -rm(SDPA_TEST_FILE, force = true) +TestSDPA.runtests()