Gridap.Arrays
Gridap.Arrays
— ModuleThis module provides:
- An extension of the
AbstractArray
interface in order to properly deal with mutable caches. - A mechanism to generate lazy arrays resulting from operations between arrays.
- A collection of concrete implementations of
AbstractArray
.
The exported names in this module are:
CachedArray
CachedMatrix
CachedVector
CompressedArray
Kernel
LocalToGlobalArray
LocalToGlobalPosNegArray
SubVector
Table
UNSET
append_ptrs
append_ptrs!
append_tables_globally
append_tables_locally
apply
apply_all
apply_kernel
apply_kernel!
apply_kernels!
array_cache
array_caches
bcast
collect1d
contract
elem
empty_table
find_inverse_index_map
find_inverse_index_map!
find_local_index
flatten_partition
generate_data_and_ptrs
get_array
get_data_eltype
get_local_item
get_ptrs_eltype
getindex!
getitems!
identity_table
identity_vector
kernel_cache
kernel_caches
kernel_return_type
kernel_return_types
length_to_ptrs!
reindex
rewind_ptrs!
setsize!
test_array
test_kernel
testitem
testitems
uses_hash
Extended AbstractArray interface
When implementing new array types, it can be needed some scratch data (e.g., allocating the output), when recovering an item from an array (typically if the array elements are non-isbits objects). To circumvent this, the user could provide the scratch data needed when getting an item. However, the Julia array interface does not support this approach. When calling a[i]
, in order to get the element with index i
in array a
, there is no extra argument for the scratch data. In order to solve this problem, we add new methods to the AbstractArray
interface of Julia. We provide default implementations to the new methods, so that any AbstractArray
can be used with the extended interface. New array implementations can overload these default implementations to improve performance. The most important among the new methods is getindex!
, which allows to recover an item in the array by passing some scratch data.
The new methods are:
getindex!(cache,a::AbstractArray,i...)
array_cache(a::AbstractArray)
uses_hash(::Type{<:AbstractArray})
testitem(a::AbstractArray)
These methods can be stressed with the following function
Gridap.Arrays.getindex!
— Methodgetindex!(cache,a::AbstractArray,i...)
Returns the item of the array a
associated with index i
by (possibly) using the scratch data passed in the cache
object.
It defaults to
getindex!(cache,a::AbstractArray,i...) = a[i...]
Examples
Iterating over an array using the getindex!
function
using Gridap.Arrays
a = collect(10:15)
cache = array_cache(a)
for i in eachindex(a)
ai = getindex!(cache,a,i)
println("$i -> $ai")
end
# output
1 -> 10
2 -> 11
3 -> 12
4 -> 13
5 -> 14
6 -> 15
Gridap.Arrays.array_cache
— Methodarray_cache(a::AbstractArray)
Returns a cache object to be used in the getindex!
function. It defaults to
array_cache(a::T) where T = nothing
for types T
such that uses_hash(T) == Val(false)
, and
function array_cache(a::T) where T
hash = Dict{UInt,Any}()
array_cache(hash,a)
end
for types T
such that uses_hash(T) == Val(true)
, see the uses_hash
function. In the later case, the type T
should implement the following signature:
array_cache(hash::Dict,a::AbstractArray)
where we pass a dictionary (i.e., a hash table) in the first argument. This hash table can be used to test if the object a
has already build a cache and re-use it as follows
id = objectid(a)
if haskey(hash,id)
cache = hash[id] # Reuse cache
else
cache = ... # Build a new cache depending on your needs
hash[id] = cache # Register the cache in the hash table
end
This mechanism is needed, e.g., to re-use intermediate results in complex lazy operation trees. In multi-threading computations, a different hash table per thread has to be used in order to avoid race conditions.
Gridap.Arrays.uses_hash
— Methoduses_hash(::Type{<:AbstractArray})
This function is used to specify if the type T
uses the hash-based mechanism to reuse caches. It should return either Val(true)
or Val(false)
. It defaults to
uses_hash(::Type{<:AbstractArray}) = Val(false)
Once this function is defined for the type T
it can also be called on instances of T
.
Gridap.Arrays.testitem
— Methodtestitem(a::AbstractArray{T,N} where N) -> Any
Returns an arbitrary instance of eltype(a)
. The default returned value is the first entry in the array if length(a)>0
and testvalue(eltype(a))
if length(a)==0
See the testvalue
function.
Examples
using Gridap.Arrays
a = collect(3:10)
ai = testitem(a)
b = Int[]
bi = testitem(b)
(ai, bi)
# output
(3, 0)
Gridap.Arrays.test_array
— Functiontest_array(
a::AbstractArray{T,N}, b::AbstractArray{S,N},cmp=(==)) where {T,S,N}
Checks if the entries in a
and b
are equal using the comparison function cmp
. It also stresses the new methods added to the AbstractArray
interface interface.
Working with several arrays at once
Gridap.Arrays.getitems!
— Functiongetitems!(c::Tuple,a::Tuple,i...) -> Tuple
Extracts the i
-th entry of all arrays in the tuple a
using the caches in the tuple c
. The results is a tuple containing each one of the extracted entries.
Example
Iterating over three different arrays simultaneously using getitems!
using Gridap.Arrays
a = collect(0:5)
b = collect(10:15)
c = collect(20:25)
caches = array_caches(a,b,c)
for i in eachindex(a)
s = getitems!(caches,(a,b,c),i)
println("$i -> $s")
end
# output
1 -> (0, 10, 20)
2 -> (1, 11, 21)
3 -> (2, 12, 22)
4 -> (3, 13, 23)
5 -> (4, 14, 24)
6 -> (5, 15, 25)
Gridap.Arrays.array_caches
— Functionarray_caches(a::AbstractArray...) -> Tuple
Returns a tuple with the cache of each array in a
.
Gridap.Arrays.testitems
— Functiontestitems(b::AbstractArray...) -> Tuple
Returns a tuple with the result of testitem
applied to each of the arrays in b
.
Examples
using Gridap.Arrays
a = collect(3:10)
b = Int[]
c = Float64[]
d = ones(10)
testitems(a,b,c,d)
# output
(3, 0, 0.0, 1.0)
Creting lazy operation trees
Gridap.Arrays.apply
— Methodapply(f,a::AbstractArray...) -> AbstractArray
Applies the kernel f
to the entries of the arrays in a
(see the definition of Kernel
).
The resulting array r
is such that r[i]
equals to apply_kernel(f,ai...)
where ai
is the tuple containing the i
-th entry of the arrays in a
(see function apply_kernel
for more details). In other words, the resulting array is numerically equivalent to:
map( (x...)->apply_kernel(f,x...), a...)
See the apply_kernel
function for details.
Examples
Using a function as kernel
using Gridap.Arrays
a = collect(0:5)
b = collect(10:15)
c = apply(+,a,b)
println(c)
# output
[10, 12, 14, 16, 18, 20]
Using a user-defined kernel
using Gridap.Arrays
import Gridap.Arrays: apply_kernel!
a = collect(0:5)
b = collect(10:15)
struct MySum <: Kernel end
apply_kernel!(cache,::MySum,x,y) = x + y
k = MySum()
c = apply(k,a,b)
println(c)
# output
[10, 12, 14, 16, 18, 20]
Gridap.Arrays.apply
— Methodapply(::Type{T},f,a::AbstractArray...) where T
Like apply(f,a::AbstractArray...)
, but the user provides the element type of the resulting array in order to circumvent type inference.
Gridap.Arrays.apply
— Methodapply(f::AbstractArray,a::AbstractArray...) -> AbstractArray
Applies the kernels in the array of kernels f
to the entries in the arrays in a
.
The resulting array has the same entries as the one obtained with:
map( apply_kernel, f, a...)
See the apply_kernel
function for details.
Example
"Evaluating" an array of functions
using Gridap.Arrays
f = [+,-,max,min]
a = [1,2,3,4]
b = [4,3,2,1]
c = apply(f,a,b)
println(c)
# output
[5, -1, 3, 1]
Gridap.Arrays.apply
— Methodapply(::Type{T},f::AbstractArray,a::AbstractArray...) where T
Like apply(f::AbstractArray,a::AbstractArray...)
, but the user provides the element type of the resulting array in order to circumvent type inference.
Gridap.Arrays.apply_all
— Functionapply_all(f::Tuple,a::AbstractArray...) -> Tuple
Numerically equivalent to
tuple( ( apply(fi, a...) for fi in f)... )
Examples
using Gridap.Arrays
a = [1,2,3,4]
b = [4,3,2,1]
c = apply_all( (+,-), a, b)
# Equivalent to
# c = ( apply(+,a,b), apply(-,a,b) )
println(c)
# output
([5, 5, 5, 5], [-3, -1, 1, 3])
Operation kernels
Gridap.Arrays.Kernel
— TypeAbstract type representing the operations to be used in the apply
function.
Derived types must implement the following method:
and optionally these ones:
The kernel interface can be tested with the test_kernel
function.
Note that most of the functionality implemented in terms of this interface relies in duck typing. That is, it is not strictly needed to work with types that inherit from Kernel
. This is specially useful in order to accommodate existing types into this framework without the need to implement a wrapper type that inherits from Kernel
. For instance, a default implementation is available for Function
objects. However, we recommend that new types inherit from Kernel
.
Gridap.Arrays.apply_kernel!
— Methodapply_kernel!(cache,f,x...)
Applies the kernel f
at the arguments x...
using the scratch data provided in the given cache
object. The cache
object is built with the kernel_cache
function using arguments of the same type as in x
. In general, the returned value y
can share some part of its state with the cache
object. If the result of two or more invocations of this function need to be accessed simultaneously (e.g., in multi-threading), create and use various cache
objects (e.g., one cache per thread).
Gridap.Arrays.kernel_cache
— Methodkernel_cache(f,x...)
Returns the cache
needed to apply kernel f
with arguments of the same type as the objects in x
. This function returns nothing
by default.
Gridap.Arrays.kernel_return_type
— Methodkernel_return_type(f,x...)
Returns the type of the result of calling kernel f
with arguments of the types of the objects x
.
It defaults to typeof(apply_kernel(f,x...))
Gridap.Arrays.test_kernel
— Functiontest_kernel(f,x::Tuple,y,cmp=(==))
Function used to test if the kernel f
has been implemented correctly. f
is a kernel object, x
is a tuple containing the arguments of the kernel, and y
is the expected result. Function cmp
is used to compare the computed result with the expected one. The checks are done with the @test
macro.
Other functions using kernels
Gridap.Arrays.apply_kernel
— Functionapply_kernel(f,x...)
apply the kernel f
at the arguments in x
by creating a temporary cache internally. This functions is equivalent to
cache = kernel_cache(f,x...)
apply_kernel!(cache,f,x...)
Gridap.Arrays.apply_kernels!
— Functionapply_kernels!(caches::Tuple,fs::Tuple,x...) -> Tuple
Applies the kernels in the tuple fs
at the arguments x...
by using the corresponding cache objects in the tuple caches
. The result is also a tuple containing the result for each kernel in fs
.
Gridap.Arrays.kernel_caches
— Functionkernel_caches(fs::Tuple,x...) -> Tuple
Returns a tuple with the cache corresponding to each kernel in fs
for the arguments x...
.
Gridap.Arrays.kernel_return_types
— Functionkernel_return_types(f::Tuple,x...) -> Tuple
Computes the return types of the kernels in f
when called with arguments x
.
Build-in kernels
Gridap.Arrays.bcast
— Functionbcast(f::Function)
Returns a kernel object that represents the "boradcasted" version of the given function f
.
Gridap.Arrays.elem
— Functionelem(f::Function)
Returns a kernel that represents the element-wise version of the operation f
It does not broadcast in singleton axes. Thus, allows some performance optimizations with respect to broadcast.
Gridap.Arrays.contract
— Functioncontract(f::Function)
Like the dot product between to vectors, but using operation f
instead of *
between components.
not needed any more, to be deleted
Examples
using Gridap.Arrays
k = contract(-)
apply_kernel(k,[1,2],[2,4]) # Equivalent to (1-2) + (2-4)
# output
-3
Helper functions
Gridap.Arrays.collect1d
— Functioncollect1d(a)
Equivalent to
[a[i] for in 1:length(a)]
Gridap.Arrays.reindex
— Methodreindex(i_to_v::AbstractArray, j_to_i::AbstractArray)
Concrete array implementations
CachedArray
Gridap.Arrays.CachedArray
— Typemutable struct CachedArray{T, N, A<:AbstractArray{T,N}} <: AbstractArray{T,N}
Type providing a re-sizable array.
The size of a CachedArray
is changed via the setsize!
function.
A CachedArray
can be build with the constructors
using Gridap.Arrays
# Create an empty CachedArray
a = CachedArray(Float64,2)
# Resize to new shape (2,3)
setsize!(a,(2,3))
size(a)
# output
(2, 3)
Gridap.Arrays.CachedArray
— MethodCachedArray(a::AbstractArray)
Constructs a CachedArray
from a given array.
Gridap.Arrays.CachedArray
— MethodCachedArray(T,N)
Constructs an empty CachedArray
of element type T
and N
dimensions.
Gridap.Arrays.setsize!
— Functionsetsize!(a, s)
Changes the size of the CachedArray
a
to the size described the the tuple s
. After calling setsize!
, the array can store uninitialized values.
Gridap.Arrays.CachedMatrix
— Typeconst CachedMatrix{T,A} = CachedArray{T,2,A}
Gridap.Arrays.CachedVector
— Typeconst CachedVector{T,A} = CachedArray{T,1,A}
CompressedArray
Gridap.Arrays.CompressedArray
— Typestruct CompressedArray{T,N,A,P} <: AbstractArray{T,N}
values::A
ptrs::P
end
Type representing an array with a reduced set of values. The array is represented by a short array of values, namely the field values
, and a large array of indices, namely the field ptrs
. The i
-th component of the resulting array is defined as values[ptrs[i]]
. The type parameters A
, and P
are restricted to be array types by the inner constructor of this struct
.
Gridap.Arrays.CompressedArray
— MethodCompressedArray(values::AbstractArray,ptrs::AbstractArray)
Creates a CompressedArray
object by the given arrays of values
and ptrs
.
Table
Gridap.Arrays.Table
— Typestruct Table{T,P} <: AbstractVector{Vector{T}}
data::Vector{T}
ptrs::Vector{P}
end
Type representing a list of lists (i.e., a table) in compressed format.
Gridap.Arrays.Table
— MethodTable(data::AbstractVector{T},ptrs::AbstractVector{P}) where {T,P}
Build a table from the given data and pointers. If the arguments are not of type Vector
, they will be converted.
Gridap.Arrays.Table
— MethodTable(data::AbstractVector{T},ptrs::AbstractVector{P}) where {T,P}
Build a table from the given data and pointers. If the arguments are not of type Vector
, they will be converted.
Table(a::AbstractArray{<:AbstractArray})
Build a table from a vector of vectors. If the inputs are multidimensional arrays instead of vectors, they are flattened.
Gridap.Arrays.identity_table
— MethodGridap.Arrays.empty_table
— Methodempty_table(::Type{T},::Type{P}, l::Integer) where {T,P}
empty_table(l::Integer)
Gridap.Arrays.get_ptrs_eltype
— MethodGridap.Arrays.get_data_eltype
— MethodGridap.Arrays.append_tables_globally
— MethodGridap.Arrays.append_tables_locally
— MethodGridap.Arrays.append_tables_locally
— Methodappend_tables_locally(tables::Table...)
Gridap.Arrays.rewind_ptrs!
— Methodrewind_ptrs!(ptrs)
Rewind the given vector of pointers.
Gridap.Arrays.generate_data_and_ptrs
— Methoddata, ptrs = generate_data_and_ptrs(vv)
Given a vector of vectors, compress it and return the corresponding data and and ptrs
Gridap.Arrays.length_to_ptrs!
— Methodlength_to_ptrs!(ptrs)
Given a vector of integers, mutate it from length state to pointer state.
Gridap.Arrays.append_ptrs
— Methodappend_ptrs(pa,pb)
Append two vectors of pointers.
Gridap.Arrays.append_ptrs!
— MethodGridap.Arrays.find_inverse_index_map
— MethodGridap.Arrays.find_inverse_index_map!
— MethodGridap.Arrays.flatten_partition
— Methodflatten_partition(a_to_bs::Table,nb::Integer)
flatten_partition(a_to_bs::Table)
Gridap.Arrays.find_local_index
— Methodfind_local_index(a_to_b, b_to_la_to_a)
Gridap.Arrays.get_local_item
— Methodget_local_item(a_to_lb_to_b, lb::Integer)
Gridap.Arrays.UNSET
— ConstantLocalToGlobalArray
Gridap.Arrays.LocalToGlobalArray
— TypeGridap.Arrays.LocalToGlobalArray
— MethodLocalToGlobalPosNegArray
Gridap.Arrays.LocalToGlobalPosNegArray
— TypeGridap.Arrays.LocalToGlobalPosNegArray
— MethodSubVector
Gridap.Arrays.SubVector
— Typestruct SubVector{T,A<:AbstractVector{T}} <: AbstractVector{T}
vector::A
pini::Int
pend::Int
end
Helpers
Gridap.Arrays.identity_vector
— Methodidentity_vector(l::Integer)