Usage
This page covers the two kinds of arrays of the package in turn: FixedSizeArray, whose size is set at runtime, and then FixedSizeArrays.FixedSizeArrayDims, the underlying type in which some dimensions can be set at compile time. Everything in the first section applies to both, since a FixedSizeArray is a FixedSizeArrayDims with all dimensions dynamic.
The FixedSizeArray type
Constructors
The basic type provided by this package is FixedSizeArray. Its constructor uses a similar syntax to Base's Array:
julia> using FixedSizeArraysjulia> v = FixedSizeArray{Float64}(undef, 3)3-element FixedSizeArrays.FixedSizeArrayDims{Float64, 1, Tuple{Int64}, Memory{Float64}}: NaN 6.89878380563644e-310 6.89878407700417e-310julia> M = FixedSizeArray{Float64}(undef, 2, 2)2×2 FixedSizeArrays.FixedSizeArrayDims{Float64, 2, Tuple{Int64, Int64}, Memory{Float64}}: 5.06e-321 6.89878e-310 6.89878e-310 6.89878e-310You can also construct FixedSizeArrays from other arrays:
julia> using FixedSizeArraysjulia> arr = [10 20; 30 14]2×2 Matrix{Int64}: 10 20 30 14julia> FixedSizeArray(arr) # construct from an `AbstractArray` value2×2 FixedSizeArrays.FixedSizeArrayDims{Int64, 2, Tuple{Int64, Int64}, Memory{Int64}}: 10 20 30 14julia> FixedSizeArray{Float64}(arr) # construct from an `AbstractArray` value while converting element type2×2 FixedSizeArrays.FixedSizeArrayDims{Float64, 2, Tuple{Int64, Int64}, Memory{Float64}}: 10.0 20.0 30.0 14.0To "convert" a wrapper type like Adjoint while preserving the wrapper structure, you can use Adapt:
julia> using FixedSizeArrays, Adaptjulia> arr = transpose([10 20; 30 14])2×2 transpose(::Matrix{Int64}) with eltype Int64: 10 30 20 14julia> adapt(FixedSizeArray, arr)2×2 transpose(::FixedSizeArrays.FixedSizeArrayDims{Int64, 2, Tuple{Int64, Int64}, Memory{Int64}}) with eltype Int64: 10 30 20 14For 1- and 2-dimensional arrays you can use the aliases FixedSizeVector and FixedSizeMatrix, respectively:
julia> using FixedSizeArraysjulia> v = FixedSizeVector{Float64}(undef, 2)2-element FixedSizeArrays.FixedSizeArrayDims{Float64, 1, Tuple{Int64}, Memory{Float64}}: 6.89877337872407e-310 6.8987732328411e-310julia> M = FixedSizeMatrix{Float64}(undef, 3, 3)3×3 FixedSizeArrays.FixedSizeArrayDims{Float64, 2, Tuple{Int64, Int64}, Memory{Float64}}: NaN 2.5e-323 3.0e-323 0.0 0.0 0.0 5.0e-324 0.0 NaNThe array literals syntax [A, B, C, ...] is limited to Base's Array: to construct a FixedSizeArray from any iterator, use collect_as from Collects.jl, see The collect_as utility.
The memory backend
The FixedSizeArray{T,N,Mem} type has three parameters:
T: the type of the elements of the array (e.g.Int64orFloat64);N: the number of dimensions of the array (e.g. 1 for a vector, 2 for a matrix);Mem<:DenseVector{T}: the memory backend.
In Julia v1.11+, the default memory backend is the Memory{T} type. Since Julia v1.10 does not have the Memory type, to make this package usable also on Julia v1.10 Vector{T} is used as memory backend, but many of the memory/performance optimizations enabled by this package will not be available in that version of Julia and in general FixedSizeArrays.jl does not provide significant improvements compared to Base's Array for that specific version.
To make it easier to refer to the concrete type FixedSizeArray{T,N,Mem} with the default memory backend, the following convenient aliases are available:
The memory backend of an array is returned by parent.
Wrapping an existing memory backend
The constructors above copy the elements into a new memory backend. FixedSizeArrays.wrap instead returns a FixedSizeArray which uses an existing DenseVector, e.g. a Memory or a Vector, as its memory backend, without copying:
julia> using FixedSizeArraysjulia> v = collect(1:6);julia> a = FixedSizeArrays.wrap(FixedSizeArray, v, (2, 3));julia> size(a), parent(a) === v((2, 3), true)julia> a[2, 3] = 60; # writes to `v`julia> v[6]60The length of the parent must be equal to the product of the dimensions. At most one dimension can be given as :, which is then computed from the length of the parent. Changing the length of the parent while the wrapped array is in use, e.g. with resize! when the parent is a Vector, is not allowed: wrap the parent again after resizing it instead.
Static dimensions: the FixedSizeArrayDims type
Dynamic and static dimensions
Many arrays in numerical codes have a shape that is partly known in advance. Take the solution of a discontinuous Galerkin method in three dimensions, stored as u[variable, i, j, k, element]: the number of variables (five for the compressible Euler equations) and of nodes per direction (say four) are fixed by the choice of the equations and the polynomial degree, but the number of elements depends on the mesh, and may change while the simulation runs, e.g. with adaptive mesh refinement. With a plain Array or FixedSizeArray the compiler knows none of these sizes, so every index computation multiplies with sizes loaded from memory, and loops over the small dimensions cannot be fully unrolled.
For such arrays, this package lets you choose, for each dimension, whether it is dynamic or static:
- a dynamic dimension is an
Intstored in the array, as all dimensions of aFixedSizeArray; - a static dimension is a
ValfromBaseor aStaticIntfrom Static.jl, e.g.Val(4)orstatic(4): it is part of the type of the array and takes no space in it, so the compiler can compute the size in that dimension, and the strides and linear indices involving it, at compile time.
The dimensions are the third parameter of the underlying type FixedSizeArrays.FixedSizeArrayDims{T,N,D,Mem}: D is a tuple type of N Ints, Vals and StaticInts, in any order. FixedSizeArray{T,N,Mem} is the alias FixedSizeArrayDims{T,N,NTuple{N,Int},Mem}, so all FixedSizeArrays are FixedSizeArrayDimss, but the arrays with static dimensions are not FixedSizeArrays. FixedSizeArrayDims is public, but not exported: use it as FixedSizeArrays.FixedSizeArrayDims, or import it with using FixedSizeArrays: FixedSizeArrayDims. Static.jl is a weak dependency of FixedSizeArrays.jl, so StaticInt dimensions are only available once you load it yourself, e.g. with using Static: static, while Val dimensions need no other package. The examples below use Vals; StaticInts work in the same way, e.g. FixedSizeArrayDims{Float64}(undef, (static(5), nelements)) or FixedSizeArrayDims{Float64, 2, Tuple{StaticInt{2}, Int}}(arr).
julia> using FixedSizeArrays: FixedSizeArrays, FixedSizeArrayDimsjulia> nelements = 3;julia> u = FixedSizeArrayDims{Float64}(undef, (Val(5), Val(4), Val(4), Val(4), nelements));julia> size(u), strides(u)((5, 4, 4, 4, 3), (1, 5, 20, 80, 320))julia> size(u, 2) # a compile-time constant4julia> typeof(u)FixedSizeArrayDims{Float64, 5, Tuple{Val{5}, Val{4}, Val{4}, Val{4}, Int64}, Memory{Float64}}The same array with StaticInts from Static.jl:
julia> using FixedSizeArrays: FixedSizeArrays, FixedSizeArrayDimsjulia> using Static: static, StaticIntjulia> nelements = 3;julia> u = FixedSizeArrayDims{Float64}(undef, (static(5), static(4), static(4), static(4), nelements));julia> size(u), strides(u)((5, 4, 4, 4, 3), (1, 5, 20, 80, 320))julia> typeof(u)FixedSizeArrayDims{Float64, 5, Tuple{StaticInt{5}, StaticInt{4}, StaticInt{4}, StaticInt{4}, Int64}, Memory{Float64}}A StaticInt{n} and a Val{n} dimension behave the same, but are different types, so arrays which differ only in this are of different types too. With StaticArrayInterface.jl, the static sizes and strides are StaticInts for both.
Each combination of static dimensions is a different type, so the code using the array is compiled for each one: make static only the dimensions which are known in advance and matter for performance, such as the small ones in the example above.
Constructing arrays with static dimensions
The static dimensions cannot be derived from an array or an iterator, so an array with static dimensions is constructed either with dimensions given as Vals or StaticInts, or with a type which specifies the dimensions type D:
- the
undefconstructorFixedSizeArrayDims{T}(undef, dims)makes static the dimensions given asVal(n)orstatic(n), and dynamic those given asInts, as in the example above; - the converting constructor and
collect_asneed the dimensions type:FixedSizeArrayDims{Float64, 2, Tuple{Val{2}, Int}}(arr)copies the matrixarrinto an array whose first dimension is static, andcollect_as(FixedSizeArrayDims{Int, 1, Tuple{Val{3}}}, iter)collects three elements into a vector of static length; FixedSizeArrays.wrapwraps an existing memory backend without copying, with the dimensions given asVals orStaticInts.
julia> using FixedSizeArrays: FixedSizeArrays, FixedSizeArrayDimsjulia> using Collects: collect_asjulia> a = FixedSizeArrayDims{Float64, 2, Tuple{Val{2}, Int}}([1 2 3; 4 5 6]); # copy from a matrixjulia> size(a), size(a, 1)((2, 3), 2)julia> b = collect_as(FixedSizeArrayDims{Int, 1, Tuple{Val{3}}}, (i^2 for i in 1:3)); # from an iteratorjulia> b == [1, 4, 9]truejulia> v = collect(1.0:12.0);julia> c = FixedSizeArrays.wrap(FixedSizeArrayDims, v, (Val(2), Val(3), :)); # wrap, computing the last dimensionjulia> size(c), parent(c) === v((2, 3, 2), true)julia> d = FixedSizeArrayDims{Int}(undef, (5, Val(3))); # a static dimension after a dynamic onejulia> size(d)(5, 3)When the type of the array specifies the dimensions type, the dimensions given to the constructor or to wrap are converted to it: FixedSizeArray{Float64}(undef, (Val(2), 3)) has two dynamic dimensions, since a FixedSizeArray has only dynamic dimensions, while FixedSizeArrayDims{Float64}(undef, (Val(2), 3)) has a static one.
Operations which preserve the static dimensions
Operations which return an array with the same shape as the input keep its static dimensions, so the result has the same type: similar(a), similar(a, T), similar(typeof(a), size) and copy, as well as the in-place operations such as copyto!, fill!, in-place broadcasting with .= or rand!. Operations whose result has a shape computed at runtime return a FixedSizeArray with all dimensions dynamic, so that the type of their result does not depend on the value of the shape: similar(a, T, size), out-of-place broadcasting, map, reductions, reshape, vec and indexing with ranges or colons.
julia> using FixedSizeArraysjulia> using FixedSizeArrays: FixedSizeArrayDimsjulia> a = FixedSizeArrayDims{Float64}(undef, (Val(2), 3));julia> typeof(similar(a)) === typeof(a) # keeps the static dimensiontruejulia> typeof(a .+ 1) === FixedSizeArray{Float64, 2, Memory{Float64}} # all dimensions dynamictruejulia> typeof(a[:, 1]) === FixedSizeVector{Float64, Memory{Float64}}trueThe static dimensions are also available to other packages through the package extensions for Adapt.jl and StaticArrayInterface.jl, see Package extensions.
Package extensions
The package has extensions which are loaded automatically together with the following packages:
- Adapt.jl:
adapt(FixedSizeArray, x)converts the arrays inxtoFixedSizeArrays, also inside wrappers such asAdjoint, see the constructors above. Conversely,adapt(to, a)converts aFixedSizeArrayaas a whole, e.g.adapt(CuArray, a)is aCuArray, while for an array with static dimensions it adapts the memory backend and wraps it again, so thatadapt(CuArray, a)is aFixedSizeArrayDimsbacked by aCuVector, with the same static dimensions.FixedSizeArrayDimsitself is not anadapttarget:adapt(FixedSizeArrayDims, x)returnsxunchanged, since the static dimensions can not be derived fromx, use the constructors instead; - MPI.jl (v0.20.28 or later): the arrays of this package, and
SubArrays of them, can be used directly as communication buffers in MPI operations, e.g.MPI.Send,MPI.Recv!,MPI.Bcast!,MPI.Allreduce!andMPI.Gatherv!; - Random.jl:
rand!(a)fills the array in place; - Static.jl: enables the
StaticIntstatic dimensions (Valdimensions need no extension); - StaticArrayInterface.jl, together with Static.jl: the static sizes and strides of an array with static dimensions, and the device of its memory backend, are made available to the packages which use this interface, such as LoopVectorization.jl, StrideArraysCore.jl and Polyester.jl.
The collect_as utility
The array literals syntax [A, B, C, ...] to construct arrays is limited to Base's Array and cannot be extended to custom array types. The package Collects.jl provides a convenient function, collect_as, to overcome this limitation and construct FixedSizeArrays out of any iterable:
julia> iter = (i for i ∈ 7:9 if i≠8);julia> using FixedSizeArrays, Collectsjulia> collect_as(FixedSizeArray, iter) # construct from an arbitrary iterator2-element FixedSizeArrays.FixedSizeArrayDims{Int64, 1, Tuple{Int64}, Memory{Int64}}: 7 9julia> collect_as(FixedSizeArray{Float64}, iter) # construct from an arbitrary iterator while converting element type2-element FixedSizeArrays.FixedSizeArrayDims{Float64, 1, Tuple{Int64}, Memory{Float64}}: 7.0 9.0julia> collect_as(FixedSizeVectorDefault, (3.14, -4.2, 2.68)) # construct from a tuple3-element FixedSizeArrays.FixedSizeArrayDims{Float64, 1, Tuple{Int64}, Memory{Float64}}: 3.14 -4.2 2.68collect_as also constructs arrays with static dimensions, given a FixedSizeArrayDims type with the dimensions type, see Constructing arrays with static dimensions. See the Collects.jl Readme for more information.
BoundsErrorLight exception
To facilitate the escape analysis of the arrays of this package, accessing an out-of-bound index of these arrays raises a BoundsErrorLight exception when possible. This exception type does not store the entire array for reporting the error message, thus enabling more performance optimizations compared to arrays which throw BoundsError exceptions.
We do not guarantee to throw either BoundsError or BoundsErrorLight when accessing an out-of-bound index of a FixedSizeArray. The exact exception raised may change at any point without breaking the semantic versioning compatibility contract.
BoundsErrorLight is an alias for LightBoundsError from the LightBoundsErrors.jl package.