From 5ed3e9f647dec29ae608b31025b42d8965f1477e Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 21 Apr 2022 15:26:06 +1200 Subject: [PATCH 1/4] [FileFormats] add option for generic names --- src/FileFormats/MPS/MPS.jl | 28 ++++++++++++++++++++-------- src/FileFormats/utils.jl | 25 ++++++++++++++++++++++++- test/FileFormats/MPS/MPS.jl | 29 +++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/FileFormats/MPS/MPS.jl b/src/FileFormats/MPS/MPS.jl index 364eec37b7..743659561f 100644 --- a/src/FileFormats/MPS/MPS.jl +++ b/src/FileFormats/MPS/MPS.jl @@ -42,9 +42,10 @@ end struct Options warn::Bool objsense::Bool + generic_names::Bool end -get_options(m::Model) = get(m.ext, :MPS_OPTIONS, Options(false, true)) +get_options(m::Model) = get(m.ext, :MPS_OPTIONS, Options(false, false, false)) """ Model(; kwargs...) @@ -55,10 +56,17 @@ Keyword arguments are: - `warn::Bool=false`: print a warning when variables or constraints are renamed. - `print_objsense::Bool=false`: print the OBJSENSE section when writing + - `generic_names::Bool=false`: strip all names in the model and replace them + with the generic names `C\$i` and `R\$i` for the i'th column and row + respectively. """ -function Model(; warn::Bool = false, print_objsense::Bool = false) +function Model(; + warn::Bool = false, + print_objsense::Bool = false, + generic_names::Bool = false, +) model = Model{Float64}() - model.ext[:MPS_OPTIONS] = Options(warn, print_objsense) + model.ext[:MPS_OPTIONS] = Options(warn, print_objsense, generic_names) return model end @@ -160,11 +168,15 @@ Write `model` to `io` in the MPS file format. """ function Base.write(io::IO, model::Model) options = get_options(model) - FileFormats.create_unique_names( - model; - warn = options.warn, - replacements = Function[s->replace(s, ' ' => '_')], - ) + if options.generic_names + FileFormats.create_generic_names(model) + else + FileFormats.create_unique_names( + model; + warn = options.warn, + replacements = Function[s->replace(s, ' ' => '_')], + ) + end ordered_names = String[] names = Dict{MOI.VariableIndex,String}() for x in MOI.get(model, MOI.ListOfVariableIndices()) diff --git a/src/FileFormats/utils.jl b/src/FileFormats/utils.jl index dc2bea7ab8..d48fd76507 100644 --- a/src/FileFormats/utils.jl +++ b/src/FileFormats/utils.jl @@ -6,7 +6,7 @@ create_unique_names( model::MOI.ModelLike; warn::Bool = false, - replacements::Vector{Function} = Function[] + replacements::Vector{Function} = Function[], ) Rename variables in `model` to ensure that all variables and constraints have @@ -25,6 +25,29 @@ function create_unique_names( return end +""" + create_generic_names(model::MOI.ModelLike) + +Rename all variables and constraints in `model` to have generic names. + +This is helpful for users with proprietary models to avoid leaking information. +""" +function create_generic_names(model::MOI.ModelLike) + for (i, x) in enumerate(MOI.get(model, MOI.ListOfVariableIndices())) + MOI.set(model, MOI.VariableName(), x, "C$i") + end + i = 1 + for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent()) + if F == MOI.VariableIndex + continue # VariableIndex constraints do not need a name. + end + for c in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) + MOI.set(model, MOI.ConstraintName(), c, "R$i") + i += 1 + end + end +end + function _replace(s::String, replacements::Vector{Function}) for f in replacements s = f(s) diff --git a/test/FileFormats/MPS/MPS.jl b/test/FileFormats/MPS/MPS.jl index 7cacc3e616..ebc76da89a 100644 --- a/test/FileFormats/MPS/MPS.jl +++ b/test/FileFormats/MPS/MPS.jl @@ -569,6 +569,35 @@ function test_sos_constraints() return end +function test_generic_names() + model = MPS.Model(; generic_names = true) + x = MOI.add_variable(model) + y = MOI.add_variable(model) + c = MOI.add_constraint( + model, + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, [x, y]), 0.0), + MOI.EqualTo(1.0), + ) + MOI.add_constraint(model, y, MOI.GreaterThan(2.0)) + @test sprint(write, model) == + "NAME \n" * + "OBJSENSE MIN\n" * + "ROWS\n" * + " N OBJ\n" * + " E R1\n" * + "COLUMNS\n" * + " C1 R1 1\n" * + " C2 R1 1\n" * + "RHS\n" * + " rhs R1 1\n" * + "RANGES\n" * + "BOUNDS\n" * + " FR bounds C1\n" * + " LO bounds C2 2\n" * + " PL bounds C2\n" * + "ENDATA\n" +end + function runtests() for name in names(@__MODULE__, all = true) if startswith("$(name)", "test_") From 3b8056ca06979eae71d9c6f7f14465a5b73ae69c Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 21 Apr 2022 16:17:42 +1200 Subject: [PATCH 2/4] Update test/FileFormats/MPS/MPS.jl --- test/FileFormats/MPS/MPS.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/test/FileFormats/MPS/MPS.jl b/test/FileFormats/MPS/MPS.jl index ebc76da89a..924140ca60 100644 --- a/test/FileFormats/MPS/MPS.jl +++ b/test/FileFormats/MPS/MPS.jl @@ -581,7 +581,6 @@ function test_generic_names() MOI.add_constraint(model, y, MOI.GreaterThan(2.0)) @test sprint(write, model) == "NAME \n" * - "OBJSENSE MIN\n" * "ROWS\n" * " N OBJ\n" * " E R1\n" * From fe06aee7137a7c0c37d241092fdf9f9fdf96e35a Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 22 Apr 2022 08:32:04 +1200 Subject: [PATCH 3/4] Add REW file format --- src/FileFormats/FileFormats.jl | 8 +++++ test/FileFormats/MPS/MPS.jl | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/FileFormats/FileFormats.jl b/src/FileFormats/FileFormats.jl index 7758d81a13..0164884761 100644 --- a/src/FileFormats/FileFormats.jl +++ b/src/FileFormats/FileFormats.jl @@ -26,6 +26,7 @@ List of accepted export formats. - `FORMAT_MOF`: the MathOptFormat file format - `FORMAT_MPS`: the MPS file format - `FORMAT_NL`: the AMPL .nl file format +- `FORMAT_REW`: the .rew file format, which is MPS with generic names - `FORMAT_SDPA`: the SemiDefinite Programming Algorithm format """ @enum( @@ -36,6 +37,7 @@ List of accepted export formats. FORMAT_MOF, FORMAT_MPS, FORMAT_NL, + FORMAT_REW, FORMAT_SDPA, ) @@ -69,6 +71,8 @@ function Model(; return MPS.Model(; kwargs...) elseif format == FORMAT_NL return NL.Model(; kwargs...) + elseif format == FORMAT_REW + return MPS.Model(; generic_names = true, kwargs...) elseif format == FORMAT_SDPA return SDPA.Model(; kwargs...) else @@ -81,6 +85,10 @@ function Model(; (".lp", LP.Model), (".mof.json", MOF.Model), (".mps", MPS.Model), + ( + ".rew", + (; kwargs...) -> MPS.Model(; generic_names = true, kwargs...), + ), (".nl", NL.Model), (".dat-s", SDPA.Model), (".sdpa", SDPA.Model), diff --git a/test/FileFormats/MPS/MPS.jl b/test/FileFormats/MPS/MPS.jl index 924140ca60..75019c9604 100644 --- a/test/FileFormats/MPS/MPS.jl +++ b/test/FileFormats/MPS/MPS.jl @@ -597,6 +597,62 @@ function test_generic_names() "ENDATA\n" end +function test_rew_filename() + model = MOI.FileFormats.Model(; filename = "test.rew") + x = MOI.add_variable(model) + y = MOI.add_variable(model) + c = MOI.add_constraint( + model, + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, [x, y]), 0.0), + MOI.EqualTo(1.0), + ) + MOI.add_constraint(model, y, MOI.GreaterThan(2.0)) + @test sprint(write, model) == + "NAME \n" * + "ROWS\n" * + " N OBJ\n" * + " E R1\n" * + "COLUMNS\n" * + " C1 R1 1\n" * + " C2 R1 1\n" * + "RHS\n" * + " rhs R1 1\n" * + "RANGES\n" * + "BOUNDS\n" * + " FR bounds C1\n" * + " LO bounds C2 2\n" * + " PL bounds C2\n" * + "ENDATA\n" +end + +function test_rew_format() + model = MOI.FileFormats.Model(; format = MOI.FileFormats.FORMAT_REW) + x = MOI.add_variable(model) + y = MOI.add_variable(model) + c = MOI.add_constraint( + model, + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, [x, y]), 0.0), + MOI.EqualTo(1.0), + ) + MOI.add_constraint(model, y, MOI.GreaterThan(2.0)) + @test sprint(write, model) == + "NAME \n" * + "ROWS\n" * + " N OBJ\n" * + " E R1\n" * + "COLUMNS\n" * + " C1 R1 1\n" * + " C2 R1 1\n" * + "RHS\n" * + " rhs R1 1\n" * + "RANGES\n" * + "BOUNDS\n" * + " FR bounds C1\n" * + " LO bounds C2 2\n" * + " PL bounds C2\n" * + "ENDATA\n" +end + function runtests() for name in names(@__MODULE__, all = true) if startswith("$(name)", "test_") From 7edbdaa6f410f5dd6103febe56f8ad23ee20dbf5 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 22 Apr 2022 08:34:44 +1200 Subject: [PATCH 4/4] Add to docs --- docs/src/submodules/FileFormats/overview.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/src/submodules/FileFormats/overview.md b/docs/src/submodules/FileFormats/overview.md index e5f95c4067..c3869034a5 100644 --- a/docs/src/submodules/FileFormats/overview.md +++ b/docs/src/submodules/FileFormats/overview.md @@ -52,6 +52,17 @@ julia> model = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_NL) An AMPL (.nl) model ``` +**The REW file format** + +```jldoctest +julia> model = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_REW) +A Mathematical Programming System (MPS) model +``` + +Note that the [REW format](https://www.gurobi.com/documentation/9.5/refman/rew_format.html) +is identical to the MPS file format, except that all names are replaced with +generic identifiers. + **The SDPA file format** ```jldoctest