Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/FileFormats/MPS/MPS.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ end
function MOI.supports_constraint(
::Model,
::Type{MOI.VariableIndex},
::Type{<:Union{MOI.Parameter,MOI.Semicontinuous,MOI.Semiinteger}},
::Type{<:MOI.Parameter},
)
return false
end
Expand Down Expand Up @@ -170,7 +170,14 @@ end

Base.summary(io::IO, ::Model) = print(io, "MOI.FileFormats.MPS.Model")

@enum(VType, VTYPE_CONTINUOUS, VTYPE_INTEGER, VTYPE_BINARY)
@enum(
VType,
VTYPE_CONTINUOUS,
VTYPE_INTEGER,
VTYPE_BINARY,
VTYPE_SEMICONTINUOUS,
VTYPE_SEMIINTEGER,
)

include("read.jl")
include("write.jl")
Expand Down
25 changes: 24 additions & 1 deletion src/FileFormats/MPS/read.jl
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,15 @@ function _add_variable(model::Model{T}, data, variable_map, i, name) where {T}
x = MOI.add_variable(model)
variable_map[name] = x
MOI.set(model, MOI.VariableName(), x, name)
if data.vtype[i] == VTYPE_SEMIINTEGER
set = MOI.Semiinteger(data.col_lower[i], data.col_upper[i])
MOI.add_constraint(model, x, set)
return
elseif data.vtype[i] == VTYPE_SEMICONTINUOUS
set = MOI.Semicontinuous(data.col_lower[i], data.col_upper[i])
MOI.add_constraint(model, x, set)
return
end
set = bounds_to_set(data.col_lower[i], data.col_upper[i])
if set isa MOI.Interval
# Do not add MOI.Interval constraints because we want to follow JuMP's
Expand Down Expand Up @@ -926,7 +935,11 @@ function _parse_single_bound(
data.col_lower[col] = value
elseif bound_type == "LI"
data.col_lower[col] = value
data.vtype[col] = VTYPE_INTEGER
if data.vtype[col] in (VTYPE_SEMICONTINUOUS, VTYPE_SEMIINTEGER)
data.vtype[col] = VTYPE_SEMIINTEGER
else
data.vtype[col] = VTYPE_INTEGER
end
elseif bound_type == "UI"
data.col_upper[col] = value
data.vtype[col] = VTYPE_INTEGER
Expand All @@ -943,6 +956,16 @@ function _parse_single_bound(
data.col_lower[col] = typemin(T)
data.col_upper[col] = typemax(T)
data.vtype[col] = VTYPE_BINARY
elseif bound_type == "SC"
data.col_upper[col] = value
if data.vtype[col] == VTYPE_INTEGER
data.vtype[col] = VTYPE_SEMIINTEGER
else
data.vtype[col] = VTYPE_SEMICONTINUOUS
end
elseif bound_type == "SI"
data.col_upper[col] = value
data.vtype[col] = VTYPE_SEMIINTEGER
else
_throw_parse_error(
data,
Expand Down
41 changes: 35 additions & 6 deletions src/FileFormats/MPS/write.jl
Original file line number Diff line number Diff line change
Expand Up @@ -546,11 +546,8 @@ end
# UP upper bound x <= b
# UI integer variable x <= b
# BV binary variable x = 0 or 1
#
# Not yet implemented:
#
# SC semi-cont variable x = 0 or l <= x <= b
# l is the lower bound on the variable. If none set then defaults to 1
# SC semicontinuous variable x in {0} ∪ [LO, b]
# SI semiinteger variable x in {0} ∪ [LI, b]
# ==============================================================================

function write_single_bound(
Expand All @@ -560,7 +557,29 @@ function write_single_bound(
upper::T,
vtype,
) where {T}
if lower == upper
if vtype == VTYPE_SEMICONTINUOUS
write_single_bound(io, var_name, lower, typemax(T), VTYPE_CONTINUOUS)
println(
io,
Card(
f1 = "SC",
f2 = "bounds",
f3 = var_name,
f4 = _to_string(upper),
),
)
elseif vtype == VTYPE_SEMIINTEGER
write_single_bound(io, var_name, lower, typemax(T), VTYPE_INTEGER)
println(
io,
Card(
f1 = "SI",
f2 = "bounds",
f3 = var_name,
f4 = _to_string(upper),
),
)
elseif lower == upper
println(
io,
Card(
Expand Down Expand Up @@ -613,6 +632,14 @@ update_bounds(x, set::MOI.EqualTo) = (set.value, set.value, x[3])

update_bounds(x, set::MOI.ZeroOne) = (x[1], x[2], VTYPE_BINARY)

function update_bounds(x, set::MOI.Semicontinuous)
return (set.lower, set.upper, VTYPE_SEMICONTINUOUS)
end

function update_bounds(x, set::MOI.Semiinteger)
return (set.lower, set.upper, VTYPE_SEMIINTEGER)
end

function _collect_bounds(bounds, model, ::Type{S}, var_to_column) where {S}
for index in
MOI.get(model, MOI.ListOfConstraintIndices{MOI.VariableIndex,S}())
Expand All @@ -636,6 +663,8 @@ function write_bounds(io::IO, model::Model{T}, var_to_column) where {T}
_collect_bounds(bounds, model, MOI.EqualTo{T}, var_to_column)
_collect_bounds(bounds, model, MOI.Interval{T}, var_to_column)
_collect_bounds(bounds, model, MOI.ZeroOne, var_to_column)
_collect_bounds(bounds, model, MOI.Semiinteger{T}, var_to_column)
_collect_bounds(bounds, model, MOI.Semicontinuous{T}, var_to_column)
for (variable, column) in var_to_column
var_name = _var_name(model, variable, column, options.generic_names)
lower, upper, vtype = bounds[column]
Expand Down
102 changes: 94 additions & 8 deletions test/FileFormats/MPS/test_MPS.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1539,14 +1539,6 @@ function test_unsupported_variable_types()
MOI.UnsupportedConstraint,
MOI.add_constrained_variable(model, MOI.Parameter(2.0)),
)
@test_throws(
MOI.UnsupportedConstraint,
MOI.add_constrained_variable(model, MOI.Semicontinuous(2.0, 3.0)),
)
@test_throws(
MOI.UnsupportedConstraint,
MOI.add_constrained_variable(model, MOI.Semiinteger(2.0, 3.0)),
)
return
end

Expand Down Expand Up @@ -1840,6 +1832,100 @@ function test_unsupported_kwarg()
return
end

function test_round_trip_semicontinuous()
_test_model_equality(
"""
variables: x
minobjective: 1.0 * x
x in Semicontinuous(2.0, 3.0)
""",
["x"],
String[],
)
return
end

function test_round_trip_semiinteger()
_test_model_equality(
"""
variables: x
minobjective: 1.0 * x
x in Semiinteger(2.0, 3.0)
""",
["x"],
String[],
)
return
end

function test_semi_reading_1()
LB, UB = [-1.0, 0.0, 1.0], [-0.5, 0.0, 0.5, 1.5]
for lb in LB, ub in UB, SC in ["SC", "SI"], LO in ["LO", "LI"]
if lb > ub
continue
end
l_row = iszero(lb) ? "" : "$LO bounds x $lb\n"
src = """
NAME
ROWS
N obj
COLUMNS
x obj 1.0
RHS
BOUNDS
$(l_row)$(SC) bounds x $ub
ENDATA
"""
target = if SC == "SC" && (iszero(lb) || LO == "LO")
MOI.Semicontinuous(lb, ub)
else
MOI.Semiinteger(lb, ub)
end
model = MPS.Model()
read!(IOBuffer(src), model)
dest = MOI.Utilities.Model{Float64}()
MOI.copy_to(dest, model)
x = MOI.get(dest, MOI.VariableIndex, "x")
ci = MOI.ConstraintIndex{MOI.VariableIndex,typeof(target)}(x.value)
@test MOI.get(dest, MOI.ConstraintSet(), ci) == target
end
return
end

function test_semi_reading_2()
LB, UB = [-1.0, 0.0, 1.0], [-0.5, 0.0, 0.5, 1.5]
for lb in LB, ub in UB, SC in ["SC", "SI"], LO in ["LO", "LI"]
if lb > ub
continue
end
l_row = iszero(lb) ? "" : "$LO bounds x $lb\n"
src = """
NAME
ROWS
N obj
COLUMNS
x obj 1.0
RHS
BOUNDS
$(SC) bounds x $ub
$(l_row)ENDATA
"""
target = if SC == "SC" && (iszero(lb) || LO == "LO")
MOI.Semicontinuous(lb, ub)
else
MOI.Semiinteger(lb, ub)
end
model = MPS.Model()
read!(IOBuffer(src), model)
dest = MOI.Utilities.Model{Float64}()
MOI.copy_to(dest, model)
x = MOI.get(dest, MOI.VariableIndex, "x")
ci = MOI.ConstraintIndex{MOI.VariableIndex,typeof(target)}(x.value)
@test MOI.get(dest, MOI.ConstraintSet(), ci) == target
end
return
end

end # TestMPS

TestMPS.runtests()
Loading