Unexpected behavior: containerized expression automatically redefined when included in interval constraint

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 either a is a scalar expression or the interval constraint is changed to a one-sided inequality.

Hi @Eli_Brock, welcome to the forum :smile:

This looks like you’ve found a really, really bad bug! I’m taking a look now.

Edit: confirmed bug on master: Bug mutating expression in interval constraint · Issue #3882 · jump-dev/JuMP.jl · GitHub. Working on a fix.

1 Like

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 :smile:

1 Like