Skip to content

Commit 8e8006e

Browse files
authored
[FileFormats] add option for generic names (#1808)
1 parent 0393088 commit 8e8006e

File tree

5 files changed

+147
-9
lines changed

5 files changed

+147
-9
lines changed

docs/src/submodules/FileFormats/overview.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ julia> model = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_NL)
5252
An AMPL (.nl) model
5353
```
5454

55+
**The REW file format**
56+
57+
```jldoctest
58+
julia> model = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_REW)
59+
A Mathematical Programming System (MPS) model
60+
```
61+
62+
Note that the [REW format](https://www.gurobi.com/documentation/9.5/refman/rew_format.html)
63+
is identical to the MPS file format, except that all names are replaced with
64+
generic identifiers.
65+
5566
**The SDPA file format**
5667

5768
```jldoctest

src/FileFormats/FileFormats.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ List of accepted export formats.
2626
- `FORMAT_MOF`: the MathOptFormat file format
2727
- `FORMAT_MPS`: the MPS file format
2828
- `FORMAT_NL`: the AMPL .nl file format
29+
- `FORMAT_REW`: the .rew file format, which is MPS with generic names
2930
- `FORMAT_SDPA`: the SemiDefinite Programming Algorithm format
3031
"""
3132
@enum(
@@ -36,6 +37,7 @@ List of accepted export formats.
3637
FORMAT_MOF,
3738
FORMAT_MPS,
3839
FORMAT_NL,
40+
FORMAT_REW,
3941
FORMAT_SDPA,
4042
)
4143

@@ -69,6 +71,8 @@ function Model(;
6971
return MPS.Model(; kwargs...)
7072
elseif format == FORMAT_NL
7173
return NL.Model(; kwargs...)
74+
elseif format == FORMAT_REW
75+
return MPS.Model(; generic_names = true, kwargs...)
7276
elseif format == FORMAT_SDPA
7377
return SDPA.Model(; kwargs...)
7478
else
@@ -81,6 +85,10 @@ function Model(;
8185
(".lp", LP.Model),
8286
(".mof.json", MOF.Model),
8387
(".mps", MPS.Model),
88+
(
89+
".rew",
90+
(; kwargs...) -> MPS.Model(; generic_names = true, kwargs...),
91+
),
8492
(".nl", NL.Model),
8593
(".dat-s", SDPA.Model),
8694
(".sdpa", SDPA.Model),

src/FileFormats/MPS/MPS.jl

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ end
4242
struct Options
4343
warn::Bool
4444
objsense::Bool
45+
generic_names::Bool
4546
end
4647

47-
get_options(m::Model) = get(m.ext, :MPS_OPTIONS, Options(false, true))
48+
get_options(m::Model) = get(m.ext, :MPS_OPTIONS, Options(false, false, false))
4849

4950
"""
5051
Model(; kwargs...)
@@ -55,10 +56,17 @@ Keyword arguments are:
5556
5657
- `warn::Bool=false`: print a warning when variables or constraints are renamed.
5758
- `print_objsense::Bool=false`: print the OBJSENSE section when writing
59+
- `generic_names::Bool=false`: strip all names in the model and replace them
60+
with the generic names `C\$i` and `R\$i` for the i'th column and row
61+
respectively.
5862
"""
59-
function Model(; warn::Bool = false, print_objsense::Bool = false)
63+
function Model(;
64+
warn::Bool = false,
65+
print_objsense::Bool = false,
66+
generic_names::Bool = false,
67+
)
6068
model = Model{Float64}()
61-
model.ext[:MPS_OPTIONS] = Options(warn, print_objsense)
69+
model.ext[:MPS_OPTIONS] = Options(warn, print_objsense, generic_names)
6270
return model
6371
end
6472

@@ -160,11 +168,15 @@ Write `model` to `io` in the MPS file format.
160168
"""
161169
function Base.write(io::IO, model::Model)
162170
options = get_options(model)
163-
FileFormats.create_unique_names(
164-
model;
165-
warn = options.warn,
166-
replacements = Function[s->replace(s, ' ' => '_')],
167-
)
171+
if options.generic_names
172+
FileFormats.create_generic_names(model)
173+
else
174+
FileFormats.create_unique_names(
175+
model;
176+
warn = options.warn,
177+
replacements = Function[s->replace(s, ' ' => '_')],
178+
)
179+
end
168180
ordered_names = String[]
169181
names = Dict{MOI.VariableIndex,String}()
170182
for x in MOI.get(model, MOI.ListOfVariableIndices())

src/FileFormats/utils.jl

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
create_unique_names(
77
model::MOI.ModelLike;
88
warn::Bool = false,
9-
replacements::Vector{Function} = Function[]
9+
replacements::Vector{Function} = Function[],
1010
)
1111
1212
Rename variables in `model` to ensure that all variables and constraints have
@@ -25,6 +25,29 @@ function create_unique_names(
2525
return
2626
end
2727

28+
"""
29+
create_generic_names(model::MOI.ModelLike)
30+
31+
Rename all variables and constraints in `model` to have generic names.
32+
33+
This is helpful for users with proprietary models to avoid leaking information.
34+
"""
35+
function create_generic_names(model::MOI.ModelLike)
36+
for (i, x) in enumerate(MOI.get(model, MOI.ListOfVariableIndices()))
37+
MOI.set(model, MOI.VariableName(), x, "C$i")
38+
end
39+
i = 1
40+
for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent())
41+
if F == MOI.VariableIndex
42+
continue # VariableIndex constraints do not need a name.
43+
end
44+
for c in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
45+
MOI.set(model, MOI.ConstraintName(), c, "R$i")
46+
i += 1
47+
end
48+
end
49+
end
50+
2851
function _replace(s::String, replacements::Vector{Function})
2952
for f in replacements
3053
s = f(s)

test/FileFormats/MPS/MPS.jl

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,90 @@ function test_sos_constraints()
569569
return
570570
end
571571

572+
function test_generic_names()
573+
model = MPS.Model(; generic_names = true)
574+
x = MOI.add_variable(model)
575+
y = MOI.add_variable(model)
576+
c = MOI.add_constraint(
577+
model,
578+
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, [x, y]), 0.0),
579+
MOI.EqualTo(1.0),
580+
)
581+
MOI.add_constraint(model, y, MOI.GreaterThan(2.0))
582+
@test sprint(write, model) ==
583+
"NAME \n" *
584+
"ROWS\n" *
585+
" N OBJ\n" *
586+
" E R1\n" *
587+
"COLUMNS\n" *
588+
" C1 R1 1\n" *
589+
" C2 R1 1\n" *
590+
"RHS\n" *
591+
" rhs R1 1\n" *
592+
"RANGES\n" *
593+
"BOUNDS\n" *
594+
" FR bounds C1\n" *
595+
" LO bounds C2 2\n" *
596+
" PL bounds C2\n" *
597+
"ENDATA\n"
598+
end
599+
600+
function test_rew_filename()
601+
model = MOI.FileFormats.Model(; filename = "test.rew")
602+
x = MOI.add_variable(model)
603+
y = MOI.add_variable(model)
604+
c = MOI.add_constraint(
605+
model,
606+
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, [x, y]), 0.0),
607+
MOI.EqualTo(1.0),
608+
)
609+
MOI.add_constraint(model, y, MOI.GreaterThan(2.0))
610+
@test sprint(write, model) ==
611+
"NAME \n" *
612+
"ROWS\n" *
613+
" N OBJ\n" *
614+
" E R1\n" *
615+
"COLUMNS\n" *
616+
" C1 R1 1\n" *
617+
" C2 R1 1\n" *
618+
"RHS\n" *
619+
" rhs R1 1\n" *
620+
"RANGES\n" *
621+
"BOUNDS\n" *
622+
" FR bounds C1\n" *
623+
" LO bounds C2 2\n" *
624+
" PL bounds C2\n" *
625+
"ENDATA\n"
626+
end
627+
628+
function test_rew_format()
629+
model = MOI.FileFormats.Model(; format = MOI.FileFormats.FORMAT_REW)
630+
x = MOI.add_variable(model)
631+
y = MOI.add_variable(model)
632+
c = MOI.add_constraint(
633+
model,
634+
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, [x, y]), 0.0),
635+
MOI.EqualTo(1.0),
636+
)
637+
MOI.add_constraint(model, y, MOI.GreaterThan(2.0))
638+
@test sprint(write, model) ==
639+
"NAME \n" *
640+
"ROWS\n" *
641+
" N OBJ\n" *
642+
" E R1\n" *
643+
"COLUMNS\n" *
644+
" C1 R1 1\n" *
645+
" C2 R1 1\n" *
646+
"RHS\n" *
647+
" rhs R1 1\n" *
648+
"RANGES\n" *
649+
"BOUNDS\n" *
650+
" FR bounds C1\n" *
651+
" LO bounds C2 2\n" *
652+
" PL bounds C2\n" *
653+
"ENDATA\n"
654+
end
655+
572656
function runtests()
573657
for name in names(@__MODULE__, all = true)
574658
if startswith("$(name)", "test_")

0 commit comments

Comments
 (0)