8
8
[ ![ Coverage Status] ( https://coveralls.io/repos/github/JuliaArrays/StaticArrays.jl/badge.svg?branch=master )] ( https://coveralls.io/github/JuliaArrays/StaticArrays.jl?branch=master )
9
9
10
10
** 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} ` .
12
12
Subtypes of ` StaticArray ` will provide fast implementations of common array and
13
13
linear algebra operations. Note that here "statically sized" means that the
14
14
size can be determined from the * type* (so concrete implementations of
@@ -17,9 +17,10 @@ necessarily imply `immutable`.
17
17
18
18
The package also provides some concrete static array types: ` SVector ` , ` SMatrix `
19
19
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".
23
24
24
25
## Speed
25
26
@@ -97,11 +98,11 @@ v3 == m3 * v3 # recall that m3 = eye(SMatrix{3,3})
97
98
v1[1 ] === 1
98
99
v1[(3 ,2 ,1 )] === @SVector [3 , 2 , 1 ]
99
100
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]
101
102
102
- # Inherits from DenseArray, so is hooked into BLAS, LAPACK, etc:
103
+ # Is (partially) hooked into BLAS, LAPACK, etc:
103
104
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
105
106
106
107
# Static arrays stay statically sized, even when used by Base functions, etc:
107
108
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}}
110
111
typeof (similar (m3)) == MMatrix{3 ,3 ,Float64,9 } # (final parameter is length = 9)
111
112
similar_type (m3) == SMatrix{3 ,3 ,Float64,9 }
112
113
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 ]
114
120
reshape ([1 ,2 ,3 ,4 ], SMatrix{2 ,2 }) === @SMatrix [ 1 3 ;
115
121
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
116
126
```
117
127
118
128
## Approach
@@ -174,6 +184,36 @@ and `broadcast`.
174
184
Use of ` similar ` will fall back to a mutable container, such as a ` MVector `
175
185
(see below).
176
186
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
+
177
217
### ` SVector `
178
218
179
219
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
267
307
268
308
Convenience macros ` @MVector ` , ` @MMatrix ` and ` @MArray ` are provided.
269
309
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
+
270
328
### ` FieldVector `
271
329
272
330
Sometimes it might be useful to imbue your own types, having multiple fields,
@@ -321,12 +379,80 @@ m = [1 2;
321
379
sv = SVector {2} (v)
322
380
sm = SMatrix {2,2} (m)
323
381
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)
324
385
```
325
386
326
387
We have avoided adding ` SVector(v::AbstractVector) ` as a valid constructor to
327
388
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) ` .
329
392
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.)
330
456
331
457
### SIMD optimizations
332
458
@@ -336,14 +462,15 @@ default. Run Julia with `julia -O` or `julia -O3` to enable these optimizations,
336
462
and many of your (immutable) ` StaticArray ` methods * should* become significantly
337
463
faster!
338
464
339
- ### * FixedSizeArrays* and * ImmutableArrays* compatibility
465
+ ## Relationship to * FixedSizeArrays* and * ImmutableArrays*
340
466
341
467
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.
347
474
348
475
You can try ` using StaticArrays.FixedSizeArrays ` to add some compatibility
349
476
wrappers for the most commonly used features of the * FixedSizeArrays* package,
@@ -354,11 +481,3 @@ code.
354
481
Furthermore, ` using StaticArrays.ImmutableArrays ` will let you use the typenames
355
482
from the * ImmutableArrays* package, which does not include the array size as a
356
483
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 )
0 commit comments