From 3d60e7128bd1441dd13c3099a9ac96a48d94e9dc Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 15 Apr 2024 15:14:13 +1200 Subject: [PATCH 1/2] [FileFormats.CBF] write out EXP and EXP* as variable cones --- src/FileFormats/CBF/read.jl | 8 +- src/FileFormats/CBF/write.jl | 49 +++---- test/FileFormats/CBF/CBF.jl | 132 ++++++++++++++++++- test/FileFormats/CBF/models/example_C.cbf | 12 +- test/FileFormats/CBF/models/example_C.cbf.gz | Bin 146 -> 0 bytes 5 files changed, 154 insertions(+), 47 deletions(-) delete mode 100644 test/FileFormats/CBF/models/example_C.cbf.gz diff --git a/src/FileFormats/CBF/read.jl b/src/FileFormats/CBF/read.jl index e940d9c3a5..380c8fd9b0 100644 --- a/src/FileFormats/CBF/read.jl +++ b/src/FileFormats/CBF/read.jl @@ -149,11 +149,9 @@ function _read_VAR(io::IO, model::Model, data::_CBFReadData) # Free cones (no constraint). append!(data.scalar_vars, MOI.add_variables(model, cone_dim)) elseif cone_str == "EXP" || cone_str == "EXP*" - # The convention in CBF is the reverse of MOI, so we cannot use - # add_constrained_variables. - x = MOI.add_variables(model, 3) - append!(data.scalar_vars, x) - MOI.add_constraint(model, MOI.VectorOfVariables(reverse(x)), set) + # The convention in CBF is the reverse of MOI + x, _ = MOI.add_constrained_variables(model, set) + append!(data.scalar_vars, reverse(x)) else x, _ = MOI.add_constrained_variables(model, set) append!(data.scalar_vars, x) diff --git a/src/FileFormats/CBF/write.jl b/src/FileFormats/CBF/write.jl index c16b7c71f7..9f707739b3 100644 --- a/src/FileFormats/CBF/write.jl +++ b/src/FileFormats/CBF/write.jl @@ -21,6 +21,7 @@ mutable struct _CBFDataStructure dcoord::Vector{Tuple{Int,Int,Int,Float64}} variables_with_domain::Set{MOI.VariableIndex} variable_cones::Vector{Tuple{Vector{MOI.VariableIndex},String}} + scalar_variables::Vector{Int} function _CBFDataStructure() return new( @@ -35,6 +36,7 @@ mutable struct _CBFDataStructure Tuple{Int,Int,Int,Float64}[], Set{MOI.VariableIndex}(), Tuple{Vector{MOI.VariableIndex},String}[], + Int[], ) end end @@ -161,26 +163,6 @@ function _add_cones( return end -function _add_cones( - data::_CBFDataStructure, - model::Model, - ::Type{F}, - ::Type{S}, -) where { - F<:MOI.VectorOfVariables, - S<:Union{MOI.ExponentialCone,MOI.DualExponentialCone}, -} - # The Exponential cone in MOI and CBF are reversed. Instead of dealing with - # this complexity, just write them out as `Ax + b in K` constraints. - # TODO(odow): we should support this at some point. See #2478 - for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) - f = MOI.get(model, MOI.ConstraintFunction(), ci) - _add_function(data, f, S) - push!(data.cones, (_cone_string(data, S), 3)) - end - return -end - function _add_cones( data::_CBFDataStructure, model::Model, @@ -310,6 +292,7 @@ end function _write_VAR(io::IO, model::Model, data) num_var = MOI.get(model, MOI.NumberOfVariables()) + append!(data.scalar_variables, collect(1:num_var)) cones = Tuple{String,Int}[] current_variable = 0 for (f, str) in sort!(data.variable_cones; by = x -> first(x[1]).value) @@ -325,14 +308,22 @@ function _write_VAR(io::IO, model::Model, data) end println(io, "VAR") println(io, num_var, " ", length(cones)) + offset = 1 for (K, n) in cones println(io, K, " ", n) + if K == "EXP" || K == "EXP*" + # The ordering for EXP and EXP* is reversed between MOI and CBF + tmp = data.scalar_variables[offset] + data.scalar_variables[offset] = data.scalar_variables[offset+2] + data.scalar_variables[offset+2] = tmp + end + offset += n end println(io) return end -function _write_INT(io::IO, model::Model) +function _write_INT(io::IO, model::Model, data) cons = MOI.get( model, MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.Integer}(), @@ -344,7 +335,7 @@ function _write_INT(io::IO, model::Model) println(io, length(cons)) for ci in cons f = MOI.get(model, MOI.ConstraintFunction(), ci) - println(io, f.value - 1) + println(io, data.scalar_variables[f.value] - 1) end println(io) return @@ -362,7 +353,7 @@ function _write_PSDCON(io::IO, data::_CBFDataStructure) return end -function _write_OBJACOORD(io::IO, model::Model) +function _write_OBJACOORD(io::IO, model::Model, data) f = MOI.get( model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), @@ -370,7 +361,8 @@ function _write_OBJACOORD(io::IO, model::Model) if !isempty(f.terms) println(io, "OBJACOORD\n", length(f.terms)) for t in f.terms - println(io, t.variable.value - 1, " ", t.coefficient) + column = data.scalar_variables[t.variable.value] - 1 + println(io, column, " ", t.coefficient) end println(io) end @@ -402,7 +394,8 @@ function _write_ACOORD(io::IO, data::_CBFDataStructure) end println(io, "ACOORD\n", length(data.acoord)) for (row, var, coef) in data.acoord - println(io, row - 1, " ", var - 1, " ", coef) + column = data.scalar_variables[var] - 1 + println(io, row - 1, " ", column, " ", coef) end println(io) return @@ -430,7 +423,7 @@ function _write_HCOORD(io::IO, data::_CBFDataStructure) io, psd_idx - 1, " ", - var - 1, + data.scalar_variables[var] - 1, " ", i - 1, " ", @@ -489,7 +482,7 @@ function Base.write(io::IO, model::Model) _write_OBJSENSE(io, model) # _write_PSDVAR _write_VAR(io, model, data) - _write_INT(io, model) + _write_INT(io, model, data) _write_PSDCON(io, data) _write_CON(io, data) @@ -497,7 +490,7 @@ function Base.write(io::IO, model::Model) ### Problem data ### # _write_OBJFCOORD - constant = _write_OBJACOORD(io, model) + constant = _write_OBJACOORD(io, model, data) _write_OBJBCOORD(io, constant) # _write_FCOORD _write_ACOORD(io, data) diff --git a/test/FileFormats/CBF/CBF.jl b/test/FileFormats/CBF/CBF.jl index f769273391..31861884c0 100644 --- a/test/FileFormats/CBF/CBF.jl +++ b/test/FileFormats/CBF/CBF.jl @@ -412,15 +412,13 @@ const _EXAMPLE_MODELS = [ ( "example_C.cbf", """ - variables: a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p - maxobjective: a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + -1 + variables: a, b, c, d, e, f, g, h, i, j + maxobjective: a + b + c + d + e + f + g + h + i + j + -1 c1: [b] in Zeros(1) c2: [c] in Nonnegatives(1) c3: [d] in Nonpositives(1) c4: [e, f, g] in SecondOrderCone(3) c5: [h, i, j] in RotatedSecondOrderCone(3) - c6: [m, l, k] in ExponentialCone() - c7: [p, o, n] in DualExponentialCone() """, ), ( @@ -500,6 +498,132 @@ function test_write_variable_cones() return end +function test_roundtrip_ExponentialCone() + model = CBF.Model() + x, _ = MOI.add_constrained_variables(model, MOI.ExponentialCone()) + f = 1.0 * x[1] + 2.0 * x[2] + 3.0 * x[3] + MOI.add_constraint(model, x[1], MOI.Integer()) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x[1], x[2], x[1]]), + MOI.PositiveSemidefiniteConeTriangle(2), + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + g = MOI.Utilities.operate(vcat, Float64, f) + MOI.add_constraint(model, g, MOI.Zeros(1)) + io = IOBuffer() + write(io, model) + seekstart(io) + @test read(io, String) == """ + VER + 3 + + OBJSENSE + MIN + + VAR + 3 1 + EXP 3 + + INT + 1 + 2 + + PSDCON + 1 + 2 + + CON + 1 1 + L= 1 + + OBJACOORD + 3 + 2 1.0 + 1 2.0 + 0 3.0 + + ACOORD + 3 + 0 2 1.0 + 0 1 2.0 + 0 0 3.0 + + BCOORD + 1 + 0 0.0 + + HCOORD + 3 + 0 2 0 0 1.0 + 0 1 1 0 1.0 + 0 2 1 1 1.0 + + """ + seekstart(io) + model2 = CBF.Model() + read!(io, model2) + y = MOI.get(model2, MOI.ListOfVariableIndices()) + obj_y = MOI.get(model2, MOI.ObjectiveFunction{typeof(f)}()) + @test ≈(obj_y, 1.0 * y[1] + 2.0 * y[2] + 3.0 * y[3]) + ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.Integer}.(1:3) + @test MOI.is_valid.(model2, ci) == [true, false, false] + return +end + +function test_roundtrip_DualExponentialCone() + model = CBF.Model() + x, _ = MOI.add_constrained_variables(model, MOI.DualExponentialCone()) + f = 1.0 * x[1] + 2.0 * x[2] + 3.0 * x[3] + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + g = MOI.Utilities.operate(vcat, Float64, f) + MOI.add_constraint(model, g, MOI.Zeros(1)) + io = IOBuffer() + write(io, model) + seekstart(io) + @test read(io, String) == """ + VER + 3 + + OBJSENSE + MIN + + VAR + 3 1 + EXP* 3 + + CON + 1 1 + L= 1 + + OBJACOORD + 3 + 2 1.0 + 1 2.0 + 0 3.0 + + ACOORD + 3 + 0 2 1.0 + 0 1 2.0 + 0 0 3.0 + + BCOORD + 1 + 0 0.0 + + """ + seekstart(io) + model2 = CBF.Model() + read!(io, model2) + y = MOI.get(model2, MOI.ListOfVariableIndices()) + obj_y = MOI.get(model2, MOI.ObjectiveFunction{typeof(f)}()) + @test ≈(obj_y, 1.0 * y[1] + 2.0 * y[2] + 3.0 * y[3]) + return +end + function runtests() for name in names(@__MODULE__, all = true) if startswith("$(name)", "test_") diff --git a/test/FileFormats/CBF/models/example_C.cbf b/test/FileFormats/CBF/models/example_C.cbf index 1852f77be3..2614fe08c3 100644 --- a/test/FileFormats/CBF/models/example_C.cbf +++ b/test/FileFormats/CBF/models/example_C.cbf @@ -5,18 +5,16 @@ OBJSENSE MAX VAR -16 8 +10 6 F 1 L= 1 L+ 1 L- 1 Q 3 QR 3 -EXP 3 -EXP* 3 OBJACOORD -16 +10 0 1.0 1 1.0 2 1.0 @@ -27,12 +25,6 @@ OBJACOORD 7 1.0 8 1.0 9 1.0 -10 1.0 -11 1.0 -12 1.0 -13 1.0 -14 1.0 -15 1.0 OBJBCOORD -1.0 diff --git a/test/FileFormats/CBF/models/example_C.cbf.gz b/test/FileFormats/CBF/models/example_C.cbf.gz deleted file mode 100644 index 32239bc4e727b97c78a9a988f5cc3215b3839d17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146 zcmV;D0B!#tiwFpK=RsTm17&z&ZE$R5UqdcqVrBp>%`pyxFc3u1eonD1PTfR zrihX_7zuIzlVI-h~sMu-!0(d4`BBR+DeB-6z`#MEmWCR9M+Tx$B$lw z1AdAz%{fFZdaI%4-E?MSKFrp9nw{CR`p2x5xw2OdC>P4lkw2?)pLwmsRm%VX02s?Y A8vp Date: Mon, 15 Apr 2024 20:10:28 +1200 Subject: [PATCH 2/2] Update write.jl --- src/FileFormats/CBF/write.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/FileFormats/CBF/write.jl b/src/FileFormats/CBF/write.jl index 9f707739b3..2584345a59 100644 --- a/src/FileFormats/CBF/write.jl +++ b/src/FileFormats/CBF/write.jl @@ -21,6 +21,8 @@ mutable struct _CBFDataStructure dcoord::Vector{Tuple{Int,Int,Int,Float64}} variables_with_domain::Set{MOI.VariableIndex} variable_cones::Vector{Tuple{Vector{MOI.VariableIndex},String}} + # This is the identity mapping, except for EXP and EXP* cones, + # in which (u, v, w) in MOI are swapped to (w, v, u) in CBF. scalar_variables::Vector{Int} function _CBFDataStructure()