@expression v.s. @constraint (when to use what)

I’m pretty sure this question will be redundant, but I could not find an answer anywhere. My question is more like asking a style guide so that my code can avoid any unnecessary build/calculation time.

Q1. My guess is basically I should use @expression whenever possible rather than using @constraint to reduce the size of an optimization problem. Can anyone confirm this?

Q2. In pre-solve or any internal process to determine the class of optimization (either provided by JuMP or Solvers), can one be faster than the other? For example, in model2 below, it is straightforward that the objective is quadratic (variable x variable), but I’m not sure how model1 determines it is quadratic (variable x expression).

In the examples below, model1 uses @expression to define the objective while model2 uses @constraint.

using JuMP, Ipopt
model1 = Model(Ipopt.Optimizer)
@variables(model1, begin
    x >= 0
    y >= 0
end)
ex = @expression(model1, 2x + y - 1)
@objective(model1, Min, 2 * x * ex - 1)
optimize!(model1)
value(ex)
using JuMP, Ipopt
model2 = Model(Ipopt.Optimizer)
@variables(model2, begin
    x >= 0
    y >= 0
    ex
end)
@constraint(model2, ex == 2x + y - 1)
@objective(model2, Min, 2 * x * ex - 1)
optimize!(model2)
value(model2[:ex])
1 Like

Yes. Expressions get substituted into the final model. The constraint approach will add an extra decision variable and an extra constraint. Some solvers have presolve routines that take care of this, but others, like Ipopt, don’t.

Your second question is related: the substitution happens at the JuMP level before the problem reaches the solver. So the solver will see the objective 4 * x^2 + 2 * x * y - 2x - 1 in the first model.

To summarize: expressions are uniformly good. They can make the code more readable, and JuMP can (in some cases) build the problem more efficiently (particularly @NLexpression when used in @NLconstraint and @NLobjective. @expression less so).

4 Likes

@odow Thank you for the clear explanation!

1 Like

Is ex = 2x + y - 1 equivalent to ex = @expression(model1, 2x + y - 1)? In the following example, both approaches lead to the same result and typeof(ex) = AffExpr.

using JuMP, GLPK
model1 = Model(GLPK.Optimizer)
@variables(model1, begin
    x >= 0
    y >= 0
end)
# ex = @expression(model1, 2x + y - 1)
ex = 2x + y - 1
@show typeof(ex)
@objective(model1, Min, 2 * x + ex - 1)
optimize!(model1)
value(ex)
1 Like

Read https://jump.dev/JuMP.jl/stable/tutorials/Getting%20started/performance_tips/#Use-macros-to-build-expressions

1 Like

Thank you. I just noticed it. In short, ex = @expression(model1, 2x + y - 1) is preferred:

Use JuMP’s macros (or add_to_expression! to build expressions. Avoid constructing expressions outside the macros.

As a side note, it may be better to define a named expression with @expression(model1, ex, 2x + y - 1) such that we can access it later in a different scope with model[:ex], e.g., when a model is returned from a builder function.

1 Like