Iterating through JuMP variables

I am doing some trivial checks on the variable values output by my optimization model, and I was wondering what is the correct way to iterate through a JuMP variable. For example, if my model has a variable x[2:9, 10:15], what is the best way to check whether each of the variable values is binary? I wanted to write a generic function that would check this irrespective of what exactly my variable indices are, for which, I thought I can simply write a for loop which calls size(value.(x),1) and size(value.(x),2), and I can step through each element. But I guess size does not work on SparseAxisArray.

Also, what could be done if my second indices is a function of the first one? For example, x[i=2:3, j=1:2*i]. In this case, how to iterate through each element? I guess eachindex works for this case, but I am not sure that’s the best way to do it.

Any help here is appreciated. Thanks!

You have a few options:

julia> model = Model();

julia> @variable(model, x[2:9, 10:15])
2-dimensional DenseAxisArray{VariableRef,2,...} with index sets:
    Dimension 1, 2:9
    Dimension 2, 10:15
And data, a 8Ă—6 Matrix{VariableRef}:
 x[2,10]  x[2,11]  x[2,12]  x[2,13]  x[2,14]  x[2,15]
 x[3,10]  x[3,11]  x[3,12]  x[3,13]  x[3,14]  x[3,15]
 x[4,10]  x[4,11]  x[4,12]  x[4,13]  x[4,14]  x[4,15]
 x[5,10]  x[5,11]  x[5,12]  x[5,13]  x[5,14]  x[5,15]
 x[6,10]  x[6,11]  x[6,12]  x[6,13]  x[6,14]  x[6,15]
 x[7,10]  x[7,11]  x[7,12]  x[7,13]  x[7,14]  x[7,15]
 x[8,10]  x[8,11]  x[8,12]  x[8,13]  x[8,14]  x[8,15]
 x[9,10]  x[9,11]  x[9,12]  x[9,13]  x[9,14]  x[9,15]

julia> is_binary.(x)
2-dimensional DenseAxisArray{Bool,2,...} with index sets:
    Dimension 1, 2:9
    Dimension 2, 10:15
And data, a 8Ă—6 Matrix{Bool}:
 0  0  0  0  0  0
 0  0  0  0  0  0
 0  0  0  0  0  0
 0  0  0  0  0  0
 0  0  0  0  0  0
 0  0  0  0  0  0
 0  0  0  0  0  0
 0  0  0  0  0  0

julia> any(is_binary.(x))
false

julia> @variable(model, y[i=2:3, j=1:2*i])
JuMP.Containers.SparseAxisArray{VariableRef, 2, Tuple{Int64, Int64}} with 10 entries:
  [2, 1]  =  y[2,1]
  [2, 2]  =  y[2,2]
  [2, 3]  =  y[2,3]
  [2, 4]  =  y[2,4]
  [3, 1]  =  y[3,1]
  [3, 2]  =  y[3,2]
  [3, 3]  =  y[3,3]
  [3, 4]  =  y[3,4]
  [3, 5]  =  y[3,5]
  [3, 6]  =  y[3,6]

julia> is_binary.(y)
JuMP.Containers.SparseAxisArray{Bool, 2, Tuple{Int64, Int64}} with 10 entries:
  [2, 1]  =  false
  [2, 2]  =  false
  [2, 3]  =  false
  [2, 4]  =  false
  [3, 1]  =  false
  [3, 2]  =  false
  [3, 3]  =  false
  [3, 4]  =  false
  [3, 5]  =  false
  [3, 6]  =  false

julia> any(is_binary.(y))
false

julia> all(.!is_binary.(y))
true

julia> for i in eachindex(x)
       @show i, x[i], is_binary(x[i])
       end
(i, x[i], is_binary(x[i])) = (CartesianIndex(1, 1), x[2,10], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(2, 1), x[3,10], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(3, 1), x[4,10], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(4, 1), x[5,10], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(5, 1), x[6,10], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(6, 1), x[7,10], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(7, 1), x[8,10], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(8, 1), x[9,10], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(1, 2), x[2,11], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(2, 2), x[3,11], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(3, 2), x[4,11], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(4, 2), x[5,11], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(5, 2), x[6,11], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(6, 2), x[7,11], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(7, 2), x[8,11], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(8, 2), x[9,11], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(1, 3), x[2,12], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(2, 3), x[3,12], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(3, 3), x[4,12], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(4, 3), x[5,12], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(5, 3), x[6,12], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(6, 3), x[7,12], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(7, 3), x[8,12], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(8, 3), x[9,12], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(1, 4), x[2,13], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(2, 4), x[3,13], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(3, 4), x[4,13], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(4, 4), x[5,13], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(5, 4), x[6,13], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(6, 4), x[7,13], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(7, 4), x[8,13], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(8, 4), x[9,13], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(1, 5), x[2,14], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(2, 5), x[3,14], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(3, 5), x[4,14], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(4, 5), x[5,14], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(5, 5), x[6,14], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(6, 5), x[7,14], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(7, 5), x[8,14], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(8, 5), x[9,14], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(1, 6), x[2,15], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(2, 6), x[3,15], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(3, 6), x[4,15], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(4, 6), x[5,15], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(5, 6), x[6,15], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(6, 6), x[7,15], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(7, 6), x[8,15], false)
(i, x[i], is_binary(x[i])) = (CartesianIndex(8, 6), x[9,15], false)

julia> for i in eachindex(y)
       @show i, y[i], is_binary(y[i])
       end
(i, y[i], is_binary(y[i])) = ((2, 4), y[2,4], false)
(i, y[i], is_binary(y[i])) = ((3, 2), y[3,2], false)
(i, y[i], is_binary(y[i])) = ((3, 1), y[3,1], false)
(i, y[i], is_binary(y[i])) = ((3, 3), y[3,3], false)
(i, y[i], is_binary(y[i])) = ((3, 6), y[3,6], false)
(i, y[i], is_binary(y[i])) = ((2, 2), y[2,2], false)
(i, y[i], is_binary(y[i])) = ((2, 1), y[2,1], false)
(i, y[i], is_binary(y[i])) = ((3, 4), y[3,4], false)
(i, y[i], is_binary(y[i])) = ((2, 3), y[2,3], false)
(i, y[i], is_binary(y[i])) = ((3, 5), y[3,5], false)

The documentation describes different ways of working with containers: Containers · JuMP

1 Like

Also worth reading through the Variables API documentation.

Hi @odow,

Thanks for the reply. But maybe I was not very clear in what I wanted to do. I don’t want to check whether the variables are constrained to be binary or not. I want to check whether the values they eventually take are binary or not. For example:

using JuMP
using Gurobi

model = Model(Gurobi.Optimizer)

@variable(model, 0 <= x[1:3, 1:5] <=1)
@variable(model, 0<=y[i=2:4,j=2*i]<=1)

@constraint(model, [i=1:2, j=2:4], x[i,j] + y[j,2*j] >= 0.5)

@objective(model, Min, sum(x[i,j] for i in 1:3, j in 1:5) + sum(y[i,2*i] for i in 2:4))

optimize!(model)

Now, I want to check whether the values x and y take are binary or not (within some tolerance).
To check that, I do:

function is_var_value_0_or_1(var; tol=1e-5)
    for (n,k) in eachindex(var)
        if !isapprox(var[n,k], 0.0, atol = tol) && !isapprox(var[n,k], 1.0, atol = tol)
            return false
        end
    end
    return true
end

For x:

is_var_value_0_or_1(value.(x)) # Should return true
# Throws the following error:
# ERROR: BoundsError: attempt to access Int64 at index [2]

For y, it works fine:

is_var_value_0_or_1(value.(y))
# Returns false

I want to write a generic function is_var_value_0_or_1 (I know it’s terrible naming!) that takes in the data structure containing the variable values (and figures out its dimensions) and returns true if they are either 0 or 1, otherwise false.

1 Like

You’re approaching this from the wrong direction. Instead of writing a function that works over a container of JuMP variables, write a function that works for a single variable, then broadcast over that. So something like (I didn’t run the code, so there might by typos, etc):

function is_var_value_0_or_1(x::Real; kwargs...)
    return isapprox(x, 0; kwargs...) || isapprox(x, 1; kwargs...)
end

all(is_var_value_0_or_1.(value.(x); atol = 1e-5))

function is_var_value_0_or_1(x::VariableRef; kwargs...)
    return is_var_value_0_or_1(value(x); kwargs...)
end

all(is_var_value_0_or_1.(x; atol = 1e-5))

function is_var_value_0_or_1(x::AbstractArray{VariableRef}; kwargs...)
    return all(is_var_value_0_or_1.(x; kwargs...))
end

is_var_value_0_or_1(x; atol = 1e-5)
2 Likes

Wow. I was making it unnecessarily complicated. Thanks for pointing me in the right direction! :slight_smile:

2 Likes