Legacy and standard constraint/objective macros represent `^2` differently in nonlinear expressions
@objective and @constraint seem to be reformulating x^2 to x*x in nonlinear expressions, which leads to poorer McCormick relaxations. This isn't an issue when using the legacy macros of @NLobjective and @NLconstraint. I suspect this happens somewhere in https://github.com/PSORLab/EAGO.jl/blob/15bb6cb47a856abbd82cbd5dd54fafc836af8abf/src/eago_optimizer/moi_wrapper.jl#L74-L100 and https://github.com/PSORLab/EAGO.jl/blob/15bb6cb47a856abbd82cbd5dd54fafc836af8abf/src/eago_optimizer/moi_wrapper.jl#L277-L294
Example:
model = JuMP.Model(EAGO.Optimizer)
@variable(model, x[1:2])
@objective(model, Min, x[1]^2 + x[2]^3)
JuMP.optimize!(model)
returns
MathOptInterface.Nonlinear.Expression(
MathOptInterface.Nonlinear.Node[
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_CALL_MULTIVARIATE, 2, -1),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_CALL_MULTIVARIATE, 1, 1),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_CALL_MULTIVARIATE, 3, 2),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_MOI_VARIABLE, 1, 3),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_MOI_VARIABLE, 1, 3),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_CALL_MULTIVARIATE, 4, 2),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_MOI_VARIABLE, 2, 6),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_VALUE, 1, 6),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_MOI_VARIABLE, 3, 1)
],
[3.0]
)
model = JuMP.Model(EAGO.Optimizer)
@variable(model, x[1:2])
@NLobjective(model, Min, x[1]^2 + x[2]^3)
JuMP.optimize!(model)
returns
MathOptInterface.Nonlinear.Expression(
MathOptInterface.Nonlinear.Node[
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_CALL_MULTIVARIATE, 2, -1),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_CALL_MULTIVARIATE, 1, 1),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_CALL_MULTIVARIATE, 4, 2),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_MOI_VARIABLE, 1, 3),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_VALUE, 1, 3),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_CALL_MULTIVARIATE, 4, 2),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_MOI_VARIABLE, 2, 6),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_VALUE, 2, 6),
MathOptInterface.Nonlinear.Node(MathOptInterface.Nonlinear.NODE_MOI_VARIABLE, 3, 1)
],
[2.0, 3.0]
)
(i.e., x^2 + x^3, as expected).
@odow can you tell if this is an EAGO or a JuMP issue?
This is coming from MOI:
https://github.com/jump-dev/MathOptInterface.jl/blob/406f7cd9b0e681f1934121055b7619ab3d666202/src/Nonlinear/parse.jl#L342-L361
The reason is that x[1]^2 is treated as a QuadExpr, not ^(x[1], 2).
julia> using JuMP
julia> model = Model();
julia> @variable(model, x[1:2]);
julia> @objective(model, Min, x[1]^2 + x[2]^3);
julia> f = objective_function(model)
(x[1]²) + (x[2] ^ 3.0)
julia> f.head
:+
julia> f.args
2-element Vector{Any}:
x[1]²
x[2] ^ 3.0
julia> f.args[1]
x[1]²
julia> typeof(f.args[1])
QuadExpr (alias for GenericQuadExpr{Float64, GenericVariableRef{Float64}})
Isn't there scope in EAGO to recognize this expression type? If someone writes x * x you don't currently exploit that as x^2?
It's not really a fix, but a work-around is
julia> using JuMP
julia> model = Model();
julia> @variable(model, x[1:2]);
julia> @objective(model, Min, x[1]^2 + x[2]^3)
(x[1]²) + (x[2] ^ 3)
julia> @force_nonlinear @objective(model, Min, x[1]^2 + x[2]^3)
(x[1] ^ 2) + (x[2] ^ 3)
Isn't there scope in EAGO to recognize this expression type? If someone writes
x * xyou don't currently exploit that asx^2?
I believe EAGO used to provide a functionality like this before v0.7 for user-defined functions, but it hasn't been updated after a lot of other internal changes. After the MOI changes for handling nonlinear functions, I'd written a recursive function to convert ScalarNonlinearFunctions to a DAG in EAGO, which would have checked for things like this, but after some discussion at the last JuMP-dev I switched to using a workaround in MOI to convert back to the old DAG format. So currently we rely on MOI's expression representation, though this might change if we get more time to develop out this functionality in EAGO.
I've opened a PR here: https://github.com/jump-dev/MathOptInterface.jl/pull/2799
It's not guaranteed that I'll merge it, but I'll see if there are any other downstream impacts.
This should be fixed in the next release of MOI