Контейнеры
JuMP предоставляет специальные контейнеры, похожие на AxisArrays
. Они позволяют создавать многомерные массивы с нецелочисленными индексами.
Эти контейнеры автоматически создаются макросами JuMP. У всех макросов одинаковый базовый синтаксис:
@macroname(model, name[key1=index1, index2; optional_condition], other stuff)
Контейнеры создаются посредством синтаксиса name[key1=index1, index2; optional_condition]
. Остальные особенности зависят от конкретного макроса.
Контейнеры могут быть именованными, например name[key=index]
, или неименованными, например [key=index]
. Неименованные контейнеры называются анонимными.
Элементы в квадратных скобках перед символом ;
называются множествами индексов. Множества индексов могут быть именованными, например [i = 1:4]
, или неименованными, например [1:4]
.
Элемент в квадратных скобках после символа ;
называется условием. Условия необязательны.
Помимо стандартных макросов JuMP, таких как @variable
и @constraint
, которые создают соответственно контейнеры переменных и ограничений, можно создавать контейнеры с произвольными элементами с помощью макроса Containers.@container
.
Мы используем этот макрос для объяснения трех типов контейнеров, которые изначально поддерживаются JuMP: Array
, Containers.DenseAxisArray
и Containers.SparseAxisArray
.
Array
Массив Array
создается, когда множества индексов являются прямоугольными и имеют форму 1:n
.
julia> Containers.@container(x[i = 1:2, j = 1:3], (i, j))
2×3 Matrix{Tuple{Int64, Int64}}:
(1, 1) (1, 2) (1, 3)
(2, 1) (2, 2) (2, 3)
В результате получается обычный массив Julia Array
, с которым можно выполнять стандартные действия.
Срезы
Можно создавать срезы массивов.
julia> x[:, 1]
2-element Vector{Tuple{Int64, Int64}}:
(1, 1)
(2, 1)
julia> x[2, :]
3-element Vector{Tuple{Int64, Int64}}:
(2, 1)
(2, 2)
(2, 3)
Циклы
Для перебора элементов используйте eachindex
:
julia> for key in eachindex(x)
println(x[key])
end
(1, 1)
(2, 1)
(1, 2)
(2, 2)
(1, 3)
(2, 3)
Получение множеств индексов
Для получения множеств индексов используйте axes
:
julia> axes(x)
(Base.OneTo(2), Base.OneTo(3))
Трансляция
При трансляции через массив Array возвращается Array.
julia> swap(x::Tuple) = (last(x), first(x))
swap (generic function with 1 method)
julia> swap.(x)
2×3 Matrix{Tuple{Int64, Int64}}:
(1, 1) (2, 1) (3, 1)
(1, 2) (2, 2) (3, 2)
Таблицы
Чтобы преобразовать массив Array
в вектор Vector{<:NamedTuple}
, совместимый с Tables.jl, используйте метод Containers.rowtable
:
julia> table = Containers.rowtable(x; header = [:I, :J, :value])
6-element Vector{@NamedTuple{I::Int64, J::Int64, value::Tuple{Int64, Int64}}}:
(I = 1, J = 1, value = (1, 1))
(I = 2, J = 1, value = (2, 1))
(I = 1, J = 2, value = (1, 2))
(I = 2, J = 2, value = (2, 2))
(I = 1, J = 3, value = (1, 3))
(I = 2, J = 3, value = (2, 3))
Так как этот объект поддерживает интерфейс Tables.jl, его можно передать в любую функцию, принимающую таблицу в качестве входных данных:
julia> import DataFrames;
julia> DataFrames.DataFrame(table)
6×3 DataFrame
Row │ I J value
│ Int64 Int64 Tuple…
─────┼──────────────────────
1 │ 1 1 (1, 1)
2 │ 2 1 (2, 1)
3 │ 1 2 (1, 2)
4 │ 2 2 (2, 2)
5 │ 1 3 (1, 3)
6 │ 2 3 (2, 3)
DenseAxisArray
Массив Containers.DenseAxisArray
создается, когда множества индексов являются прямоугольными, но не имеют форму 1:n
. Множества индексов могут быть любого типа.
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, [:A, :B]
And data, a 2×2 Matrix{Tuple{Int64, Symbol}}:
(1, :A) (1, :B)
(2, :A) (2, :B)
Срезы
Можно создавать срезы массивов DenseAxisArray.
julia> x[:, :A]
1-dimensional DenseAxisArray{Tuple{Int64, Symbol},1,...} with index sets:
Dimension 1, Base.OneTo(2)
And data, a 2-element Vector{Tuple{Int64, Symbol}}:
(1, :A)
(2, :A)
julia> x[1, :]
1-dimensional DenseAxisArray{Tuple{Int64, Symbol},1,...} with index sets:
Dimension 1, [:A, :B]
And data, a 2-element Vector{Tuple{Int64, Symbol}}:
(1, :A)
(1, :B)
Циклы
Для перебора элементов используйте eachindex
:
julia> for key in eachindex(x)
println(x[key])
end
(1, :A)
(2, :A)
(1, :B)
(2, :B)
Получение множеств индексов
Для получения множеств индексов используйте axes
:
julia> axes(x)
(Base.OneTo(2), [:A, :B])
Трансляция
При трансляции через массив DenseAxisArray возвращается 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, [:A, :B]
And data, a 2×2 Matrix{Tuple{Symbol, Int64}}:
(:A, 1) (:B, 1)
(:A, 2) (:B, 2)
Доступ к внутренним данным
Для копирования внутренних данных в новый массив Array
используйте Array(x)
:
julia> Array(x)
2×2 Matrix{Tuple{Int64, Symbol}}:
(1, :A) (1, :B)
(2, :A) (2, :B)
Для доступа к внутренним данным без копирования используйте x.data
.
julia> x.data
2×2 Matrix{Tuple{Int64, Symbol}}:
(1, :A) (1, :B)
(2, :A) (2, :B)
Таблицы
Чтобы преобразовать массив DenseAxisArray
в вектор Vector{<:NamedTuple}
, совместимый с Tables.jl, используйте метод Containers.rowtable
:
julia> table = Containers.rowtable(x; header = [:I, :J, :value])
4-element Vector{@NamedTuple{I::Int64, J::Symbol, value::Tuple{Int64, Symbol}}}:
(I = 1, J = :A, value = (1, :A))
(I = 2, J = :A, value = (2, :A))
(I = 1, J = :B, value = (1, :B))
(I = 2, J = :B, value = (2, :B))
Так как этот объект поддерживает интерфейс Tables.jl, его можно передать в любую функцию, принимающую таблицу в качестве входных данных:
julia> import DataFrames;
julia> DataFrames.DataFrame(table)
4×3 DataFrame
Row │ I J value
│ Int64 Symbol Tuple…
─────┼────────────────────────
1 │ 1 A (1, :A)
2 │ 2 A (2, :A)
3 │ 1 B (1, :B)
4 │ 2 B (2, :B)
Индексирование по ключевым словам
Если у всех осей есть имена, можно использовать индексирование по ключевым словам:
julia> x[i = 2, j = :A]
(2, :A)
julia> x[i = :, j = :B]
1-dimensional DenseAxisArray{Tuple{Int64, Symbol},1,...} with index sets:
Dimension 1, Base.OneTo(2)
And data, a 2-element Vector{Tuple{Int64, Symbol}}:
(1, :B)
(2, :B)
SparseAxisArray
Массив Containers.SparseAxisArray
создается, когда множества индексов не являются прямоугольными. Это происходит в двух ситуациях:
Индекс зависит от предыдущего индекса:
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, 1] = (1, 1)
[1, 2] = (1, 2)
[2, 2] = (2, 2)
Применяется синтаксис [indices; condition]
:
julia> x = Containers.@container([i = 1:3, j = [:A, :B]; i > 1], (i, j))
JuMP.Containers.SparseAxisArray{Tuple{Int64, Symbol}, 2, Tuple{Int64, Symbol}} with 4 entries:
[2, A] = (2, :A)
[2, B] = (2, :B)
[3, A] = (3, :A)
[3, B] = (3, :B)
Здесь имеются множества индексов i = 1:3, j = [:A, :B]
, за которыми следует символ ;
и условие со значением true
или false
: i > 1
.
Срезы
Поддерживается создание срезов:
julia> y = x[:, :B]
JuMP.Containers.SparseAxisArray{Tuple{Int64, Symbol}, 1, Tuple{Int64}} with 2 entries:
[2] = (2, :B)
[3] = (3, :B)
Циклы
Для перебора элементов используйте eachindex
:
julia> for key in eachindex(x)
println(x[key])
end
(2, :A)
(2, :B)
(3, :A)
(3, :B)
julia> for key in eachindex(y)
println(y[key])
end
(2, :B)
(3, :B)
Если |
Трансляция
При трансляции через массив SparseAxisArray возвращается SparseAxisArray.
julia> swap(x::Tuple) = (last(x), first(x))
swap (generic function with 1 method)
julia> swap.(y)
JuMP.Containers.SparseAxisArray{Tuple{Symbol, Int64}, 1, Tuple{Int64}} with 2 entries:
[2] = (:B, 2)
[3] = (:B, 3)
Таблицы
Чтобы преобразовать массив SparseAxisArray
в вектор Vector{<:NamedTuple}
, совместимый с Tables.jl, используйте метод Containers.rowtable
:
julia> table = Containers.rowtable(x; header = [:I, :J, :value])
4-element Vector{@NamedTuple{I::Int64, J::Symbol, value::Tuple{Int64, Symbol}}}:
(I = 2, J = :A, value = (2, :A))
(I = 2, J = :B, value = (2, :B))
(I = 3, J = :A, value = (3, :A))
(I = 3, J = :B, value = (3, :B))
Так как этот объект поддерживает интерфейс Tables.jl, его можно передать в любую функцию, принимающую таблицу в качестве входных данных:
julia> import DataFrames;
julia> DataFrames.DataFrame(table)
4×3 DataFrame
Row │ I J value
│ Int64 Symbol Tuple…
─────┼────────────────────────
1 │ 2 A (2, :A)
2 │ 2 B (2, :B)
3 │ 3 A (3, :A)
4 │ 3 B (3, :B)
Принудительное задание типа контейнера
Чтобы использовать тип T
для контейнера, передайте container = T
. Например:
julia> Containers.@container([i = 1:2, j = 1:2], i + j, container = Array)
2×2 Matrix{Int64}:
2 3
3 4
julia> Containers.@container([i = 1:2, j = 1:2], i + j, container = Dict)
Dict{Tuple{Int64, Int64}, Int64} with 4 entries:
(1, 2) => 3
(1, 1) => 2
(2, 2) => 4
(2, 1) => 3
Можно также передать DenseAxisArray
или SparseAxisArray
.
Как выбираются различные типы контейнеров
Если компилятор может определить во время компиляции, что множества индексов являются прямоугольными компактными множествами целых чисел, начинающимися с 1
, Containers.@container
возвращает массив. Это верно в случае, если множества индексов идентифицируются макросом как 1:n
:
julia> Containers.@container([i=1:3, j=1:5], i + j)
3×5 Matrix{Int64}:
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
или как экземпляр Base.OneTo
:
julia> set = Base.OneTo(3)
Base.OneTo(3)
julia> Containers.@container([i=set, j=1:5], i + j)
3×5 Matrix{Int64}:
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
Если компилятор определяет во время компиляции, что множество индексов является прямоугольным, но не обязательно имеет форму 1:n
, то вместо этого будет создан массив Containers.DenseAxisArray
:
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 Matrix{Int64}:
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
Что произошло здесь? Хотя мы знаем, что |
Однако если известно, что индексы образуют массив Array
, можно принудительно задать тип контейнера с помощью container = Array
:
julia> set = 1:3
1:3
julia> Containers.@container([i=set, j=1:5], i + j, container = Array)
3×5 Matrix{Int64}:
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
Вот еще один похожий пример:
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 Matrix{Int64}:
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 Matrix{Int64}:
2 3 4 5 6
Наконец, если компилятор не может установить, что множество индексов является прямоугольным, создается массив Containers.SparseAxisArray
.
Это происходит, если некоторые индексы зависят от предыдущего индекса:
julia> Containers.@container([i=1:3, j=1:i], i + j)
JuMP.Containers.SparseAxisArray{Int64, 2, Tuple{Int64, Int64}} with 6 entries:
[1, 1] = 2
[2, 1] = 3
[2, 2] = 4
[3, 1] = 4
[3, 2] = 5
[3, 3] = 6
или если во множествах индексов есть условие:
julia> Containers.@container([i = 1:5; isodd(i)], i^2)
JuMP.Containers.SparseAxisArray{Int64, 1, Tuple{Int64}} with 3 entries:
[1] = 1
[3] = 9
[5] = 25
Условие может зависеть от нескольких индексов; единственным требованием является то, чтобы это было выражение, возвращающее true
или 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