From 68228da8568bc94594bd8bc22f219798c4d405ed Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 30 Aug 2024 11:13:31 +1200 Subject: [PATCH] [FileFormats.MPS] fix MAX_SENSE with quadratic objective (#2539) --- src/FileFormats/MPS/MPS.jl | 21 +++++++++++---------- test/FileFormats/MPS/MPS.jl | 24 ++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/FileFormats/MPS/MPS.jl b/src/FileFormats/MPS/MPS.jl index bebc9611b9..e1b2267dad 100644 --- a/src/FileFormats/MPS/MPS.jl +++ b/src/FileFormats/MPS/MPS.jl @@ -237,7 +237,7 @@ function Base.write(io::IO, model::Model) write_rhs(io, model, obj_const) write_ranges(io, model) write_bounds(io, model, var_to_column) - write_quadobj(io, model, var_to_column) + write_quadobj(io, model, flip_obj, var_to_column) if options.quadratic_format != kQuadraticFormatCPLEX # Gurobi needs qcons _after_ quadobj and _before_ SOS. write_quadcons(io, model, var_to_column) @@ -805,7 +805,7 @@ end # QUADRATIC OBJECTIVE # ============================================================================== -function write_quadobj(io::IO, model::Model, var_to_column) +function write_quadobj(io::IO, model::Model, flip_obj::Bool, var_to_column) f = _get_objective(model) if isempty(f.quadratic_terms) return @@ -822,6 +822,7 @@ function write_quadobj(io::IO, model::Model, var_to_column) _write_q_matrix( io, model, + flip_obj, f, var_to_column; duplicate_off_diagonal = options.quadratic_format == @@ -833,6 +834,7 @@ end function _write_q_matrix( io::IO, model::Model, + flip_obj::Bool, f, var_to_column; duplicate_off_diagonal::Bool, @@ -861,15 +863,13 @@ function _write_q_matrix( ) x_name = _var_name(model, x, var_to_column[x], options.generic_names) y_name = _var_name(model, y, var_to_column[y], options.generic_names) - println( - io, - Card(f2 = x_name, f3 = y_name, f4 = _to_string(terms[(x, y)])), - ) + coef = terms[(x, y)] + if flip_obj + coef *= -1 + end + println(io, Card(f2 = x_name, f3 = y_name, f4 = _to_string(coef))) if x != y && duplicate_off_diagonal - println( - io, - Card(f2 = y_name, f3 = x_name, f4 = _to_string(terms[(x, y)])), - ) + println(io, Card(f2 = y_name, f3 = x_name, f4 = _to_string(coef))) end end return @@ -899,6 +899,7 @@ function write_quadcons(io::IO, model::Model, var_to_column) _write_q_matrix( io, model, + false, # flip_obj f, var_to_column; duplicate_off_diagonal = options.quadratic_format != diff --git a/test/FileFormats/MPS/MPS.jl b/test/FileFormats/MPS/MPS.jl index 3fccdc1801..8081411113 100644 --- a/test/FileFormats/MPS/MPS.jl +++ b/test/FileFormats/MPS/MPS.jl @@ -9,10 +9,9 @@ module TestMPS using Test import MathOptInterface as MOI +import MathOptInterface.FileFormats: MPS import DataStructures: OrderedDict -const MPS = MOI.FileFormats.MPS - function runtests() for name in names(@__MODULE__; all = true) if startswith("$(name)", "test_") @@ -1249,6 +1248,27 @@ function test_binary_with_infeasible_bounds() return end +function test_issue_2538() + model = MPS.Model() + x = MOI.add_variables(model, 2) + f = 1.0 * x[1] + 2.0 * x[2] + 3.0 * x[1] * x[2] + 4.0 * x[2] * x[2] + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + io = IOBuffer() + write(io, model) + model_2 = MPS.Model() + seekstart(io) + read!(io, model_2) + MOI.get(model_2, MOI.ObjectiveSense()) == MOI.MIN_SENSE + y = MOI.get(model_2, MOI.ListOfVariableIndices()) + g = MOI.get(model_2, MOI.ObjectiveFunction{typeof(f)}()) + @test isapprox( + g, + -1.0 * y[1] - 2.0 * y[2] - 3.0 * y[1] * y[2] - 4.0 * y[2] * y[2], + ) + return +end + end # TestMPS TestMPS.runtests()