From d1de3bb03e1680e4f71c677e50b4d3b41946f4f0 Mon Sep 17 00:00:00 2001 From: Kibaek Kim Date: Tue, 28 Apr 2020 00:04:07 -0500 Subject: [PATCH 1/2] feature to read/write integer constraints --- src/FileFormats/SDPA/SDPA.jl | 38 ++++++++++++++++++- test/FileFormats/SDPA/SDPA.jl | 12 +++++- .../SDPA/models/example_integer.sdpa | 25 ++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 test/FileFormats/SDPA/models/example_integer.sdpa diff --git a/src/FileFormats/SDPA/SDPA.jl b/src/FileFormats/SDPA/SDPA.jl index 9b9fe9278e..5b862631c3 100644 --- a/src/FileFormats/SDPA/SDPA.jl +++ b/src/FileFormats/SDPA/SDPA.jl @@ -8,7 +8,7 @@ import MathOptInterface const MOI = MathOptInterface MOI.Utilities.@model(Model, - (), + (MOI.Integer,), (), (MOI.Nonnegatives, MOI.PositiveSemidefiniteConeTriangle), (), @@ -26,6 +26,10 @@ function MOI.supports_constraint( return false end +function MOI.supports_constraint( + ::Model, ::Type{MOI.SingleVariable}, ::Type{MOI.Integer}) + return true +end function MOI.supports( ::Model, @@ -202,6 +206,16 @@ function Base.write(io::IO, model::Model{T}) where {T} for i in eachindex(psd) _print_constraint(length(nonneg) + i, true, psd[i]) end + + # Integrality constraints. + # Based on the extension: http://www.opt.tu-darmstadt.de/scipsdp/downloads/data_format.txt + integer_cons = model_cons(MOI.SingleVariable, MOI.Integer) + if length(integer_cons) > 0 + println(io, "*INTEGER") + for con_idx in integer_cons + println(io, "*$(con_function(con_idx).variable.value)") + end + end return end @@ -242,6 +256,9 @@ function Base.read!(io::IO, model::Model{T}) where T end end objective_read = false + integer_read = false + scalar_vars = nothing + intvar_idx = Int[] c = nothing funcs = nothing MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) @@ -251,6 +268,20 @@ function Base.read!(io::IO, model::Model{T}) where T if startswith(line, '"') continue end + # The lines starting with * should also be skipped + # according to http://plato.asu.edu/ftp/sdpa_format.txt. + if startswith(line, '*') + # Exceptions for integer variables + if startswith(line, "*INTEGER") + integer_read = true + elseif integer_read + if !num_variables_read + error("The number of variables should be given before *INTEGER section.") + end + push!(intvar_idx, parse(Int, strip(line[2:end]))) + end + continue + end if !num_variables_read if isempty(line) continue @@ -258,7 +289,7 @@ function Base.read!(io::IO, model::Model{T}) where T num_variables_read = true # According to http://plato.asu.edu/ftp/sdpa_format.txt, # additional text after the number of variables should be ignored. - MOI.add_variables(model, parse(Int, split(line)[1])) + scalar_vars = MOI.add_variables(model, parse(Int, split(line)[1])) elseif num_blocks === nothing if isempty(line) continue @@ -328,6 +359,9 @@ function Base.read!(io::IO, model::Model{T}) where T for block in 1:num_blocks MOI.add_constraint(model, funcs[block], block_sets[block]) end + for var_idx in intvar_idx + MOI.add_constraint(model, MOI.SingleVariable(scalar_vars[var_idx]), MOI.Integer()) + end return end diff --git a/test/FileFormats/SDPA/SDPA.jl b/test/FileFormats/SDPA/SDPA.jl index 59266b8e9e..6dc567bb86 100644 --- a/test/FileFormats/SDPA/SDPA.jl +++ b/test/FileFormats/SDPA/SDPA.jl @@ -18,6 +18,7 @@ function set_var_and_con_names(model::MOI.ModelLike) idx = 0 constraint_names = String[] for i in Iterators.flatten(( + MOI.get(model, MOI.ListOfConstraintIndices{MOI.SingleVariable, MOI.Integer}()), MOI.get(model, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives}()), MOI.get(model, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle}()))) idx += 1 @@ -70,7 +71,6 @@ end MOI.Interval(1.0, 2.0), MOI.Semiinteger(1.0, 2.0), MOI.Semicontinuous(1.0, 2.0), - MOI.Integer(), MOI.ZeroOne() ] model_string = """ @@ -201,6 +201,16 @@ example_models = [ minobjective: 1x c1: [0, 1x + -1, 0] in PositiveSemidefiniteConeTriangle(2) """), + ("example_integer.sdpa", """ + variables: x, y, z + minobjective: 1x + -2y + -1z + c1: [1x, 1y, 1z] in PositiveSemidefiniteConeTriangle(2) + c2: [1z, 1x, 2.1] in PositiveSemidefiniteConeTriangle(2) + c3: [1x + 1y + 1z + -1, -1x + -1y + -1z + 8] in Nonnegatives(2) + c4: x in Integer() + c5: y in Integer() + c6: z in Integer() + """), ] @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) diff --git a/test/FileFormats/SDPA/models/example_integer.sdpa b/test/FileFormats/SDPA/models/example_integer.sdpa new file mode 100644 index 0000000000..8e4e56d160 --- /dev/null +++ b/test/FileFormats/SDPA/models/example_integer.sdpa @@ -0,0 +1,25 @@ +* Example from http://www.opt.tu-darmstadt.de/scipsdp/downloads/data_format.txt +3 +3 +2 2 -2 +* the next line gives the objective values in the order of the variables +1 -2 -1 +* the remaining lines give the nonzeroes of the constraints with variable (0 meaning the constant part) block row column value +1 1 1 1 1 +2 1 1 2 1 +3 1 2 2 1 +1 2 1 2 1 +3 2 1 1 1 +0 2 2 2 -2.1 +1 3 1 1 1 +2 3 1 1 1 +3 3 1 1 1 +0 3 1 1 1 +1 3 2 2 -1 +2 3 2 2 -1 +3 3 2 2 -1 +0 3 2 2 -8 +*INTEGER +*1 +*2 +*3 \ No newline at end of file From 56bdb0f8931925a3e186e69a4b1c01d4f9142b57 Mon Sep 17 00:00:00 2001 From: Kibaek Kim Date: Tue, 28 Apr 2020 09:24:58 -0500 Subject: [PATCH 2/2] addressed the suggestions from @blegat --- src/FileFormats/SDPA/SDPA.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/FileFormats/SDPA/SDPA.jl b/src/FileFormats/SDPA/SDPA.jl index 5b862631c3..1521c24d18 100644 --- a/src/FileFormats/SDPA/SDPA.jl +++ b/src/FileFormats/SDPA/SDPA.jl @@ -8,7 +8,7 @@ import MathOptInterface const MOI = MathOptInterface MOI.Utilities.@model(Model, - (MOI.Integer,), + (), (), (MOI.Nonnegatives, MOI.PositiveSemidefiniteConeTriangle), (), @@ -319,7 +319,7 @@ function Base.read!(io::IO, model::Model{T}) where T obj = zero(MOI.ScalarAffineFunction{T}) for i in eachindex(c) if !iszero(c[i]) - push!(obj.terms, MOI.ScalarAffineTerm(c[i], MOI.VariableIndex(i))) + push!(obj.terms, MOI.ScalarAffineTerm(c[i], scalar_vars[i])) end end MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj) @@ -351,7 +351,7 @@ function Base.read!(io::IO, model::Model{T}) where T else if !iszero(coef) push!(funcs[block].terms, MOI.VectorAffineTerm(k, - MOI.ScalarAffineTerm(coef, MOI.VariableIndex(matrix)))) + MOI.ScalarAffineTerm(coef, scalar_vars[matrix]))) end end end