Skip to content

Commit 72d23e5

Browse files
mlubinodow
andauthored
[FileFormats.MPS]: avoid creating list of variable names (#2426)
* MPS: avoid creating list of variable names * format * fix for indicators * format * Refactor create_generic_names --------- Co-authored-by: odow <[email protected]>
1 parent cd5cd9b commit 72d23e5

File tree

3 files changed

+99
-77
lines changed

3 files changed

+99
-77
lines changed

src/FileFormats/MPS/MPS.jl

Lines changed: 69 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module MPS
99
import ..FileFormats
1010

1111
import MathOptInterface as MOI
12+
import DataStructures: OrderedDict
1213

1314
const _NUM_TO_STRING = [string(i) for i in -10:10]
1415

@@ -209,7 +210,8 @@ Write `model` to `io` in the MPS file format.
209210
function Base.write(io::IO, model::Model)
210211
options = get_options(model)
211212
if options.generic_names
212-
FileFormats.create_generic_names(model)
213+
# Generic variable names handled in this writer.
214+
FileFormats.create_generic_constraint_names(model)
213215
else
214216
FileFormats.create_unique_names(
215217
model;
@@ -218,11 +220,8 @@ function Base.write(io::IO, model::Model)
218220
)
219221
end
220222
variables = MOI.get(model, MOI.ListOfVariableIndices())
221-
ordered_names = Vector{String}(undef, length(variables))
222-
var_to_column = Dict{MOI.VariableIndex,Int}()
223+
var_to_column = OrderedDict{MOI.VariableIndex,Int}()
223224
for (i, x) in enumerate(variables)
224-
n = MOI.get(model, MOI.VariableName(), x)
225-
ordered_names[i] = n
226225
var_to_column[x] = i
227226
end
228227
write_model_name(io, model)
@@ -237,20 +236,19 @@ function Base.write(io::IO, model::Model)
237236
flip_obj = MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE
238237
end
239238
write_rows(io, model)
240-
obj_const, indicators =
241-
write_columns(io, model, flip_obj, ordered_names, var_to_column)
239+
obj_const, indicators = write_columns(io, model, flip_obj, var_to_column)
242240
write_rhs(io, model, obj_const)
243241
write_ranges(io, model)
244-
write_bounds(io, model, ordered_names, var_to_column)
245-
write_quadobj(io, model, ordered_names, var_to_column)
242+
write_bounds(io, model, var_to_column)
243+
write_quadobj(io, model, var_to_column)
246244
if options.quadratic_format != kQuadraticFormatCPLEX
247245
# Gurobi needs qcons _after_ quadobj and _before_ SOS.
248-
write_quadcons(io, model, ordered_names, var_to_column)
246+
write_quadcons(io, model, var_to_column)
249247
end
250-
write_sos(io, model, ordered_names, var_to_column)
248+
write_sos(io, model, var_to_column)
251249
if options.quadratic_format == kQuadraticFormatCPLEX
252250
# CPLEX needs qcons _after_ SOS.
253-
write_quadcons(io, model, ordered_names, var_to_column)
251+
write_quadcons(io, model, var_to_column)
254252
end
255253
write_indicators(io, indicators)
256254
println(io, "ENDATA")
@@ -388,7 +386,7 @@ function list_of_integer_variables(model::Model, var_to_column)
388386
end
389387

390388
function _extract_terms(
391-
var_to_column::Dict{MOI.VariableIndex,Int},
389+
var_to_column::OrderedDict{MOI.VariableIndex,Int},
392390
coefficients::Vector{Vector{Tuple{String,Float64}}},
393391
row_name::String,
394392
func::MOI.ScalarAffineFunction,
@@ -403,7 +401,7 @@ function _extract_terms(
403401
end
404402

405403
function _extract_terms(
406-
var_to_column::Dict{MOI.VariableIndex,Int},
404+
var_to_column::OrderedDict{MOI.VariableIndex,Int},
407405
coefficients::Vector{Vector{Tuple{String,Float64}}},
408406
row_name::String,
409407
func::MOI.ScalarQuadraticFunction,
@@ -421,7 +419,7 @@ function _collect_coefficients(
421419
model,
422420
::Type{F},
423421
::Type{S},
424-
var_to_column::Dict{MOI.VariableIndex,Int},
422+
var_to_column::OrderedDict{MOI.VariableIndex,Int},
425423
coefficients::Vector{Vector{Tuple{String,Float64}}},
426424
) where {F,S}
427425
for index in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
@@ -441,6 +439,7 @@ function _collect_indicator(
441439
coefficients,
442440
indicators,
443441
) where {S}
442+
options = get_options(model)
444443
F = MOI.VectorAffineFunction{Float64}
445444
for index in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
446445
row_name = MOI.get(model, MOI.ConstraintName(), index)
@@ -449,10 +448,8 @@ function _collect_indicator(
449448
z = convert(MOI.VariableIndex, funcs[1])
450449
_extract_terms(var_to_column, coefficients, row_name, funcs[2])
451450
condition = _activation_condition(S)
452-
push!(
453-
indicators,
454-
(row_name, MOI.get(model, MOI.VariableName(), z), condition),
455-
)
451+
var_name = _var_name(model, z, var_to_column[z], options.generic_names)
452+
push!(indicators, (row_name, var_name, condition))
456453
end
457454
return
458455
end
@@ -468,16 +465,24 @@ function _extract_terms_objective(model, var_to_column, coefficients, flip_obj)
468465
return obj_func.constant
469466
end
470467

471-
function write_columns(
472-
io::IO,
468+
function _var_name(
473469
model::Model,
474-
flip_obj,
475-
ordered_names,
476-
var_to_column,
477-
)
470+
variable::MOI.VariableIndex,
471+
column::Int,
472+
generic_name::Bool,
473+
)::String
474+
if generic_name
475+
return "C$column"
476+
else
477+
return MOI.get(model, MOI.VariableName(), variable)
478+
end
479+
end
480+
481+
function write_columns(io::IO, model::Model, flip_obj, var_to_column)
482+
options = get_options(model)
478483
indicators = Tuple{String,String,MOI.ActivationCondition}[]
479484
coefficients = Vector{Tuple{String,Float64}}[
480-
Tuple{String,Float64}[] for _ in ordered_names
485+
Tuple{String,Float64}[] for _ in 1:length(var_to_column)
481486
]
482487
# Build constraint coefficients
483488
# The functions and sets are given explicitly so that this function is
@@ -511,7 +516,8 @@ function write_columns(
511516
integer_variables = list_of_integer_variables(model, var_to_column)
512517
println(io, "COLUMNS")
513518
int_open = false
514-
for (column, variable) in enumerate(ordered_names)
519+
for (variable, column) in var_to_column
520+
var_name = _var_name(model, variable, column, options.generic_names)
515521
is_int = column in integer_variables
516522
if is_int && !int_open
517523
println(io, Card(f2 = "MARKER", f3 = "'MARKER'", f5 = "'INTORG'"))
@@ -523,13 +529,13 @@ function write_columns(
523529
if length(coefficients[column]) == 0
524530
# Every variable must appear in the COLUMNS section. Add a 0
525531
# objective coefficient instead.
526-
println(io, Card(f2 = variable, f3 = "OBJ", f4 = "0"))
532+
println(io, Card(f2 = var_name, f3 = "OBJ", f4 = "0"))
527533
end
528534
for (constraint, coefficient) in coefficients[column]
529535
println(
530536
io,
531537
Card(
532-
f2 = variable,
538+
f2 = var_name,
533539
f3 = constraint,
534540
f4 = _to_string(coefficient),
535541
),
@@ -750,9 +756,10 @@ function _collect_bounds(bounds, model, ::Type{S}, var_to_column) where {S}
750756
return
751757
end
752758

753-
function write_bounds(io::IO, model::Model, ordered_names, var_to_column)
759+
function write_bounds(io::IO, model::Model, var_to_column)
760+
options = get_options(model)
754761
println(io, "BOUNDS")
755-
bounds = [(-Inf, Inf, VTYPE_CONTINUOUS) for _ in ordered_names]
762+
bounds = [(-Inf, Inf, VTYPE_CONTINUOUS) for _ in 1:length(var_to_column)]
756763
@_unroll for S in (
757764
MOI.LessThan{Float64},
758765
MOI.GreaterThan{Float64},
@@ -762,7 +769,8 @@ function write_bounds(io::IO, model::Model, ordered_names, var_to_column)
762769
)
763770
_collect_bounds(bounds, model, S, var_to_column)
764771
end
765-
for (column, var_name) in enumerate(ordered_names)
772+
for (variable, column) in var_to_column
773+
var_name = _var_name(model, variable, column, options.generic_names)
766774
lower, upper, vtype = bounds[column]
767775
if vtype == VTYPE_BINARY
768776
println(io, Card(f1 = "BV", f2 = "bounds", f3 = var_name))
@@ -782,7 +790,7 @@ end
782790
# QUADRATIC OBJECTIVE
783791
# ==============================================================================
784792

785-
function write_quadobj(io::IO, model::Model, ordered_names, var_to_column)
793+
function write_quadobj(io::IO, model::Model, var_to_column)
786794
f = _get_objective(model)
787795
if isempty(f.quadratic_terms)
788796
return
@@ -798,8 +806,8 @@ function write_quadobj(io::IO, model::Model, ordered_names, var_to_column)
798806
end
799807
_write_q_matrix(
800808
io,
809+
model,
801810
f,
802-
ordered_names,
803811
var_to_column;
804812
duplicate_off_diagonal = options.quadratic_format ==
805813
kQuadraticFormatCPLEX,
@@ -809,18 +817,20 @@ end
809817

810818
function _write_q_matrix(
811819
io::IO,
820+
model::Model,
812821
f,
813-
ordered_names,
814822
var_to_column;
815823
duplicate_off_diagonal::Bool,
816824
)
825+
options = get_options(model)
817826
# Convert the quadratic terms into matrix form. We don't need to scale
818827
# because MOI uses the same Q/2 format as Gurobi, but we do need to ensure
819828
# we collate off-diagonal terms in the lower-triangular.
820-
terms = Dict{Tuple{Int,Int},Float64}()
829+
terms = Dict{Tuple{MOI.VariableIndex,MOI.VariableIndex},Float64}()
821830
for term in f.quadratic_terms
822-
x, y = var_to_column[term.variable_1], var_to_column[term.variable_2]
823-
if x > y
831+
x = term.variable_1
832+
y = term.variable_2
833+
if var_to_column[x] > var_to_column[y]
824834
x, y = y, x
825835
end
826836
if haskey(terms, (x, y))
@@ -830,23 +840,20 @@ function _write_q_matrix(
830840
end
831841
end
832842
# Use sort for reproducibility, and so the Q matrix is given in order.
833-
for (x, y) in sort!(collect(keys(terms)))
843+
for (x, y) in sort!(
844+
collect(keys(terms)),
845+
by = ((x, y),) -> (var_to_column[x], var_to_column[y]),
846+
)
847+
x_name = _var_name(model, x, var_to_column[x], options.generic_names)
848+
y_name = _var_name(model, y, var_to_column[y], options.generic_names)
834849
println(
835850
io,
836-
Card(
837-
f2 = ordered_names[x],
838-
f3 = ordered_names[y],
839-
f4 = _to_string(terms[(x, y)]),
840-
),
851+
Card(f2 = x_name, f3 = y_name, f4 = _to_string(terms[(x, y)])),
841852
)
842853
if x != y && duplicate_off_diagonal
843854
println(
844855
io,
845-
Card(
846-
f2 = ordered_names[y],
847-
f3 = ordered_names[x],
848-
f4 = _to_string(terms[(x, y)]),
849-
),
856+
Card(f2 = y_name, f3 = x_name, f4 = _to_string(terms[(x, y)])),
850857
)
851858
end
852859
end
@@ -857,7 +864,7 @@ end
857864
# QUADRATIC CONSTRAINTS
858865
# ==============================================================================
859866

860-
function write_quadcons(io::IO, model::Model, ordered_names, var_to_column)
867+
function write_quadcons(io::IO, model::Model, var_to_column)
861868
options = get_options(model)
862869
F = MOI.ScalarQuadraticFunction{Float64}
863870
for S in (
@@ -876,8 +883,8 @@ function write_quadcons(io::IO, model::Model, ordered_names, var_to_column)
876883
f = MOI.get(model, MOI.ConstraintFunction(), ci)
877884
_write_q_matrix(
878885
io,
886+
model,
879887
f,
880-
ordered_names,
881888
var_to_column;
882889
duplicate_off_diagonal = options.quadratic_format !=
883890
kQuadraticFormatMosek,
@@ -891,22 +898,22 @@ end
891898
# SOS
892899
# ==============================================================================
893900

894-
function write_sos_constraint(
895-
io::IO,
896-
model::Model,
897-
index,
898-
ordered_names,
899-
var_to_column,
900-
)
901+
function write_sos_constraint(io::IO, model::Model, index, var_to_column)
902+
options = get_options(model)
901903
func = MOI.get(model, MOI.ConstraintFunction(), index)
902904
set = MOI.get(model, MOI.ConstraintSet(), index)
903905
for (variable, weight) in zip(func.variables, set.weights)
904-
column = var_to_column[variable]
905-
println(io, Card(f2 = ordered_names[column], f3 = _to_string(weight)))
906+
var_name = _var_name(
907+
model,
908+
variable,
909+
var_to_column[variable],
910+
options.generic_names,
911+
)
912+
println(io, Card(f2 = var_name, f3 = _to_string(weight)))
906913
end
907914
end
908915

909-
function write_sos(io::IO, model::Model, ordered_names, var_to_column)
916+
function write_sos(io::IO, model::Model, var_to_column)
910917
sos1_indices = MOI.get(
911918
model,
912919
MOI.ListOfConstraintIndices{MOI.VectorOfVariables,MOI.SOS1{Float64}}(),
@@ -921,13 +928,7 @@ function write_sos(io::IO, model::Model, ordered_names, var_to_column)
921928
for (sos_type, indices) in enumerate([sos1_indices, sos2_indices])
922929
for index in indices
923930
println(io, Card(f1 = "S$(sos_type)", f2 = "SOS$(idx)"))
924-
write_sos_constraint(
925-
io,
926-
model,
927-
index,
928-
ordered_names,
929-
var_to_column,
930-
)
931+
write_sos_constraint(io, model, index, var_to_column)
931932
idx += 1
932933
end
933934
end

src/FileFormats/utils.jl

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,39 @@ Rename all variables and constraints in `model` to have generic names.
3939
This is helpful for users with proprietary models to avoid leaking information.
4040
"""
4141
function create_generic_names(model::MOI.ModelLike)
42+
create_generic_variable_names(model)
43+
create_generic_constraint_names(model)
44+
return
45+
end
46+
47+
function create_generic_variable_names(model::MOI.ModelLike)
4248
for (i, x) in enumerate(MOI.get(model, MOI.ListOfVariableIndices()))
4349
MOI.set(model, MOI.VariableName(), x, "C$i")
4450
end
51+
return
52+
end
53+
54+
function create_generic_constraint_names(model::MOI.ModelLike)
4555
i = 1
4656
for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent())
47-
if F == MOI.VariableIndex
48-
continue # VariableIndex constraints do not need a name.
49-
end
50-
for c in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
51-
MOI.set(model, MOI.ConstraintName(), c, "R$i")
52-
i += 1
57+
if F != MOI.VariableIndex
58+
i = create_generic_constraint_names(model, F, S, i)
5359
end
5460
end
61+
return
62+
end
63+
64+
function create_generic_constraint_names(
65+
model::MOI.ModelLike,
66+
::Type{F},
67+
::Type{S},
68+
i::Int,
69+
) where {F,S}
70+
for c in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
71+
MOI.set(model, MOI.ConstraintName(), c, "R$i")
72+
i += 1
73+
end
74+
return i
5575
end
5676

5777
function _replace(s::String, replacements::Vector{Function})

0 commit comments

Comments
 (0)