Skip to content

Commit 695a38e

Browse files
author
Andy Ferris
committed
Updated README etc with new features
1 parent 8b8f7c1 commit 695a38e

File tree

4 files changed

+148
-29
lines changed

4 files changed

+148
-29
lines changed

README.md

Lines changed: 142 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
[![Coverage Status](https://coveralls.io/repos/github/JuliaArrays/StaticArrays.jl/badge.svg?branch=master)](https://coveralls.io/github/JuliaArrays/StaticArrays.jl?branch=master)
99

1010
**StaticArrays** provides a framework for implementing statically sized arrays
11-
in Julia (≥ 0.5), using the abstract type `StaticArray{T,N} <: DenseArray{T,N}`.
11+
in Julia (≥ 0.5), using the abstract type `StaticArray{T,N} <: AbstractArray{T,N}`.
1212
Subtypes of `StaticArray` will provide fast implementations of common array and
1313
linear algebra operations. Note that here "statically sized" means that the
1414
size can be determined from the *type* (so concrete implementations of
@@ -17,9 +17,10 @@ necessarily imply `immutable`.
1717

1818
The package also provides some concrete static array types: `SVector`, `SMatrix`
1919
and `SArray`, which may be used as-is (or else embedded in your own type).
20-
Mutable versions `MVector`, `MMatrix` and `MArray` are also exported. Further,
21-
the abstract `FieldVector` can be used to make fast `StaticVector`s out of any
22-
uniform Julia "struct".
20+
Mutable versions `MVector`, `MMatrix` and `MArray` are also exported, as well
21+
as `SizedArray` for annotating standard `Array`s with static size information.
22+
Further, the abstract `FieldVector` can be used to make fast `StaticVector`s
23+
out of any uniform Julia "struct".
2324

2425
## Speed
2526

@@ -97,11 +98,11 @@ v3 == m3 * v3 # recall that m3 = eye(SMatrix{3,3})
9798
v1[1] === 1
9899
v1[(3,2,1)] === @SVector [3, 2, 1]
99100
v1[:] === v1
100-
typeof(v1[[1,2,3]]) == Vector # Can't determine size from the type of [1,2,3]
101+
typeof(v1[[1,2,3]]) <: Vector # Can't determine size from the type of [1,2,3]
101102

102-
# Inherits from DenseArray, so is hooked into BLAS, LAPACK, etc:
103+
# Is (partially) hooked into BLAS, LAPACK, etc:
103104
rand(MMatrix{20,20}) * rand(MMatrix{20,20}) # large matrices can use BLAS
104-
eig(m3) # eig(), etc use LAPACK
105+
eig(m3) # eig(), etc uses specialized algorithms up to 3×3, or else LAPACK
105106

106107
# Static arrays stay statically sized, even when used by Base functions, etc:
107108
typeof(eig(m3)) == Tuple{MVector{3,Float64}, MMatrix{3,3,Float64,9}}
@@ -110,9 +111,18 @@ typeof(eig(m3)) == Tuple{MVector{3,Float64}, MMatrix{3,3,Float64,9}}
110111
typeof(similar(m3)) == MMatrix{3,3,Float64,9} # (final parameter is length = 9)
111112
similar_type(m3) == SMatrix{3,3,Float64,9}
112113

113-
# reshape() uses types to specify size:
114+
# The Size trait is a compile-time constant representing the size
115+
Size(m3) === Size(3,3)
116+
117+
# reshape() uses Size() or types to specify size:
118+
reshape([1,2,3,4], Size(2,2)) === @SMatrix [ 1 3 ;
119+
2 4 ]
114120
reshape([1,2,3,4], SMatrix{2,2}) === @SMatrix [ 1 3 ;
115121
2 4 ]
122+
123+
# A standard Array can be wrapped into a SizedArray
124+
m4 = Size(3,3)(rand(3,3))
125+
inv(m4) # Take advantage of specialized fast methods
116126
```
117127

118128
## Approach
@@ -174,6 +184,36 @@ and `broadcast`.
174184
Use of `similar` will fall back to a mutable container, such as a `MVector`
175185
(see below).
176186

187+
### The `Size` trait
188+
189+
The size of a statically sized array is a static parameter associated with the
190+
type of the array. The `Size` trait is provided as an abstract representation of
191+
the dimensions of a static array. An array `sa::SA` of size `(dims...)` is
192+
associated with `Size{(dims...)}()`. The following are equivalent (`@pure`)
193+
constructors:
194+
```julia
195+
Size{(dims...)}()
196+
Size(dims...)
197+
Size(sa::StaticArray)
198+
Size(SA) # SA <: StaticArray
199+
```
200+
This is extremely useful for (a) performing dispatch depending on the size of an
201+
array, and (b) passing array dimensions that the compiler can reason about.
202+
203+
An example of size-based dispatch for the determinant of a matrix would be:
204+
```julia
205+
det(x::StaticMatrix) = _det(Size(x), x)
206+
_det(::Size{(1,1)}, x::StaticMatrix) = x[1,1]
207+
_det(::Size{(2,2)}, x::StaticMatrix) = x[1,1]*x[2,2] - x[1,2]*x[2,1]
208+
# and other definitions as necessary
209+
```
210+
211+
Examples of using `Size` as a compile-time constant include
212+
```julia
213+
reshape(svector, Size(2,2)) # Convert SVector{4} to SMatrix{2,2}
214+
Size(3,3)(rand(3,3)) # Construct a random 3×3 SizedArray (see below)
215+
```
216+
177217
### `SVector`
178218

179219
The simplest static array is the `SVector`, defined as
@@ -267,6 +307,24 @@ copied as e.g. an immutable `SVector` to the stack for use, or into e.g. an
267307

268308
Convenience macros `@MVector`, `@MMatrix` and `@MArray` are provided.
269309

310+
### `SizedArray`: a decorate size wrapper for `Array`
311+
312+
Another convenient mutable type is the `SizedArray`, which is just a wrapper-type
313+
about a standard Julia `Array` which declares its knwon size. For example, if
314+
we knew that `a` was a 2×2 `Matrix`, then we can type `sa = SizedArray{(2,2)}(a)`
315+
to construct a new object which knows the type (the size will be verified
316+
automatically). A more convenient syntax for obtaining a `SizedArray` is by calling
317+
a `Size` object, e.g. `sa = Size(2,2)(a)`.
318+
319+
Then, methods on `sa` will use the specialized code provided by the *StaticArrays*
320+
pacakge, which in many cases will be much, much faster. For example, calling
321+
`eig(sa)` will be signficantly faster than `eig(a)` since it will perform a
322+
specialized 2×2 matrix diagonalization rather than a general algorithm provided
323+
by Julia and *LAPACK*.
324+
325+
In some cases it will make more sense to use a `SizedArray`, and in other cases
326+
an `MArray` might be preferable.
327+
270328
### `FieldVector`
271329

272330
Sometimes it might be useful to imbue your own types, having multiple fields,
@@ -321,12 +379,80 @@ m = [1 2;
321379
sv = SVector{2}(v)
322380
sm = SMatrix{2,2}(m)
323381
sa = SArray{(2,2)}(m)
382+
383+
sized_v = Size(2)(v) # SizedArray{(2,)}(v)
384+
sized_m = Size(2,2)(m) # SizedArray{(2,2)}(m)
324385
```
325386

326387
We have avoided adding `SVector(v::AbstractVector)` as a valid constructor to
327388
help users avoid the type instability (and potential performance disaster, if
328-
used without care) of this innocuous looking expression.
389+
used without care) of this innocuous looking expression. However, the simplest
390+
way to deal with an `Array` is to create a `SizedArray` by calling a `Size`
391+
instance, e.g. `Size(2)(v)`.
329392

393+
### Arrays of static arrays
394+
395+
Storing a large number of static arrays is convenient as an array of static
396+
arrays. For example, a collection of positions (3D coordinates - `SVector{3,Float64}`)
397+
could be represented as a `Vector{SVector{3,Float64}}`.
398+
399+
Another common way of storing the same data is as a 3×`N` `Matrix{Float64}`.
400+
Rather conveniently, such types have *exactly* the same binary layout in memory,
401+
and therefore we can use `reinterpret` to convert between the two formats
402+
```julia
403+
function svectors(x::Matrix{Float64})
404+
@assert size(x,1) == 3
405+
reinterpret(SVector{3,Float64}, x, (size(x,2),))
406+
end
407+
```
408+
Such a conversion does not copy the data, rather it refers to the *same* memory
409+
referenced by two different Julia `Array`s. Arguably, a `Vector` of `SVector`s
410+
is preferable to a `Matrix` because (a) it provides a better abstraction of the
411+
objects contained in the array and (b) it allows the fast *StaticArrays* methods
412+
to act on elements.
413+
414+
### Working with mutable and immutable arrays
415+
416+
Generally, it is performant to rebind an *immutable* array, such as
417+
```julia
418+
function average_position(positions::Vector{SVector{3,Float64}})
419+
x = zeros(SVector{3,Float64})
420+
for pos positions
421+
x = x + pos
422+
end
423+
return x / length(positions)
424+
end
425+
```
426+
so long as the `Type` of the rebound variable (`x`, above) does not change.
427+
428+
On the other hand, the above code for mutable containers like `Array`, `MArray`
429+
or `SizedArray` is *not* very efficient. Mutable containers in Julia 0.5 must
430+
be *allocated* and later *garbage collected*, and for small, fixed-size arrays
431+
this can be a leading contribution to the cost. In the above code, a new array
432+
will be instantiated and allocated on each iteration of the loop. In order to
433+
avoid unnecessary allocations, it is best to allocate an array only once and
434+
apply mutating functions to it:
435+
```julia
436+
function average_position(positions::Vector{SVector{3,Float64}})
437+
x = zeros(MVector{3,Float64})
438+
for pos positions
439+
# Take advantage of Julia 0.5 broadcast fusion
440+
x .= (+).(x, pos) # same as broadcast!(+, x, x, positions[i])
441+
end
442+
x .= (/).(x, length(positions))
443+
return x
444+
end
445+
```
446+
Keep in mind that Julia 0.5 does not fuse calls to `.+`, etc (or `.+=` etc),
447+
however the `.=` and `(+).()` syntaxes are fused into a single, efficient call
448+
to `broadcast!`. The simpler syntax `x .+= pos` is expected to be non-allocating
449+
(and therefore faster) in Julia 0.6.
450+
451+
The functions `setindex`, `push`, `pop`, `shift`, `unshift`, `insert` and `deleteat`
452+
are provided for performing certain specific operations on static arrays, in
453+
analogy with the standard functions `setindex!`, `push!`, `pop!`, etc. (Note that
454+
if the size of the static array changes, the type of the output will differ from
455+
the input.)
330456

331457
### SIMD optimizations
332458

@@ -336,14 +462,15 @@ default. Run Julia with `julia -O` or `julia -O3` to enable these optimizations,
336462
and many of your (immutable) `StaticArray` methods *should* become significantly
337463
faster!
338464

339-
### *FixedSizeArrays* and *ImmutableArrays* compatibility
465+
## Relationship to *FixedSizeArrays* and *ImmutableArrays*
340466

341467
Several existing packages for statically sized arrays have been developed for
342-
Julia, noteably *FixedSizeArrays* and *ImmutableArrays*. Upon consultation, it
343-
has been decided to move forward with *StaticArrays* which has found a new home
344-
in the *JuliaArrays* github organization. It is recommended that new users use
345-
this package, and that existing dependent packages consider switching to
346-
*StaticArrays* sometime during the life-cycle of Julia v0.5.
468+
Julia, noteably *FixedSizeArrays* and *ImmutableArrays* which provided signficant
469+
inspiration for this package. Upon consultation, it has been decided to move
470+
forward with *StaticArrays* which has found a new home in the *JuliaArrays*
471+
github organization. It is recommended that new users use this package, and
472+
that existing dependent packages consider switching to *StaticArrays* sometime
473+
during the life-cycle of Julia v0.5.
347474

348475
You can try `using StaticArrays.FixedSizeArrays` to add some compatibility
349476
wrappers for the most commonly used features of the *FixedSizeArrays* package,
@@ -354,11 +481,3 @@ code.
354481
Furthermore, `using StaticArrays.ImmutableArrays` will let you use the typenames
355482
from the *ImmutableArrays* package, which does not include the array size as a
356483
type parameter (e.g. `Vector3{T}` and `Matrix3x3{T}`).
357-
358-
### See also
359-
360-
This package takes inspiration from:
361-
362-
* [Julep: More support for working with immutables #11902](https://github.com/JuliaLang/julia/issues/11902)
363-
* [FixedSizeArrays.jl](https://github.com/SimonDanisch/FixedSizeArrays.jl)
364-
* [ImmutableArrays.jl](https://github.com/JuliaGeometry/ImmutableArrays.jl)

src/MArray.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ end
113113
if isbits(T)
114114
unsafe_store!(Base.unsafe_convert(Ptr{T}, Base.data_pointer_from_objref(v)), val, i)
115115
else
116-
# This one is thought to be unsafe (#27)
116+
# This one is unsafe (#27)
117117
# unsafe_store!(Base.unsafe_convert(Ptr{Ptr{Void}}, Base.data_pointer_from_objref(v.data)), Base.data_pointer_from_objref(val), i)
118-
error("setindex!() with non-isbits eltype is not supported by StaticArrays")
118+
error("setindex!() with non-isbits eltype is not supported by StaticArrays. Consider using SizedArray.")
119119
end
120120

121121
return val

src/MMatrix.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ end
130130
if isbits(T)
131131
unsafe_store!(Base.unsafe_convert(Ptr{T}, Base.data_pointer_from_objref(m)), val, i)
132132
else # TODO check that this isn't crazy. Also, check it doesn't cause problems with GC...
133-
# This one is thought to be unsafe (#27)
133+
# This one is unsafe (#27)
134134
# unsafe_store!(Base.unsafe_convert(Ptr{Ptr{Void}}, Base.data_pointer_from_objref(m.data)), Base.data_pointer_from_objref(val), i)
135-
error("setindex!() with non-isbits eltype is not supported by StaticArrays")
135+
error("setindex!() with non-isbits eltype is not supported by StaticArrays. Consider using SizedArray.")
136136
end
137137

138138
return val

src/MVector.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ end
6363
if isbits(T)
6464
unsafe_store!(Base.unsafe_convert(Ptr{T}, Base.data_pointer_from_objref(v)), val, i)
6565
else
66-
# This one is thought to be unsafe (#27)
66+
# This one is unsafe (#27)
6767
#unsafe_store!(Base.unsafe_convert(Ptr{Ptr{Void}}, Base.data_pointer_from_objref(v.data)), Base.data_pointer_from_objref(val), i)
68-
error("setindex!() with non-isbits eltype is not supported by StaticArrays")
68+
error("setindex!() with non-isbits eltype is not supported by StaticArrays. Consider using SizedArray.")
6969
end
7070

7171
return val

0 commit comments

Comments
 (0)