JuMP.set_upper_bound on AffExpr?

Is it possible to extend the syntax of set_upper_bound to an expression, despite (perhaps) there might not be a corresponding solver API?

Consider

julia> using JuMP

julia> model = Model();

julia> @variable(model, x[1:2]);

julia> @expression(model, e, x[1] + 2x[2]);

Suppose we could write

julia> set_upper_bound(e, 1)
ERROR: Cannot call set_upper_bound with x[1] + 2 x[2] because it is not an affine expression of one variable.

julia> set_upper_bound(e, 2)
ERROR: Cannot call set_upper_bound with x[1] + 2 x[2] because it is not an affine expression of one variable.

instead of

julia> c1 = @constraint(model, e ≤ 1)
x[1] + 2 x[2] <= 1

julia> JuMP.delete(model, c1);

julia> c1 = @constraint(model, e ≤ 2)
x[1] + 2 x[2] <= 2

Is it possible to extend the syntax of set_upper_bound to an expression

In theory there’s nothing technically stopping us, but we won’t be implementing this.

One reason (among others): what would upper_bound(e) and has_upper_bound(e) return?

The _bound has a very specific meaning in JuMP in the context of variable bounds. An affine constraint is something very different.

If the user hadn’t written set_upper_bound(e, some_Float64) ever before, then it doesn’t exist.
(@constraint are used to build “complicating” constraints, while set_upper_bound is reserved to build relatively simple constraints.) In this manner the code reads more clear.

e.g., In a Lagrangian relaxation, only those constraints built with JuMP.@constraint are dualized.

ever before, then it doesn’t exist.

And if it does exist? How do we associate the expression with a constraint? Is the constraint reference returned by set_upper_bound? What happens if the expression does not have any variable terms?

In this manner the code reads more clear

I think this is where we disagree. A bound and a constraint are different concepts. We’re going to keep them separate.

You can do something like this in your code (although I don’t think you should):

my_set_upper_bound(x::VariableRef, u) = set_upper_bound(x, u)
function my_set_upper_bound(x::AffExpr, u)
    model = owner_model(x)
    @constraint(model, x <= u)
    return
end

It’s not a big thing, we can just shelve it.

This code is not correct, I think, cf. this

julia> using JuMP; model = Model(); @variable(model, x);

julia> set_upper_bound(x, 2)

julia> set_upper_bound(x, 3); print(model)
Feasibility
Subject to
 x <= 3

The x <= 2 is overwritten by the latest <= 3, so it doesn’t exist in model anymore.

The x <= 2 is overwritten by the latest <= 3, so it doesn’t exist in model anymore.

This is another reason not to implement it

It is straightforward to piggyback on the existing API

julia> using JuMP; model = Model(); @variable(model, x[1:2]);

julia> @variable(model, e <= 1); # the RHS number is the upper bound

julia> @constraint(model, e == x[1] + 2x[2]) # the RHS is the expression
-x[1] - 2 x[2] + e == 0

julia> set_upper_bound(e, 2)

julia> set_upper_bound(e, 3)

The expense is: we need one additional decision variable.
If this variable == the desired expression x[1] + 2x[2] is an “objective” one, it’s value may be large, e.g., the value of e might be 10000, whereas value of the rest “physical” decisions could be e.g. 0 or 1.

So, in this case, the numeric scale of the decisions vary greatly, (I don’t know if it will affect the solver somehow, maybe not).

If we use expression and “upper_bound” constraints, the above numeric inconsistence could be avoided.