diff --git a/src/MOI_wrapper/MOI_nonlinear.jl b/src/MOI_wrapper/MOI_nonlinear.jl index 8208802..976098c 100644 --- a/src/MOI_wrapper/MOI_nonlinear.jl +++ b/src/MOI_wrapper/MOI_nonlinear.jl @@ -206,15 +206,24 @@ function _add_expression_tree_node( append!(data, -1.0) append!(parent, parent_index) multiply_parent_index = Cint(length(opcode) - 1) - _add_expression_tree_node( - model, - stack, - opcode, - data, - parent, - term.coefficient, - multiply_parent_index, - ) + # https://jump.dev/MathOptInterface.jl/stable/reference/standard_form + # ScalarQuadraticFunction stores diagonal ScalarQuadraticTerms multiplied by + # 2 + coeff = term.coefficient + if term.variable_1 == term.variable_2 + coeff /= 2 + end + if !isone(coeff) + _add_expression_tree_node( + model, + stack, + opcode, + data, + parent, + coeff, + multiply_parent_index, + ) + end _add_expression_tree_node( model, stack, @@ -274,19 +283,13 @@ function _add_expression_tree_node( ) end for term in s.quadratic_terms - # https://jump.dev/MathOptInterface.jl/stable/reference/standard_form - # ScalarQuadraticFunction stores ScalarQuadraticTerms multiplied by 2 _add_expression_tree_node( model, stack, opcode, data, parent, - MOI.ScalarQuadraticTerm{Float64}( - 0.5 * term.coefficient, - term.variable_1, - term.variable_2, - ), + term, plus_parent_index, ) end diff --git a/test/MOI/MOI_wrapper.jl b/test/MOI/MOI_wrapper.jl index f841267..b8d43f5 100644 --- a/test/MOI/MOI_wrapper.jl +++ b/test/MOI/MOI_wrapper.jl @@ -1352,8 +1352,8 @@ function test_nonlinear_quadratic_3() MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) f = 1.0 * x + 1.0 * y MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) - f1 = MOI.ScalarQuadraticTerm{Float64}(1.0, x, x) - f2 = MOI.ScalarQuadraticTerm{Float64}(1.0, y, y) + f1 = MOI.ScalarQuadraticTerm{Float64}(2.0, x, x) + f2 = MOI.ScalarQuadraticTerm{Float64}(2.0, y, y) f3 = MOI.ScalarNonlinearFunction(:+, Any[f1, f2]) g = MOI.ScalarNonlinearFunction(:sqrt, Any[f3]) c = MOI.add_constraint(model, g, MOI.LessThan(1.0)) @@ -1456,6 +1456,28 @@ function test_delete_nonlinear_index() return end +function test_scalar_quadratic_function_with_off_diag_in_scalar_nonlinear() + if !Gurobi._supports_nonlinear() + return + end + for (a, b, status) in [ + (1.0, 2.0, MOI.OPTIMAL), + (1.0, 3.0, MOI.INFEASIBLE), + (2.0, 3.0, MOI.OPTIMAL), + (2.0, 4.0, MOI.INFEASIBLE), + ] + model = Gurobi.Optimizer(GRB_ENV) + MOI.set(model, MOI.Silent(), true) + x, _ = MOI.add_constrained_variable(model, MOI.EqualTo(2.0)) + y, _ = MOI.add_constrained_variable(model, MOI.EqualTo(3.0)) + f = MOI.ScalarNonlinearFunction(:sqrt, Any[a*x*y]) + MOI.add_constraint(model, f, MOI.GreaterThan(b)) + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == status + end + return +end + end # TestMOIWrapper TestMOIWrapper.runtests()