Reducing model building time

Is there a difference in the time taken to build the JuMP model if I use for loops for defining variables/constraints. For example:

using JuMP
model = Model()

@variable(model,  x[i in 1:7, j in 1:10] >= 0)
@constraint(model, [i in 2:6, j in 3:7], x[i,j] + x[j,i] >= 10)

Now, instead, if I do the following:

using JuMP
model = Model()
for i in 1:7
    for j in 1:10
        @variable(model, x[i,j] >= 0)
    end
end

for i in 2:6
    for j in 3:7
        @constraint(model, x[i,j] + x[j,i] >= 10)
    end
end

Although the first method is more succinct, is it always better than defining variables/constraint using for loops (or the other way round)? For small models, I understand there is practically no difference, but how do the two methods differ for large models. I obviously have a large model and want to make sure that the model building time is reduced.

Also, is one method better than the other if I have conditions in the variable/constraint definition? For example, something like x[i=1:4; mod(i, 2)==0]. Is it better (or worse) to use a for loop in this case?

I don’t think there’s any significant difference between the two versions for large models, at least as written, but I’ll leave the final word on this to the JuMP devs. In your second example though, an explicit for loop could be better when you have something like this: x[i in 1:1000, j in 1:1000, k in 1:1000; demanding_computation(k)==0]. That’s because it unrolls to something like this…

for i in 1:1000, j in 1:1000, k in 1:1000
    if demanding_computation(k) == 0
        @variable(model, x[i,j,k])
    end
end

Then it should be clear that this is better…

for k in 1:1000
    if demanding_computation(k) == 0
        for i in 1:1000, j in 1:1000
            @variable(model, x[i,j,k])
        end
    end
end

See also this section of the JuMP manual.

2 Likes

@NiclasMattsson hit upon the major points.

Depending on the solver, one thing that can help is Performance tips · JuMP.

Other than that, it’s usually a case that if your model takes a long time to build, it’s going to take an even longer time to solve.

1 Like

Thanks @NiclasMattsson and @odow for the input on this.

1 Like

Hi @odow,
Defining variables using for loop gives me the following error:

using JuMP
model = Model()
for k in 1:10
    if demanding_computation(k) == 0
        for i in 1:10, j in 1:10
            @variable(model, x[i,j,k])
        end
    end
end

This gives me the following error:

ERROR: An object of name x is already attached to this model. If this
    is intended, consider using the anonymous construction syntax, e.g.,
    `x = @variable(model, [1:N], ...)` where the name of the object does
    not appear inside the macro.

    Alternatively, use `unregister(model, :x)` to first unregister
    the existing name from the model. Note that this will not delete the
    object; it will just remove the reference at `model[:x]`.

Any way around this (I still want to use for loop)? Thanks!

You cannot build a variable like this.

It’s always useful to remember that JuMP is not a special part of Julia, so if you find yourself fighting the JuMP syntax, use a different datastructure.

I would do

using JuMP
model = Model()
add_k = [demanding_computation(k) for k in 1:10]
@variable(model, x[i=1:10, j=1:10, k=1:10; add_k[k]])
1 Like

Thanks. That makes sense. One last question:
If I have a predefined set of indices like indices = [[1,2,3], [4,5,6]], and I want to construct variables x[1,2,3] and x[4,5,6] out of it, I usually simply do:

using JuMP
model = Model()

@variable(model, x[indices] >= 0)

Although I get the correct indices, I get an extra parenthesis like x[[1,2,3]] and x[[4,5,6]], which is expected because @variable(model, x[indices] >=0) is same as @variable(model, x[i in indices]), so each vector serve as a single index. I assume this is not an issue, but is there a way I can still get multiple indices, i.e., x[1,2,3] instead of x[[1,2,3]]?

(I can post this as a separate question if you feel it’s unrelated). Thanks :slight_smile:

This is a slightly subtle question, in which the difference is a dense, one-dimensional array of variables with the keys [1, 2, 3] and [4, 5, 6], and a sparse, three-dimensional array of variables with elements in positions (1, 2, 3) and (4, 5, 6).

This comes back to the data structure question. There’s no correct answer, it depends on your model, and why you want that.

These three examples might be helpful:

julia> using JuMP

julia> indices = [[1,2,3], [4,5,6]]
2-element Vector{Vector{Int64}}:
 [1, 2, 3]
 [4, 5, 6]

julia> model = Model();

julia> @variable(model, x[indices])
1-dimensional DenseAxisArray{VariableRef,1,...} with index sets:
    Dimension 1, [[1, 2, 3], [4, 5, 6]]
And data, a 2-element Vector{VariableRef}:
 x[[1, 2, 3]]
 x[[4, 5, 6]]

julia> x[[1, 2, 3]]
x[[1, 2, 3]]

julia> x[indices[1]]
x[[1, 2, 3]]

julia> indices2 = [tuple(i...) for i in indices]
2-element Vector{Tuple{Int64, Int64, Int64}}:
 (1, 2, 3)
 (4, 5, 6)

julia> model = Model();

julia> @variable(model, x[indices2])
1-dimensional DenseAxisArray{VariableRef,1,...} with index sets:
    Dimension 1, [(1, 2, 3), (4, 5, 6)]
And data, a 2-element Vector{VariableRef}:
 x[(1, 2, 3)]
 x[(4, 5, 6)]

julia> x[(1, 2, 3)]
x[(1, 2, 3)]

julia> x[indices2[1]]
x[(1, 2, 3)]

julia> I, J, K = [sort(unique(getindex.(indices, i))) for i in 1:3]
3-element Vector{Vector{Int64}}:
 [1, 4]
 [2, 5]
 [3, 6]

julia> model = Model();

julia> @variable(model, x[i=I, j=J, k=K; [i, j, k] in indices])
JuMP.Containers.SparseAxisArray{VariableRef, 3, Tuple{Int64, Int64, Int64}} with 2 entries:
  [1, 2, 3]  =  x[1,2,3]
  [4, 5, 6]  =  x[4,5,6]

julia> x[1, 2, 3]
x[1,2,3]

The real answer is that if you’re building large-scale problems, pick a data structure that best suits the problem, not necessarily one that is provided by JuMP.

1 Like

Sounds good. Thanks for a very clear explanation!

1 Like