Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FileFormats.CBF] write out EXP and EXP* as variable cones #2482

Merged
merged 2 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions src/FileFormats/CBF/read.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
51 changes: 23 additions & 28 deletions src/FileFormats/CBF/write.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ 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}
odow marked this conversation as resolved.
Show resolved Hide resolved

function _CBFDataStructure()
return new(
Expand All @@ -35,6 +38,7 @@ mutable struct _CBFDataStructure
Tuple{Int,Int,Int,Float64}[],
Set{MOI.VariableIndex}(),
Tuple{Vector{MOI.VariableIndex},String}[],
Int[],
)
end
end
Expand Down Expand Up @@ -161,26 +165,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,
Expand Down Expand Up @@ -310,6 +294,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)
Expand All @@ -325,14 +310,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}(),
Expand All @@ -344,7 +337,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
Expand All @@ -362,15 +355,16 @@ 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}}(),
)
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
Expand Down Expand Up @@ -402,7 +396,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
Expand Down Expand Up @@ -430,7 +425,7 @@ function _write_HCOORD(io::IO, data::_CBFDataStructure)
io,
psd_idx - 1,
" ",
var - 1,
data.scalar_variables[var] - 1,
" ",
i - 1,
" ",
Expand Down Expand Up @@ -489,15 +484,15 @@ 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)

###
### 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)
Expand Down
132 changes: 128 additions & 4 deletions test/FileFormats/CBF/CBF.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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()
""",
),
(
Expand Down Expand Up @@ -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_")
Expand Down
12 changes: 2 additions & 10 deletions test/FileFormats/CBF/models/example_C.cbf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Binary file removed test/FileFormats/CBF/models/example_C.cbf.gz
Binary file not shown.
Loading