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