When a containerized affine expression is included in an interval constraint, the expression seems to be automatically modified in-place to remove constants. MWE:
using JuMP
model = Model()
@variable(model, x)
@expression(model, a[1:1], x+1)
display(a)
@constraint(model, -1 <= a[1] <= 1)
display(a)
Expected output:
1-element Vector{AffExpr}:
x + 1
1-element Vector{AffExpr}:
x + 1
Actual output:
1-element Vector{AffExpr}:
x + 1
1-element Vector{AffExpr}:
x
I believe this behavior is unexpected. a may be used in future constraints, or the user may be interested in the optimal value of a. In either case, one should expect it to continue to represent x+1 regardless of the constraints in which it appears.
I have only noticed this with containerized expressions in interval constraints. The MWE behaves as expected if eithera is a scalar expression or the interval constraint is changed to a one-sided inequality.
For the moment, a work-around is to add + 0 to the constraint:
julia> using JuMP
julia> model = Model()
A JuMP Model
├ solver: none
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none
julia> @variable(model, x)
x
julia> @expression(model, a[1:1], x+1)
1-element Vector{AffExpr}:
x + 1
julia> a
1-element Vector{AffExpr}:
x + 1
julia> @constraint(model, -1 <= a[1] + 0 <= 1)
x ∈ [-2, 0]
julia> a
1-element Vector{AffExpr}:
x + 1
This bug happens when:
You are using the double-sided interval constraint or the f in set syntax where the set is LessThan, GreaterThan, EqualTo or Interval.
The body of the constraint is an expression that is more complicated than a symbol like <= a <= but does not contain any arithmetic like + 0. Something like a[1] is pretty much the only thing that could trigger this! 1 * a[1] or a[1] + 0 is too complicated, and a scalar a is too simple.
There is a constant term in the constraint body that is not 0.
It’s a fairly rare combination of events, which is why we didn’t notice this sooner