Skip to content

Commit a754175

Browse files
authored
[FileFormats.MPS] fix writing duplicate bounds for binary variables (#2431)
1 parent 8d2bfce commit a754175

File tree

2 files changed

+177
-28
lines changed

2 files changed

+177
-28
lines changed

src/FileFormats/MPS/MPS.jl

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,11 @@ end
261261

262262
function write_model_name(io::IO, model::Model)
263263
model_name = MOI.get(model, MOI.Name())
264-
println(io, rpad("NAME", 14), model_name)
264+
if isempty(model_name)
265+
println(io, "NAME")
266+
else
267+
println(io, rpad("NAME", 14), model_name)
268+
end
265269
return
266270
end
267271

@@ -542,6 +546,9 @@ function write_columns(io::IO, model::Model, flip_obj, var_to_column)
542546
)
543547
end
544548
end
549+
if int_open
550+
println(io, Card(f2 = "MARKER", f3 = "'MARKER'", f5 = "'INTEND'"))
551+
end
545552
return constant, indicators
546553
end
547554

@@ -773,11 +780,16 @@ function write_bounds(io::IO, model::Model, var_to_column)
773780
var_name = _var_name(model, variable, column, options.generic_names)
774781
lower, upper, vtype = bounds[column]
775782
if vtype == VTYPE_BINARY
776-
println(io, Card(f1 = "BV", f2 = "bounds", f3 = var_name))
777-
# Only add bounds if they are tighter than the implicit bounds of a
778-
# binary variable.
779-
if lower > 0 || upper < 1
780-
write_single_bound(io, var_name, lower, upper)
783+
if lower <= 0 && upper >= 1
784+
println(io, Card(f1 = "BV", f2 = "bounds", f3 = var_name))
785+
else
786+
if lower > 0
787+
lower = 1
788+
end
789+
if upper < 1
790+
upper = 0
791+
end
792+
write_single_bound(io, var_name, max(0, lower), min(1, upper))
781793
end
782794
else
783795
write_single_bound(io, var_name, lower, upper)

test/FileFormats/MPS/MPS.jl

Lines changed: 159 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,35 @@ using Test
1111
import MathOptInterface as MOI
1212
import MathOptInterface.Utilities as MOIU
1313
import DataStructures: OrderedDict
14+
1415
const MPS = MOI.FileFormats.MPS
16+
1517
const MPS_TEST_FILE = "test.mps"
1618

19+
function runtests()
20+
for name in names(@__MODULE__, all = true)
21+
if startswith("$(name)", "test_")
22+
@testset "$name" begin
23+
getfield(@__MODULE__, name)()
24+
end
25+
end
26+
end
27+
sleep(1.0) # Allow time for unlink to happen.
28+
rm(MPS_TEST_FILE, force = true)
29+
return
30+
end
31+
32+
function _test_write_to_file(input::String, output::String)
33+
model = MPS.Model()
34+
MOI.Utilities.loadfromstring!(model, input)
35+
data = sprint(write, model)
36+
if data != output
37+
print(data)
38+
end
39+
@test data == output
40+
return
41+
end
42+
1743
function _test_model_equality(
1844
model_string,
1945
variables,
@@ -423,7 +449,7 @@ a_really_long_name <= 2.0
423449
)
424450
MOI.write_to_file(model, MPS_TEST_FILE)
425451
@test read(MPS_TEST_FILE, String) ==
426-
"NAME \n" *
452+
"NAME\n" *
427453
"ROWS\n" *
428454
" N OBJ\n" *
429455
"COLUMNS\n" *
@@ -468,7 +494,7 @@ function test_names_with_spaces()
468494
)
469495
MOI.set(model, MOI.ConstraintName(), c, "c c")
470496
@test sprint(write, model) ==
471-
"NAME \n" *
497+
"NAME\n" *
472498
"ROWS\n" *
473499
" N OBJ\n" *
474500
" E c_c\n" *
@@ -489,7 +515,7 @@ function test_objsense_default()
489515
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
490516
MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), x)
491517
@test sprint(write, model) ==
492-
"NAME \n" *
518+
"NAME\n" *
493519
"ROWS\n" *
494520
" N OBJ\n" *
495521
"COLUMNS\n" *
@@ -508,7 +534,7 @@ function test_objsense_true()
508534
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
509535
MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), x)
510536
@test sprint(write, model) ==
511-
"NAME \n" *
537+
"NAME\n" *
512538
"OBJSENSE\n" *
513539
" MAX\n" *
514540
"ROWS\n" *
@@ -537,7 +563,7 @@ function test_sos_constraints()
537563
MOI.SOS2([1.2, 2.3, 3.4]),
538564
)
539565
@test sprint(write, model) ==
540-
"NAME \n" *
566+
"NAME\n" *
541567
"ROWS\n" *
542568
" N OBJ\n" *
543569
"COLUMNS\n" *
@@ -582,7 +608,7 @@ function test_generic_names()
582608
)
583609
MOI.add_constraint(model, y, MOI.GreaterThan(2.0))
584610
@test sprint(write, model) ==
585-
"NAME \n" *
611+
"NAME\n" *
586612
"ROWS\n" *
587613
" N OBJ\n" *
588614
" E R1\n" *
@@ -610,7 +636,7 @@ function test_rew_filename()
610636
)
611637
MOI.add_constraint(model, y, MOI.GreaterThan(2.0))
612638
@test sprint(write, model) ==
613-
"NAME \n" *
639+
"NAME\n" *
614640
"ROWS\n" *
615641
" N OBJ\n" *
616642
" E R1\n" *
@@ -638,7 +664,7 @@ function test_rew_format()
638664
)
639665
MOI.add_constraint(model, y, MOI.GreaterThan(2.0))
640666
@test sprint(write, model) ==
641-
"NAME \n" *
667+
"NAME\n" *
642668
"ROWS\n" *
643669
" N OBJ\n" *
644670
" E R1\n" *
@@ -663,7 +689,7 @@ function test_infinite_interval()
663689
MOI.add_constraint(model, 1.0 * x, MOI.Interval(2.0, Inf))
664690
MOI.add_constraint(model, 1.0 * x, MOI.Interval(3.0, 4.0))
665691
@test sprint(write, model) ==
666-
"NAME \n" *
692+
"NAME\n" *
667693
"ROWS\n" *
668694
" N OBJ\n" *
669695
" N c1\n" *
@@ -698,7 +724,7 @@ minobjective: x + y + 5.0 * x * x + 1.0 * x * y + 1.0 * y * x + 1.2 * y * y
698724
)
699725
MOI.write_to_file(model, MPS_TEST_FILE)
700726
@test read(MPS_TEST_FILE, String) ==
701-
"NAME \n" *
727+
"NAME\n" *
702728
"ROWS\n" *
703729
" N OBJ\n" *
704730
"COLUMNS\n" *
@@ -728,7 +754,7 @@ minobjective: x + y + 5.0 * x * x + 1.0 * x * y + 1.0 * y * x + 1.2 * y * y
728754
)
729755
MOI.write_to_file(model, MPS_TEST_FILE)
730756
@test read(MPS_TEST_FILE, String) ==
731-
"NAME \n" *
757+
"NAME\n" *
732758
"ROWS\n" *
733759
" N OBJ\n" *
734760
"COLUMNS\n" *
@@ -759,7 +785,7 @@ c1: x + y + 5.0 * x * x + 1.0 * x * y + 1.0 * y * x + 1.2 * y * y <= 1.0
759785
)
760786
MOI.write_to_file(model, MPS_TEST_FILE)
761787
@test read(MPS_TEST_FILE, String) ==
762-
"NAME \n" *
788+
"NAME\n" *
763789
"ROWS\n" *
764790
" N OBJ\n" *
765791
" L c1\n" *
@@ -792,7 +818,7 @@ c1: x + y + 5.0 * x * x + 1.0 * x * y + 1.0 * y * x + 1.2 * y * y <= 1.0
792818
)
793819
MOI.write_to_file(model, MPS_TEST_FILE)
794820
@test read(MPS_TEST_FILE, String) ==
795-
"NAME \n" *
821+
"NAME\n" *
796822
"ROWS\n" *
797823
" N OBJ\n" *
798824
" L c1\n" *
@@ -1067,19 +1093,130 @@ function test_parse_name_line()
10671093
return
10681094
end
10691095

1070-
function runtests()
1071-
for name in names(@__MODULE__, all = true)
1072-
if startswith("$(name)", "test_")
1073-
@testset "$name" begin
1074-
getfield(@__MODULE__, name)()
1075-
end
1076-
end
1096+
function test_binary_with_unneeded_bounds()
1097+
target = """
1098+
NAME
1099+
ROWS
1100+
N OBJ
1101+
COLUMNS
1102+
MARKER 'MARKER' 'INTORG'
1103+
x OBJ 1
1104+
MARKER 'MARKER' 'INTEND'
1105+
RHS
1106+
RANGES
1107+
BOUNDS
1108+
BV bounds x
1109+
ENDATA
1110+
"""
1111+
for test in [
1112+
"""
1113+
variables: x
1114+
minobjective: 1.0 * x
1115+
x in ZeroOne()
1116+
""",
1117+
"""
1118+
variables: x
1119+
minobjective: 1.0 * x
1120+
x in ZeroOne()
1121+
x >= 0.0
1122+
x <= 1.0
1123+
""",
1124+
"""
1125+
variables: x
1126+
minobjective: 1.0 * x
1127+
x in ZeroOne()
1128+
x in Interval(-0.1, 1.2)
1129+
""",
1130+
]
1131+
_test_write_to_file(test, target)
1132+
end
1133+
return
1134+
end
1135+
1136+
function test_binary_with_restrictive_bounds()
1137+
target = """
1138+
NAME
1139+
ROWS
1140+
N OBJ
1141+
COLUMNS
1142+
MARKER 'MARKER' 'INTORG'
1143+
x OBJ 1
1144+
MARKER 'MARKER' 'INTEND'
1145+
RHS
1146+
RANGES
1147+
BOUNDS
1148+
FX bounds x 1
1149+
ENDATA
1150+
"""
1151+
for test in [
1152+
"""
1153+
variables: x
1154+
minobjective: 1.0 * x
1155+
x in ZeroOne()
1156+
x >= 0.5
1157+
""",
1158+
"""
1159+
variables: x
1160+
minobjective: 1.0 * x
1161+
x in ZeroOne()
1162+
x >= 0.1
1163+
x <= 1.0
1164+
""",
1165+
"""
1166+
variables: x
1167+
minobjective: 1.0 * x
1168+
x in ZeroOne()
1169+
x in Interval(0.2, 1.2)
1170+
""",
1171+
]
1172+
_test_write_to_file(test, target)
10771173
end
1078-
sleep(1.0) # Allow time for unlink to happen.
1079-
rm(MPS_TEST_FILE, force = true)
10801174
return
10811175
end
10821176

1177+
function test_binary_with_infeasible_bounds()
1178+
target = """
1179+
NAME
1180+
ROWS
1181+
N OBJ
1182+
COLUMNS
1183+
MARKER 'MARKER' 'INTORG'
1184+
x OBJ 1
1185+
MARKER 'MARKER' 'INTEND'
1186+
RHS
1187+
RANGES
1188+
BOUNDS
1189+
LO bounds x 1
1190+
UP bounds x 0
1191+
ENDATA
1192+
"""
1193+
for test in [
1194+
"""
1195+
variables: x
1196+
minobjective: 1.0 * x
1197+
x in ZeroOne()
1198+
x >= 0.5
1199+
x <= 0.1
1200+
""",
1201+
"""
1202+
variables: x
1203+
minobjective: 1.0 * x
1204+
x in ZeroOne()
1205+
x >= 1.0
1206+
x <= 0.0
1207+
""",
1208+
"""
1209+
variables: x
1210+
minobjective: 1.0 * x
1211+
x in ZeroOne()
1212+
x in Interval(0.9, 0.1)
1213+
""",
1214+
]
1215+
_test_write_to_file(test, target)
1216+
end
1217+
return
10831218
end
10841219

1220+
end # TestMPS
1221+
10851222
TestMPS.runtests()

0 commit comments

Comments
 (0)