Containers

Containers

JuMP provides a specialized container similar to AxisArrays that enables indexing with non-integer indices. Normally these are created automatically by JuMP's macros. The following constructors can be used to create them manually.

DenseAxisArray(data::Array{T, N}, axes...) where {T, N}

Construct a JuMP array with the underlying data specified by the data array and the given axes. Exactly N axes must be provided, and their lengths must match size(data) in the corresponding dimensions.

Example

julia> array = JuMP.Containers.DenseAxisArray([1 2; 3 4], [:a, :b], 2:3)
2-dimensional DenseAxisArray{Int64,2,...} with index sets:
    Dimension 1, Symbol[:a, :b]
    Dimension 2, 2:3
And data, a 2×2 Array{Int64,2}:
 1  2
 3  4

julia> array[:b, 3]
4
DenseAxisArray{T}(undef, axes...) where T

Construct an uninitialized DenseAxisArray with element-type T indexed over the given axes.

Example

julia> array = JuMP.Containers.DenseAxisArray{Float64}(undef, [:a, :b], 1:2);

julia> fill!(array, 1.0)
2-dimensional DenseAxisArray{Float64,2,...} with index sets:
    Dimension 1, Symbol[:a, :b]
    Dimension 2, 1:2
And data, a 2×2 Array{Float64,2}:
 1.0  1.0
 1.0  1.0

julia> array[:a, 2] = 5.0
5.0

julia> array[:a, 2]
5.0

julia> array
2-dimensional DenseAxisArray{Float64,2,...} with index sets:
    Dimension 1, Symbol[:a, :b]
    Dimension 2, 1:2
And data, a 2×2 Array{Float64,2}:
 1.0  5.0
 1.0  1.0
struct SparseAxisArray{T,N,K<:NTuple{N, Any}} <: AbstractArray{T,N}
    data::Dict{K,T}
end

N-dimensional array with elements of type T where only a subset of the entries are defined. The entries with indices idx = (i1, i2, ..., iN) in keys(data) has value data[idx]. Note that as opposed to SparseArrays.AbstractSparseArray, the missing entries are not assumed to be zero(T), they are simply not part of the array. This means that the result of map(f, sa::SparseAxisArray) or f.(sa::SparseAxisArray) has the same sparsity structure than sa even if f(zero(T)) is not zero.

Example

julia> dict = Dict((:a, 2) => 1.0, (:a, 3) => 2.0, (:b, 3) => 3.0)
Dict{Tuple{Symbol,Int64},Float64} with 3 entries:
  (:b, 3) => 3.0
  (:a, 2) => 1.0
  (:a, 3) => 2.0

julia> array = JuMP.Containers.SparseAxisArray(dict)
JuMP.Containers.SparseAxisArray{Float64,2,Tuple{Symbol,Int64}} with 3 entries:
  [b, 3]  =  3.0
  [a, 2]  =  1.0
  [a, 3]  =  2.0

julia> array[:b, 3]
3.0

Containers in macros

The container function encodes the logic for how containers are constructed in JuMP's macros. The @container macro is available to create containers independently of any JuMP model.

container(f::Function, indices, ::Type{C})

Create a container of type C with indices indices and values at given indices given by f.

container(f::Function, indices)

Create a container with indices indices and values at given indices given by f. The type of container used is determined by default_container.

Examples

julia> Containers.container((i, j) -> i + j, Containers.vectorized_product(Base.OneTo(3), Base.OneTo(3)))
3×3 Array{Int64,2}:
 2  3  4
 3  4  5
 4  5  6

julia> Containers.container((i, j) -> i + j, Containers.vectorized_product(1:3, 1:3))
2-dimensional DenseAxisArray{Int64,2,...} with index sets:
    Dimension 1, 1:3
    Dimension 2, 1:3
And data, a 3×3 Array{Int64,2}:
 2  3  4
 3  4  5
 4  5  6

julia> Containers.container((i, j) -> i + j, Containers.vectorized_product(2:3, Base.OneTo(3)))
2-dimensional DenseAxisArray{Int64,2,...} with index sets:
    Dimension 1, 2:3
    Dimension 2, Base.OneTo(3)
And data, a 2×3 Array{Int64,2}:
 3  4  5
 4  5  6

julia> Containers.container((i, j) -> i + j, Containers.nested(() -> 1:3, i -> i:3, condition = (i, j) -> isodd(i) || isodd(j)))
SparseAxisArray{Int64,2,Tuple{Int64,Int64}} with 5 entries:
  [1, 2]  =  3
  [2, 3]  =  5
  [3, 3]  =  6
  [1, 1]  =  2
  [1, 3]  =  4
default_container(indices)

If indices is a NestedIterator, return a SparseAxisArray. Otherwise, indices should be a VectorizedProductIterator and the function returns Array if all iterators of the product are Base.OneTo and retunrs DenseAxisArray otherwise.

struct VectorizedProductIterator{T}
    prod::Iterators.ProductIterator{T}
end

Cartesian product of the iterators prod.iterators. It is the same iterator as Base.Iterators.ProductIterator except that it is independent of the IteratorSize of the elements of prod.iterators. For instance:

  • size(Iterators.product(1, 2)) is tuple() while size(VectorizedProductIterator(1, 2)) is (1, 1).
  • size(Iterators.product(ones(2, 3))) is (2, 3) while size(VectorizedProductIterator(ones(2, 3))) is (1, 1).
struct NestedIterator{T}
    iterators::T # Tuple of functions
    condition::Function
end

Iterators over the tuples that are produced by a nested for loop. For instance, if length(iterators) == 3 , this corresponds to the tuples (i1, i2, i3) produced by:

for i1 in iterators[1]()
    for i2 in iterator[2](i1)
        for i3 in iterator[3](i1, i2)
            if condition(i1, i2, i3)
                # produces (i1, i2, i3)
            end
        end
    end
end
@container([i=..., j=..., ...], expr)

Create a container with indices i, j, ... and values given by expr that may depend on the value of the indices.

@container(ref[i=..., j=..., ...], expr)

Same as above but the container is assigned to the variable of name ref.

The type of container can be controlled by the container keyword. See Containers in macros. Note that when the index set is explicitly given as 1:n for any expression n, it is transformed to Base.OneTo(n) before being given to container.

Examples

julia> Containers.@container([i = 1:3, j = 1:3], i + j)
3×3 Array{Int64,2}:
 2  3  4
 3  4  5
 4  5  6

julia> I = 1:3
1:3

julia> Containers.@container([i = I, j = I], i + j)
2-dimensional DenseAxisArray{Int64,2,...} with index sets:
    Dimension 1, 1:3
    Dimension 2, 1:3
And data, a 3×3 Array{Int64,2}:
 2  3  4
 3  4  5
 4  5  6

julia> Containers.@container([i = 2:3, j = 1:3], i + j)
2-dimensional DenseAxisArray{Int64,2,...} with index sets:
    Dimension 1, 2:3
    Dimension 2, Base.OneTo(3)
And data, a 2×3 Array{Int64,2}:
 3  4  5
 4  5  6

julia> Containers.@container([i = 1:3, j = 1:3; i <= j], i + j)
JuMP.Containers.SparseAxisArray{Int64,2,Tuple{Int64,Int64}} with 6 entries:
  [1, 2]  =  3
  [2, 3]  =  5
  [3, 3]  =  6
  [2, 2]  =  4
  [1, 1]  =  2
  [1, 3]  =  4

In the @variable (resp. @constraint) macro, containers of variables (resp. constraints) can be created with the following syntax:

Each expression index_set_i can either be

The macro then creates the container using the JuMP.Containers.container function with the following arguments:

  1. A function taking as argument the value of the indices and returning the value to be stored in the container, e.g. a variable for the @variable macro and a constraint for the @constraint macro.
  2. An iterator over the indices of the container.
  3. The value of the container keyword argument if given.