Overview

Shapes provides Julia traits for describing the shape of a value, like a scalar or array.

Shapes can be used for pre-allocating storage and allows for viewing flat, unstructured data (e.g. a Vector{Float64}) as a set of variables, each with their own shapes and data types.

Shapes is heavily inspired by ValueShapes and StaticArrays; it is essentially a statically-sized version of the former, allowing for zero-cost abstractions.

Shape and MultiShape

The core type provided by Shapes.jl is Shape{S,T,N,L}, where S, T, N, and L describe the size, data type, dimensionality, and overall length of the shape, respectively. ScalarShape{T}, VectorShape{T, L}, and MatrixShape{M, N, T} aliases are provided for convenience.

julia> using Shapes

julia> shape = MatrixShape(Float64, 5, 10)
Shape{Tuple{5,10},Float64,2,50}()

Shapes can be used with a variety of Base functions to describe different aspects of the Shape:

julia> size(shape) == (5, 10) && length(shape) == 50 && ndims(shape) == 2
true
julia> axes(shape) # Shapes uses the static ranges provided by StaticArrays.jl
(SOneTo(5), SOneTo(10))

Shapes also distinguish between its possibly abstract data type and the underlying concrete storage type:

julia> shape = VectorShape(Real, 5);

julia> eltype(shape)
Real

julia> concrete_eltype(shape) # The default for T, where Real <: T <: Number, is Float64.
Float64

This can be used for pre-allocating concrete storage for a Shape:

julia> x = Array(undef, shape, 100);

julia> typeof(x)
Array{Float64,2}

julia> size(x)
(5, 100)

Shapes also provides MultiShape{S,T,N,L}, which behaves as a NamedTuple of shapes and can be used to represent a collection of variables or parameters:

julia> multishape = MultiShape(x = ScalarShape(Real), y = VectorShape(Float64, 3))
MultiShape{Tuple{4},Real,1,4,(x = Shape{Tuple{},Real,0,1}(), y = Shape{Tuple{3},Float64,1,3}())}()

julia> multishape.y
Shape{Tuple{3},Float64,1,3}()

ShapedView

Perhaps the most useful feature of Shapes.jl is ShapedView, which provides a structured view of flat numerical data. A ShapedView{T,N,Shape} <: AbstractArray{T,N} behaves just like a AbstractArray{T,N} of size equal to size(Shape), but wraps a flat Vector{T} under the hood.

using Random

xshape = ScalarShape(Real);
yshape = MatrixShape(Real, 5, 10);
multishape = MultiShape(x=xshape, y=yshape);

xdata = rand(xshape);
ydata = rand(yshape);
flatdata = [xdata, ydata...];

shapedview = ShapedView(flatdata, multishape);
# alternatively: shapedview = multishape(flatdata)

@assert shapedview.x == xdata == flatdata[1]
shapedview.x = 10
@assert shapedview.x == 10

@assert shapedview.y == ydata
@assert shapedview.y == reshape(view(flatdata, 2:length(flatdata)), size(yshape))

Shapes can also be nested arbitraily:


multishape = MultiShape(
    a=VectorShape(Int, 5),
    b=MatrixShape(Int, 10, 10),
    c = MultiShape(
        d = ScalarShape(Int),
        e = VectorShape(Int, 15)
    ),
)

flatdata = Float64.(collect(1:length(multishape)))
shapedview = ShapedView(flatdata, multishape);

@assert shapedview.a == 1:5
@assert vec(shapedview.b) == 6:105
@assert vec(shapedview.c) == 106:121
@assert shapedview.c.d == 106
@assert vec(shapedview.c.e) == 107:121