Skip to content

Commit 04735d2

Browse files
authored
Merge pull request #2070 from JuliaOpt/bl/@container
Create containers with map instead of for loops
2 parents 1a4f663 + 1192fd8 commit 04735d2

29 files changed

+1030
-468
lines changed

docs/src/constraints.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ following.
251251
One way of adding a group of constraints compactly is the following:
252252
```jldoctest constraint_arrays; setup=:(model=Model(); @variable(model, x))
253253
julia> @constraint(model, con[i = 1:3], i * x <= i + 1)
254-
3-element Array{ConstraintRef{Model,C,Shape} where Shape<:AbstractShape where C,1}:
254+
3-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},1}:
255255
con[1] : x <= 2.0
256256
con[2] : 2 x <= 3.0
257257
con[3] : 3 x <= 4.0
@@ -264,7 +264,7 @@ julia> con[1]
264264
con[1] : x <= 2.0
265265
266266
julia> con[2:3]
267-
2-element Array{ConstraintRef{Model,C,Shape} where Shape<:AbstractShape where C,1}:
267+
2-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},1}:
268268
con[2] : 2 x <= 3.0
269269
con[3] : 3 x <= 4.0
270270
```
@@ -273,7 +273,7 @@ Anonymous containers can also be constructed by dropping the name (e.g. `con`)
273273
before the square brackets:
274274
```jldoctest constraint_arrays
275275
julia> @constraint(model, [i = 1:2], i * x <= i + 1)
276-
2-element Array{ConstraintRef{Model,C,Shape} where Shape<:AbstractShape where C,1}:
276+
2-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},1}:
277277
x <= 2.0
278278
2 x <= 3.0
279279
```
@@ -294,10 +294,10 @@ variables.
294294

295295
```jldoctest constraint_jumparrays; setup=:(model=Model(); @variable(model, x))
296296
julia> @constraint(model, con[i = 1:2, j = 2:3], i * x <= j + 1)
297-
2-dimensional DenseAxisArray{ConstraintRef{Model,C,Shape} where Shape<:AbstractShape where C,2,...} with index sets:
298-
Dimension 1, 1:2
297+
2-dimensional DenseAxisArray{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},2,...} with index sets:
298+
Dimension 1, Base.OneTo(2)
299299
Dimension 2, 2:3
300-
And data, a 2×2 Array{ConstraintRef{Model,C,Shape} where Shape<:AbstractShape where C,2}:
300+
And data, a 2×2 Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},2}:
301301
con[1,2] : x <= 3.0 con[1,3] : x <= 4.0
302302
con[2,2] : 2 x <= 3.0 con[2,3] : 2 x <= 4.0
303303
```
@@ -311,7 +311,7 @@ similar to the [syntax for constructing](@ref variable_sparseaxisarrays) a
311311

312312
```jldoctest constraint_jumparrays; setup=:(model=Model(); @variable(model, x))
313313
julia> @constraint(model, con[i = 1:2, j = 1:2; i != j], i * x <= j + 1)
314-
JuMP.Containers.SparseAxisArray{ConstraintRef{Model,C,Shape} where Shape<:AbstractShape where C,2,Tuple{Any,Any}} with 2 entries:
314+
JuMP.Containers.SparseAxisArray{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},2,Tuple{Int64,Int64}} with 2 entries:
315315
[1, 2] = con[1,2] : x <= 3.0
316316
[2, 1] = con[2,1] : 2 x <= 2.0
317317
```

docs/src/containers.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,15 @@ JuMP.Containers.SparseAxisArray
2121
Containers in macros
2222
--------------------
2323

24-
The `generate_container` function encodes the logic for how containers are
25-
constructed in JuMP's macros.
24+
The `container` function encodes the logic for how containers are
25+
constructed in JuMP's macros. The `@container` macro is available to create
26+
containers independently of any JuMP model.
2627
```@docs
27-
JuMP.Containers.generate_container
28+
JuMP.Containers.container
29+
JuMP.Containers.default_container
30+
JuMP.Containers.VectorizedProductIterator
31+
JuMP.Containers.NestedIterator
32+
JuMP.Containers.@container
2833
```
2934

3035
In the [`@variable`](@ref) (resp. [`@constraint`](@ref)) macro, containers of
@@ -44,12 +49,12 @@ Each expression `index_set_i` can either be
4449
keyword arguments to be expressions depending on the `index_name`.
4550

4651
The macro then creates the container using the
47-
[`JuMP.Containers.generate_container`](@ref) function with the following
52+
[`JuMP.Containers.container`](@ref) function with the following
4853
arguments:
4954

50-
1. `VariableRef` for the [`@variable`](@ref) macro and `ConstraintRef` for the
51-
[`@constraint`](@ref) macro.
52-
2. The index variables and arbitrary symbols for dimensions for which no
53-
variable index is specified.
54-
3. The index sets specified.
55-
4. The value of the `keyword` argument if given or `:Auto`.
55+
1. A function taking as argument the value of the indices and returning the
56+
value to be stored in the container, e.g. a variable for the
57+
[`@variable`](@ref) macro and a constraint for the [`@constraint`](@ref)
58+
macro.
59+
2. An iterator over the indices of the container.
60+
4. The value of the `container` keyword argument if given.

docs/src/variables.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ return a `DenseAxisArray`. For example:
324324
```jldoctest variables_jump_arrays; setup=:(model=Model())
325325
julia> @variable(model, x[1:2, [:A,:B]])
326326
2-dimensional DenseAxisArray{VariableRef,2,...} with index sets:
327-
Dimension 1, 1:2
327+
Dimension 1, Base.OneTo(2)
328328
Dimension 2, Symbol[:A, :B]
329329
And data, a 2×2 Array{VariableRef,2}:
330330
x[1,A] x[1,B]
@@ -371,7 +371,7 @@ For example, this applies when indices have a dependence upon previous
371371
indices (called *triangular indexing*). JuMP supports this as follows:
372372
```jldoctest; setup=:(model=Model())
373373
julia> @variable(model, x[i=1:2, j=i:2])
374-
JuMP.Containers.SparseAxisArray{VariableRef,2,Tuple{Any,Any}} with 3 entries:
374+
JuMP.Containers.SparseAxisArray{VariableRef,2,Tuple{Int64,Int64}} with 3 entries:
375375
[1, 2] = x[1,2]
376376
[2, 2] = x[2,2]
377377
[1, 1] = x[1,1]
@@ -382,7 +382,7 @@ syntax appends a comparison check that depends upon the named indices and is
382382
separated from the indices by a semi-colon (`;`). For example:
383383
```jldoctest; setup=:(model=Model())
384384
julia> @variable(model, x[i=1:4; mod(i, 2)==0])
385-
JuMP.Containers.SparseAxisArray{VariableRef,1,Tuple{Any}} with 2 entries:
385+
JuMP.Containers.SparseAxisArray{VariableRef,1,Tuple{Int64}} with 2 entries:
386386
[4] = x[4]
387387
[2] = x[2]
388388
```

src/Containers/Containers.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,10 @@ export DenseAxisArray, SparseAxisArray
2626
include("DenseAxisArray.jl")
2727
include("SparseAxisArray.jl")
2828
include("generate_container.jl")
29+
include("vectorized_product_iterator.jl")
30+
include("nested_iterator.jl")
31+
include("no_duplicate_dict.jl")
32+
include("container.jl")
33+
include("macro.jl")
2934

3035
end

src/Containers/container.jl

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors
2+
# This Source Code Form is subject to the terms of the Mozilla Public
3+
# License, v. 2.0. If a copy of the MPL was not distributed with this
4+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5+
6+
"""
7+
default_container(indices)
8+
9+
If `indices` is a [`NestedIterator`](@ref), return a
10+
[`SparseAxisArray`](@ref). Otherwise, `indices` should be
11+
a `VectorizedProductIterator` and the function returns
12+
`Array` if all iterators of the product are `Base.OneTo` and retunrs
13+
[`DenseAxisArray`](@ref) otherwise.
14+
"""
15+
function default_container end
16+
17+
"""
18+
container(f::Function, indices, ::Type{C})
19+
20+
Create a container of type `C` with indices `indices` and values at given
21+
indices given by `f`.
22+
23+
container(f::Function, indices)
24+
25+
Create a container with indices `indices` and values at given indices given by
26+
`f`. The type of container used is determined by [`default_container`](@ref).
27+
28+
## Examples
29+
30+
```@jldoctest
31+
julia> Containers.container((i, j) -> i + j, Containers.vectorized_product(Base.OneTo(3), Base.OneTo(3)))
32+
3×3 Array{Int64,2}:
33+
2 3 4
34+
3 4 5
35+
4 5 6
36+
37+
julia> Containers.container((i, j) -> i + j, Containers.vectorized_product(1:3, 1:3))
38+
2-dimensional DenseAxisArray{Int64,2,...} with index sets:
39+
Dimension 1, 1:3
40+
Dimension 2, 1:3
41+
And data, a 3×3 Array{Int64,2}:
42+
2 3 4
43+
3 4 5
44+
4 5 6
45+
46+
julia> Containers.container((i, j) -> i + j, Containers.vectorized_product(2:3, Base.OneTo(3)))
47+
2-dimensional DenseAxisArray{Int64,2,...} with index sets:
48+
Dimension 1, 2:3
49+
Dimension 2, Base.OneTo(3)
50+
And data, a 2×3 Array{Int64,2}:
51+
3 4 5
52+
4 5 6
53+
54+
julia> Containers.container((i, j) -> i + j, Containers.nested(() -> 1:3, i -> i:3, condition = (i, j) -> isodd(i) || isodd(j)))
55+
SparseAxisArray{Int64,2,Tuple{Int64,Int64}} with 5 entries:
56+
[1, 2] = 3
57+
[2, 3] = 5
58+
[3, 3] = 6
59+
[1, 1] = 2
60+
[1, 3] = 4
61+
```
62+
"""
63+
function container end
64+
65+
container(f::Function, indices) = container(f, indices, default_container(indices))
66+
67+
const ArrayIndices{N} = VectorizedProductIterator{NTuple{N, Base.OneTo{Int}}}
68+
default_container(::ArrayIndices) = Array
69+
function container(f::Function, indices::ArrayIndices, ::Type{Array})
70+
return map(I -> f(I...), indices)
71+
end
72+
function _oneto(indices)
73+
if indices isa UnitRange{Int} && indices == 1:length(indices)
74+
return Base.OneTo(length(indices))
75+
end
76+
error("Index set for array is not one-based interval.")
77+
end
78+
function container(f::Function, indices::VectorizedProductIterator,
79+
::Type{Array})
80+
container(f, vectorized_product(_oneto.(indices.prod.iterators)...), Array)
81+
end
82+
default_container(::VectorizedProductIterator) = DenseAxisArray
83+
function container(f::Function, indices::VectorizedProductIterator,
84+
::Type{DenseAxisArray})
85+
return DenseAxisArray(map(I -> f(I...), indices), indices.prod.iterators...)
86+
end
87+
default_container(::NestedIterator) = SparseAxisArray
88+
function container(f::Function, indices,
89+
::Type{SparseAxisArray})
90+
# Same as `map` but does not allocate the resulting vector.
91+
mappings = Base.Generator(I -> I => f(I...), indices)
92+
# Same as `Dict(mapping)` but it will error if two indices are the same.
93+
data = NoDuplicateDict(mappings)
94+
return SparseAxisArray(data.dict)
95+
end

0 commit comments

Comments
 (0)