# Containers

JuMP provides specialized containers similar to `AxisArrays`

that enable multi-dimensional arrays with non-integer indices.

These containers are created automatically by JuMP's macros. Each macro has the same basic syntax:

`@macroname(model, name[key1=index1, index2; optional_condition], other stuff)`

The containers are generated by the `name[key1=index1, index2; optional_condition]`

syntax. Everything else is specific to the particular macro.

Containers can be named, e.g., `name[key=index]`

, or unnamed, e.g., `[key=index]`

. We call unnamed containers *anonymous*.

We call the bits inside the square brackets and before the `;`

the *index sets*. The index sets can be named, e.g., `[i = 1:4]`

, or they can be unnamed, e.g., `[1:4]`

.

We call the bit inside the square brackets and after the `;`

the *condition*. Conditions are optional.

In addition to the standard JuMP macros like `@variable`

and `@constraint`

, which construct containers of variables and constraints respectively, you can use `Containers.@container`

to construct containers with arbitrary elements.

We will use this macro to explain the three types of containers that are natively supported by JuMP: `Array`

, `Containers.DenseAxisArray`

, and `Containers.SparseAxisArray`

.

## Array

An `Array`

is created when the index sets are rectangular and the index sets are of the form `1:n`

.

```
julia> Containers.@container(x[i = 1:2, j = 1:3], (i, j))
2×3 Array{Tuple{Int64,Int64},2}:
(1, 1) (1, 2) (1, 3)
(2, 1) (2, 2) (2, 3)
```

The result is just a normal Julia array, so you can do all the usual things.

### Slicing

Arrays can be sliced

```
julia> x[:, 1]
2-element Array{Tuple{Int64,Int64},1}:
(1, 1)
(2, 1)
julia> x[2, :]
3-element Array{Tuple{Int64,Int64},1}:
(2, 1)
(2, 2)
(2, 3)
```

### Looping

Use `eachindex`

to loop over the elements:

```
julia> for key in eachindex(x)
println(x[key])
end
(1, 1)
(2, 1)
(1, 2)
(2, 2)
(1, 3)
(2, 3)
```

### Get the index sets

Use `axes`

to obtain the index sets:

```
julia> axes(x)
(Base.OneTo(2), Base.OneTo(3))
```

### Broadcasting

Broadcasting over an Array returns an Array

```
julia> swap(x::Tuple) = (last(x), first(x))
swap (generic function with 1 method)
julia> swap.(x)
2×3 Array{Tuple{Int64,Int64},2}:
(1, 1) (2, 1) (3, 1)
(1, 2) (2, 2) (3, 2)
```

## DenseAxisArray

A `Containers.DenseAxisArray`

is created when the index sets are rectangular, but not of the form `1:n`

. The index sets can be of any type.

```
julia> x = Containers.@container([i = 1:2, j = [:A, :B]], (i, j))
2-dimensional DenseAxisArray{Tuple{Int64,Symbol},2,...} with index sets:
Dimension 1, Base.OneTo(2)
Dimension 2, Symbol[:A, :B]
And data, a 2×2 Array{Tuple{Int64,Symbol},2}:
(1, :A) (1, :B)
(2, :A) (2, :B)
```

### Slicing

DenseAxisArrays can be sliced

```
julia> x[:, :A]
1-dimensional DenseAxisArray{Tuple{Int64,Symbol},1,...} with index sets:
Dimension 1, Base.OneTo(2)
And data, a 2-element Array{Tuple{Int64,Symbol},1}:
(1, :A)
(2, :A)
julia> x[1, :]
1-dimensional DenseAxisArray{Tuple{Int64,Symbol},1,...} with index sets:
Dimension 1, Symbol[:A, :B]
And data, a 2-element Array{Tuple{Int64,Symbol},1}:
(1, :A)
(1, :B)
```

### Looping

Use `eachindex`

to loop over the elements:

```
julia> for key in eachindex(x)
println(x[key])
end
(1, :A)
(2, :A)
(1, :B)
(2, :B)
```

### Get the index sets

Use `axes`

to obtain the index sets:

```
julia> axes(x)
(Base.OneTo(2), Symbol[:A, :B])
```

### Broadcasting

Broadcasting over a DenseAxisArray returns a DenseAxisArray

```
julia> swap(x::Tuple) = (last(x), first(x))
swap (generic function with 1 method)
julia> swap.(x)
2-dimensional DenseAxisArray{Tuple{Symbol,Int64},2,...} with index sets:
Dimension 1, Base.OneTo(2)
Dimension 2, Symbol[:A, :B]
And data, a 2×2 Array{Tuple{Symbol,Int64},2}:
(:A, 1) (:B, 1)
(:A, 2) (:B, 2)
```

## SparseAxisArray

A `Containers.SparseAxisArray`

is created when the index sets are non-rectangular. This occurs in two circumstances:

An index depends on a prior index:

```
julia> Containers.@container([i = 1:2, j = i:2], (i, j))
JuMP.Containers.SparseAxisArray{Tuple{Int64,Int64},2,Tuple{Int64,Int64}} with 3 entries:
[1, 2] = (1, 2)
[2, 2] = (2, 2)
[1, 1] = (1, 1)
```

The `[indices; condition]`

syntax is used:

```
julia> x = Containers.@container([i = 1:3, j = [:A, :B]; i > 1 && j == :B], (i, j))
JuMP.Containers.SparseAxisArray{Tuple{Int64,Symbol},2,Tuple{Int64,Symbol}} with 2 entries:
[2, B] = (2, :B)
[3, B] = (3, :B)
```

Here we have the index sets `i = 1:3, j = [:A, :B]`

, followed by `;`

, and then a condition, which evaluates to `true`

or `false`

: `i > 1 && j == :B`

.

### Slicing

Slicing is not supported.

```
julia> x[:, :B]
ERROR: ArgumentError: Indexing with `:` is not supported by Containers.SparseAxisArray
[...]
```

### Looping

Use `eachindex`

to loop over the elements:

```
julia> for key in eachindex(x)
println(x[key])
end
(2, :B)
(3, :B)
```

### Broadcasting

Broadcasting over a SparseAxisArray returns a SparseAxisArray

```
julia> swap(x::Tuple) = (last(x), first(x))
swap (generic function with 1 method)
julia> swap.(x)
JuMP.Containers.SparseAxisArray{Tuple{Symbol,Int64},2,Tuple{Int64,Symbol}} with 2 entries:
[2, B] = (:B, 2)
[3, B] = (:B, 3)
```

## How different container types are chosen

If the compiler can prove *at compile time* that the index sets are rectangular, and indexed by a compact set of integers that start at `1`

, `Containers.@container`

will return an array. This is the case if your index sets are visible to the macro as `1:n`

:

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

or an instance of `Base.OneTo`

:

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

If the compiler can prove that the index set is rectangular, but not necessarily of the form `1:n`

at compile time, then a `Containers.DenseAxisArray`

will be constructed instead:

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

What happened here? Although we know that `set`

contains `1:3`

, at compile time the `typeof(set)`

is a `UnitRange{Int}`

. Therefore, Julia can't prove that the range starts at `1`

(it only finds this out at runtime), and it defaults to a `DenseAxisArray`

. The case where we explicitly wrote `i = 1:3`

worked because the macro can "see" the `1`

at compile time.

However, if you know that the indices really do form an `Array`

, you can force the container type with `container = Array`

:

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

Here's another example with something similar:

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

Finally, if the compiler cannot prove that the index set is rectangular, a `Containers.SparseAxisArray`

will be created.

This occurs when some indices depend on a previous one:

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

or if there is a condition on the index sets:

```
julia> Containers.@container([i = 1:5; isodd(i)], i^2)
JuMP.Containers.SparseAxisArray{Int64,1,Tuple{Int64}} with 3 entries:
[3] = 9
[5] = 25
[1] = 1
```

The condition can depend on multiple indices; it just needs to be a function that returns `true`

or `false`

:

```
julia> condition(i, j) = isodd(i) && iseven(j)
condition (generic function with 1 method)
julia> Containers.@container([i = 1:2, j = 1:4; condition(i, j)], i + j)
JuMP.Containers.SparseAxisArray{Int64,2,Tuple{Int64,Int64}} with 2 entries:
[1, 2] = 3
[1, 4] = 5
```