My understanding about the zeros function in julia is that it has independent entries.
But today I find a weird behavior which I cannot explain why.
Compare the following unexpected outcome 🔴 and the expected outcome ✅
julia> vec = zeros(Int, 3)
3-element Vector{Int64}:
0
0
0
julia> for i = 1:3
vec[i] += i
end
julia> vec
3-element Vector{Int64}:
1
2
3
julia> using JuMP
julia> model = Model();
julia> vec = zeros(JuMP.AffExpr, 3)
3-element Vector{AffExpr}:
0
0
0
julia> for i = 1:3
x = JuMP.@variable(model)
println("i = $i, x = $x")
add_to_expression!(vec[i], x)
end
i = 1, x = _[1]
i = 2, x = _[2]
i = 3, x = _[3]
julia> vec # 🔴
3-element Vector{AffExpr}:
_[1] + _[2] + _[3]
_[1] + _[2] + _[3]
_[1] + _[2] + _[3]
julia> model = Model();
julia> vec = [JuMP.AffExpr(0) for _ = 1:3]
3-element Vector{AffExpr}:
0
0
0
julia> for i = 1:3
x = JuMP.@variable(model)
println("i = $i, x = $x")
add_to_expression!(vec[i], x)
end
i = 1, x = _[1]
i = 2, x = _[2]
i = 3, x = _[3]
julia> vec # ✅
3-element Vector{AffExpr}:
_[1]
_[2]
_[3]
They behave differently because of how JuMP’s AffExpr objects are created and stored in the array.
vec = zeros(JuMP.AffExpr, 3)
This line creates an array of three references to the same default-constructed AffExpr() object.
So when you do:
add_to_expression!(vec[i], x)
you’re modifying the same object every time. The result is that all three elements in vec appear identical and contain all the variables added across iterations.
vec = [JuMP.AffExpr(0) for _ = 1:3]
This explicitly creates three distinct AffExpr objects initialized with zero. Now:
add_to_expression!(vec3[i], x)
modifies a unique object each time, so each element in vec holds a different variable — as you intended.
Avoid zeros(JuMP.AffExpr, n) — use list comprehensions to ensure unique instances:
vec = [JuMP.AffExpr(0) for _ in 1:n]
This pattern is common for JuMP modeling when dealing with arrays of expressions.