diff --git a/src/FileFormats/MPS/MPS.jl b/src/FileFormats/MPS/MPS.jl index 6514276985..364eec37b7 100644 --- a/src/FileFormats/MPS/MPS.jl +++ b/src/FileFormats/MPS/MPS.jl @@ -41,9 +41,10 @@ end struct Options warn::Bool + objsense::Bool end -get_options(m::Model) = get(m.ext, :MPS_OPTIONS, Options(false)) +get_options(m::Model) = get(m.ext, :MPS_OPTIONS, Options(false, true)) """ Model(; kwargs...) @@ -53,10 +54,11 @@ Create an empty instance of FileFormats.MPS.Model. 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 """ -function Model(; warn::Bool = false) +function Model(; warn::Bool = false, print_objsense::Bool = false) model = Model{Float64}() - model.ext[:MPS_OPTIONS] = Options(warn) + model.ext[:MPS_OPTIONS] = Options(warn, print_objsense) return model end @@ -171,13 +173,18 @@ function Base.write(io::IO, model::Model) names[x] = n end write_model_name(io, model) - if MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE - println(io, "OBJSENSE MAX") + flip_obj = false + if options.objsense + if MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE + println(io, "OBJSENSE MAX") + else + println(io, "OBJSENSE MIN") + end else - println(io, "OBJSENSE MIN") + flip_obj = MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE end write_rows(io, model) - obj_const = write_columns(io, model, ordered_names, names) + obj_const = write_columns(io, model, flip_obj, ordered_names, names) write_rhs(io, model, obj_const) write_ranges(io, model) write_bounds(io, model, ordered_names, names) @@ -256,10 +263,12 @@ function _extract_terms( coefficients::Dict{String,Vector{Tuple{String,Float64}}}, row_name::String, func::MOI.ScalarAffineFunction, + flip_sign::Bool = false, ) for term in func.terms variable_name = v_names[term.variable] - push!(coefficients[variable_name], (row_name, term.coefficient)) + coef = flip_sign ? -term.coefficient : term.coefficient + push!(coefficients[variable_name], (row_name, coef)) end return end @@ -281,7 +290,7 @@ function _collect_coefficients( return end -function write_columns(io::IO, model::Model, ordered_names, names) +function write_columns(io::IO, model::Model, flip_obj, ordered_names, names) coefficients = Dict{String,Vector{Tuple{String,Float64}}}( n => Tuple{String,Float64}[] for n in ordered_names ) @@ -294,7 +303,7 @@ function write_columns(io::IO, model::Model, ordered_names, names) model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) - _extract_terms(names, coefficients, "OBJ", obj_func) + _extract_terms(names, coefficients, "OBJ", obj_func, flip_obj) integer_variables = list_of_integer_variables(model, names) println(io, "COLUMNS") int_open = false diff --git a/test/FileFormats/MPS/MPS.jl b/test/FileFormats/MPS/MPS.jl index 0e944a4044..7cacc3e616 100644 --- a/test/FileFormats/MPS/MPS.jl +++ b/test/FileFormats/MPS/MPS.jl @@ -8,8 +8,8 @@ const MOIU = MOI.Utilities const MPS = MOI.FileFormats.MPS const MPS_TEST_FILE = "test.mps" -function _test_model_equality(model_string, variables, constraints) - model = MPS.Model() +function _test_model_equality(model_string, variables, constraints; kwargs...) + model = MPS.Model(; kwargs...) MOIU.loadfromstring!(model, model_string) MOI.write_to_file(model, MPS_TEST_FILE) model_2 = MPS.Model() @@ -114,7 +114,18 @@ function test_maximization() MOI.set(model, MOI.VariableName(), x, "x") MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), x) - @test sprint(MPS.write_columns, model, ["x"], Dict(x => "x")) == + @test sprint(MPS.write_columns, model, true, ["x"], Dict(x => "x")) == + "COLUMNS\n x OBJ -1\n" +end + +function test_maximization_objsense_false() + model = MPS.Model(; print_objsense = true) + x = MOI.add_variable(model) + MOI.set(model, MOI.VariableName(), x, "x") + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), x) + sprint(MPS.write, model) + @test sprint(MPS.write_columns, model, false, ["x"], Dict(x => "x")) == "COLUMNS\n x OBJ 1\n" end @@ -290,7 +301,7 @@ c1: 1.1 * x in Interval(1.0, 2.0) end function test_objsense_max() - return _test_model_equality( + _test_model_equality( """ variables: x maxobjective: 1.2x @@ -298,7 +309,19 @@ c1: 1.0 * x >= 0.0 """, ["x"], ["c1"], + print_objsense = true, + ) + _test_model_equality( + """ +variables: x +minobjective: 1.2x +c1: 1.0 * x >= 0.0 +""", + ["x"], + ["c1"], + print_objsense = true, ) + return end function test_MARKER_INT() @@ -400,7 +423,6 @@ a_really_long_name <= 2.0 MOI.write_to_file(model, MPS_TEST_FILE) @test read(MPS_TEST_FILE, String) == "NAME \n" * - "OBJSENSE MIN\n" * "ROWS\n" * " N OBJ\n" * "COLUMNS\n" * @@ -446,7 +468,6 @@ function test_names_with_spaces() MOI.set(model, MOI.ConstraintName(), c, "c c") @test sprint(write, model) == "NAME \n" * - "OBJSENSE MIN\n" * "ROWS\n" * " N OBJ\n" * " E c_c\n" * @@ -460,6 +481,45 @@ function test_names_with_spaces() "ENDATA\n" end +function test_objsense_default() + model = MPS.Model() + x = MOI.add_variable(model) + MOI.set(model, MOI.VariableName(), x, "x") + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), x) + @test sprint(write, model) == + "NAME \n" * + "ROWS\n" * + " N OBJ\n" * + "COLUMNS\n" * + " x OBJ -1\n" * + "RHS\n" * + "RANGES\n" * + "BOUNDS\n" * + " FR bounds x\n" * + "ENDATA\n" +end + +function test_objsense_true() + model = MPS.Model(; print_objsense = true) + x = MOI.add_variable(model) + MOI.set(model, MOI.VariableName(), x, "x") + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), x) + @test sprint(write, model) == + "NAME \n" * + "OBJSENSE MAX\n" * + "ROWS\n" * + " N OBJ\n" * + "COLUMNS\n" * + " x OBJ 1\n" * + "RHS\n" * + "RANGES\n" * + "BOUNDS\n" * + " FR bounds x\n" * + "ENDATA\n" +end + function test_sos_constraints() model = MPS.Model() x = MOI.add_variables(model, 3) @@ -476,7 +536,6 @@ function test_sos_constraints() ) @test sprint(write, model) == "NAME \n" * - "OBJSENSE MIN\n" * "ROWS\n" * " N OBJ\n" * "COLUMNS\n" *