|
11 | 11 |
|
12 | 12 | **StaticArrays** provides a framework for implementing statically sized arrays
|
13 | 13 | in Julia (≥ 0.5), using the abstract type `StaticArray{Size,T,N} <: AbstractArray{T,N}`.
|
14 |
| -Subtypes of `StaticArray` will provide fast implementations of common array and |
| 14 | +Subtypes of [StaticArray](@ref) will provide fast implementations of common array and |
15 | 15 | linear algebra operations. Note that here "statically sized" means that the
|
16 | 16 | size can be determined from the *type*, and "static" does **not** necessarily
|
17 | 17 | imply `immutable`.
|
@@ -141,309 +141,6 @@ precise dimensions of the input. In combination with intelligent fallbacks to
|
141 | 141 | the methods in Base, we seek to provide a comprehensive support for statically
|
142 | 142 | sized arrays, large or small, that hopefully "just works".
|
143 | 143 |
|
144 |
| -## API Details |
145 |
| - |
146 |
| -### The `Size` trait |
147 |
| - |
148 |
| -The size of a statically sized array is a static parameter associated with the |
149 |
| -type of the array. The `Size` trait is provided as an abstract representation of |
150 |
| -the dimensions of a static array. An array `sa::SA` of size `(dims...)` is |
151 |
| -associated with `Size{(dims...)}()`. The following are equivalent (`@pure`) |
152 |
| -constructors: |
153 |
| -```julia |
154 |
| -Size{(dims...)}() |
155 |
| -Size(dims...) |
156 |
| -Size(sa::StaticArray) |
157 |
| -Size(SA) # SA <: StaticArray |
158 |
| -``` |
159 |
| -This is extremely useful for (a) performing dispatch depending on the size of an |
160 |
| -array, and (b) passing array dimensions that the compiler can reason about. |
161 |
| - |
162 |
| -An example of size-based dispatch for the determinant of a matrix would be: |
163 |
| -```julia |
164 |
| -det(x::StaticMatrix) = _det(Size(x), x) |
165 |
| -_det(::Size{(1,1)}, x::StaticMatrix) = x[1,1] |
166 |
| -_det(::Size{(2,2)}, x::StaticMatrix) = x[1,1]*x[2,2] - x[1,2]*x[2,1] |
167 |
| -# and other definitions as necessary |
168 |
| -``` |
169 |
| - |
170 |
| -Examples of using `Size` as a compile-time constant include |
171 |
| -```julia |
172 |
| -reshape(svector, Size(2,2)) # Convert SVector{4} to SMatrix{2,2} |
173 |
| -Size(3,3)(rand(3,3)) # Construct a random 3×3 SizedArray (see below) |
174 |
| -``` |
175 |
| - |
176 |
| -### Indexing |
177 |
| - |
178 |
| -Statically sized indexing can be realized by indexing each dimension by a |
179 |
| -scalar, a `StaticVector` or `:`. Indexing in this way will result a statically |
180 |
| -sized array (even if the input was dynamically sized, in the case of |
181 |
| -`StaticVector` indices) of the closest type (as defined by `similar_type`). |
182 |
| - |
183 |
| -Conversely, indexing a statically sized array with a dynamically sized index |
184 |
| -(such as a `Vector{Integer}` or `UnitRange{Integer}`) will result in a standard |
185 |
| -(dynamically sized) `Array`. |
186 |
| - |
187 |
| -### `similar_type()` |
188 |
| - |
189 |
| -Since immutable arrays need to be constructed "all-at-once", we need a way of |
190 |
| -obtaining an appropriate constructor if the element type or dimensions of the |
191 |
| -output array differs from the input. To this end, `similar_type` is introduced, |
192 |
| -behaving just like `similar`, except that it returns a type. Relevant methods |
193 |
| -are: |
194 |
| - |
195 |
| -```julia |
196 |
| -similar_type{A <: StaticArray}(::Type{A}) # defaults to A |
197 |
| -similar_type{A <: StaticArray, ElType}(::Type{A}, ::Type{ElType}) # Change element type |
198 |
| -similar_type{A <: AbstractArray}(::Type{A}, size::Size) # Change size |
199 |
| -similar_type{A <: AbstractArray, ElType}(::Type{A}, ::Type{ElType}, size::Size) # Change both |
200 |
| -``` |
201 |
| - |
202 |
| -These setting will affect everything, from indexing, to matrix multiplication |
203 |
| -and `broadcast`. Users wanting introduce a new array type should *only* overload |
204 |
| -the last method in the above. |
205 |
| - |
206 |
| -Use of `similar` will fall back to a mutable container, such as a `MVector` |
207 |
| -(see below), and it requires use of the `Size` trait if you wish to set a new |
208 |
| -static size (or else a dynamically sized `Array` will be generated when |
209 |
| -specifying the size as plain integers). |
210 |
| - |
211 |
| -### `SVector` |
212 |
| - |
213 |
| -The simplest static array is the type `SVector{N,T}`, which provides an |
214 |
| -immutable vector of fixed length `N` and type `T`. |
215 |
| - |
216 |
| -`SVector` defines a series of convenience constructors, so you can just type |
217 |
| -e.g. `SVector(1,2,3)`. Alternatively there is an intelligent `@SVector` macro |
218 |
| -where you can use native Julia array literals syntax, comprehensions, and the |
219 |
| -`zeros()`, `ones()`, `fill()`, `rand()` and `randn()` functions, such as `@SVector [1,2,3]`, |
220 |
| -`@SVector Float64[1,2,3]`, `@SVector [f(i) for i = 1:10]`, `@SVector zeros(3)`, |
221 |
| -`@SVector randn(Float32, 4)`, etc (Note: the range of a comprehension is evaluated at global scope by the |
222 |
| -macro, and must be made of combinations of literal values, functions, or global |
223 |
| -variables, but is not limited to just simple ranges. Extending this to |
224 |
| -(hopefully statically known by type-inference) local-scope variables is hoped |
225 |
| -for the future. The `zeros()`, `ones()`, `fill()`, `rand()` and `randn()` functions do not have this |
226 |
| -limitation.) |
227 |
| - |
228 |
| -### `SMatrix` |
229 |
| - |
230 |
| -Statically sized `N×M` matrices are provided by `SMatrix{N,M,T,L}`. |
231 |
| - |
232 |
| -Here `L` is the `length` of the matrix, such that `N × M = L`. However, |
233 |
| -convenience constructors are provided, so that `L`, `T` and even `M` are |
234 |
| -unnecessary. At minimum, you can type `SMatrix{2}(1,2,3,4)` to create a 2×2 |
235 |
| -matrix (the total number of elements must divide evenly into `N`). A |
236 |
| -convenience macro `@SMatrix [1 2; 3 4]` is provided (which also accepts |
237 |
| -comprehensions and the `zeros()`, `ones()`, `fill()`, `rand()`, `randn()` and `eye()` |
238 |
| -functions). |
239 |
| - |
240 |
| -### `SArray` |
241 |
| - |
242 |
| -A container with arbitrarily many dimensions is defined as |
243 |
| -`struct SArray{Size,T,N,L} <: StaticArray{Size,T,N}`, where |
244 |
| -`Size = Tuple{S1, S2, ...}` is a tuple of `Int`s. You can easily construct one with |
245 |
| -the `@SArray` macro, supporting all the features of `@SVector` and `@SMatrix` |
246 |
| -(but with arbitrary dimension). |
247 |
| - |
248 |
| -The main reason `SVector` and `SMatrix` are defined is to make it easier to |
249 |
| -define the types without the extra tuple characters (compare `SVector{3}` to |
250 |
| -`SArray{Tuple{3}}`). |
251 |
| - |
252 |
| -### `Scalar` |
253 |
| - |
254 |
| -Sometimes you want to broadcast an operation, but not over one of your inputs. |
255 |
| -A classic example is attempting to displace a collection of vectors by the |
256 |
| -same vector. We can now do this with the `Scalar` type: |
257 |
| - |
258 |
| -```julia |
259 |
| -[[1,2,3], [4,5,6]] .+ Scalar([1,0,-1]) # [[2,2,2], [5,5,5]] |
260 |
| -``` |
261 |
| - |
262 |
| -`Scalar` is simply an implementation of an immutable, 0-dimensional `StaticArray`. |
263 |
| - |
264 |
| -### Mutable arrays: `MVector`, `MMatrix` and `MArray` |
265 |
| - |
266 |
| -These statically sized arrays are identical to the above, but are defined as |
267 |
| -`mutable struct`s, instead of immutable `struct`s. Because they are mutable, they |
268 |
| -allow `setindex!` to be defined (achieved through pointer manipulation, into a |
269 |
| -tuple). |
270 |
| - |
271 |
| -As a consequence of Julia's internal implementation, these mutable containers |
272 |
| -live on the heap, not the stack. Their memory must be allocated and tracked by |
273 |
| -the garbage collector. Nevertheless, there is opportunity for speed |
274 |
| -improvements relative to `Base.Array` because (a) there may be one less |
275 |
| -pointer indirection, (b) their (typically small) static size allows for |
276 |
| -additional loop unrolling and inlining, and consequentially (c) their mutating |
277 |
| -methods like `map!` are extremely fast. Benchmarking shows that operations such |
278 |
| -as addition and matrix multiplication are faster for `MMatrix` than `Matrix`, |
279 |
| -at least for sizes up to 14 × 14, though keep in mind that optimal speed will |
280 |
| -be obtained by using mutating functions (like `map!` or `A_mul_B!`) where |
281 |
| -possible, rather than reallocating new memory. |
282 |
| - |
283 |
| -Mutable static arrays also happen to be very useful containers that can be |
284 |
| -constructed on the heap (with the ability to use `setindex!`, etc), and later |
285 |
| -copied as e.g. an immutable `SVector` to the stack for use, or into e.g. an |
286 |
| -`Array{SVector}` for storage. |
287 |
| - |
288 |
| -Convenience macros `@MVector`, `@MMatrix` and `@MArray` are provided. |
289 |
| - |
290 |
| -### `SizedArray`: a decorate size wrapper for `Array` |
291 |
| - |
292 |
| -Another convenient mutable type is the `SizedArray`, which is just a wrapper-type |
293 |
| -about a standard Julia `Array` which declares its knwon size. For example, if |
294 |
| -we knew that `a` was a 2×2 `Matrix`, then we can type `sa = SizedArray{Tuple{2,2}}(a)` |
295 |
| -to construct a new object which knows the type (the size will be verified |
296 |
| -automatically). A more convenient syntax for obtaining a `SizedArray` is by calling |
297 |
| -a `Size` object, e.g. `sa = Size(2,2)(a)`. |
298 |
| - |
299 |
| -Then, methods on `sa` will use the specialized code provided by the *StaticArrays* |
300 |
| -pacakge, which in many cases will be much, much faster. For example, calling |
301 |
| -`eig(sa)` will be signficantly faster than `eig(a)` since it will perform a |
302 |
| -specialized 2×2 matrix diagonalization rather than a general algorithm provided |
303 |
| -by Julia and *LAPACK*. |
304 |
| - |
305 |
| -In some cases it will make more sense to use a `SizedArray`, and in other cases |
306 |
| -an `MArray` might be preferable. |
307 |
| - |
308 |
| -### `FieldVector` |
309 |
| - |
310 |
| -Sometimes it might be useful to imbue your own types, having multiple fields, |
311 |
| -with vector-like properties. *StaticArrays* can take care of this for you by |
312 |
| -allowing you to inherit from `FieldVector{N, T}`. For example, consider: |
313 |
| - |
314 |
| -```julia |
315 |
| -struct Point3D <: FieldVector{3, Float64} |
316 |
| - x::Float64 |
317 |
| - y::Float64 |
318 |
| - z::Float64 |
319 |
| -end |
320 |
| -``` |
321 |
| - |
322 |
| -With this type, users can easily access fields to `p = Point3D(x,y,z)` using |
323 |
| -`p.x`, `p.y` or `p.z`, or alternatively via `p[1]`, `p[2]`, or `p[3]`. You may |
324 |
| -even permute the coordinates with `p[SVector(3,2,1)]`). Furthermore, `Point3D` |
325 |
| -is a complete `AbstractVector` implementation where you can add, subtract or |
326 |
| -scale vectors, multiply them by matrices, etc. |
327 |
| - |
328 |
| -It is also worth noting that `FieldVector`s may be mutable or immutable, and |
329 |
| -that `setindex!` is defined for use on mutable types. For immutable containers, |
330 |
| -you may want to define a method for `similar_type` so that operations leave the |
331 |
| -type constant (otherwise they may fall back to `SVector`). For mutable |
332 |
| -containers, you may want to define a default constructor (no inputs) and an |
333 |
| -appropriate method for `similar`, |
334 |
| - |
335 |
| -### Implementing your own types |
336 |
| - |
337 |
| -You can easily create your own `StaticArray` type, by defining linear |
338 |
| -`getindex` (and optionally `setindex!` for mutable types - see |
339 |
| -`setindex(::MArray, val, i)` in *MArray.jl* for an example of how to |
340 |
| -achieve this through pointer manipulation). Your type should define a constructor |
341 |
| -that takes a tuple of the data (and mutable containers may want to define a |
342 |
| -default constructor). |
343 |
| - |
344 |
| -Other useful functions to overload may be `similar_type` (and `similar` for |
345 |
| -mutable containers). |
346 |
| - |
347 |
| -### Conversions from `Array` |
348 |
| - |
349 |
| -In order to convert from a dynamically sized `AbstractArray` to one of the |
350 |
| -statically sized array types, you must specify the size explicitly. For |
351 |
| -example, |
352 |
| - |
353 |
| -```julia |
354 |
| -v = [1,2] |
355 |
| - |
356 |
| -m = [1 2; |
357 |
| - 3 4] |
358 |
| - |
359 |
| -# ... a lot of intervening code |
360 |
| - |
361 |
| -sv = SVector{2}(v) |
362 |
| -sm = SMatrix{2,2}(m) |
363 |
| -sa = SArray{(2,2)}(m) |
364 |
| - |
365 |
| -sized_v = Size(2)(v) # SizedArray{(2,)}(v) |
366 |
| -sized_m = Size(2,2)(m) # SizedArray{(2,2)}(m) |
367 |
| -``` |
368 |
| - |
369 |
| -We have avoided adding `SVector(v::AbstractVector)` as a valid constructor to |
370 |
| -help users avoid the type instability (and potential performance disaster, if |
371 |
| -used without care) of this innocuous looking expression. However, the simplest |
372 |
| -way to deal with an `Array` is to create a `SizedArray` by calling a `Size` |
373 |
| -instance, e.g. `Size(2)(v)`. |
374 |
| - |
375 |
| -### Arrays of static arrays |
376 |
| - |
377 |
| -Storing a large number of static arrays is convenient as an array of static |
378 |
| -arrays. For example, a collection of positions (3D coordinates - `SVector{3,Float64}`) |
379 |
| -could be represented as a `Vector{SVector{3,Float64}}`. |
380 |
| - |
381 |
| -Another common way of storing the same data is as a 3×`N` `Matrix{Float64}`. |
382 |
| -Rather conveniently, such types have *exactly* the same binary layout in memory, |
383 |
| -and therefore we can use `reinterpret` to convert between the two formats |
384 |
| -```julia |
385 |
| -function svectors(x::Matrix{Float64}) |
386 |
| - @assert size(x,1) == 3 |
387 |
| - reinterpret(SVector{3,Float64}, x, (size(x,2),)) |
388 |
| -end |
389 |
| -``` |
390 |
| -Such a conversion does not copy the data, rather it refers to the *same* memory |
391 |
| -referenced by two different Julia `Array`s. Arguably, a `Vector` of `SVector`s |
392 |
| -is preferable to a `Matrix` because (a) it provides a better abstraction of the |
393 |
| -objects contained in the array and (b) it allows the fast *StaticArrays* methods |
394 |
| -to act on elements. |
395 |
| - |
396 |
| -### Working with mutable and immutable arrays |
397 |
| - |
398 |
| -Generally, it is performant to rebind an *immutable* array, such as |
399 |
| -```julia |
400 |
| -function average_position(positions::Vector{SVector{3,Float64}}) |
401 |
| - x = zeros(SVector{3,Float64}) |
402 |
| - for pos ∈ positions |
403 |
| - x = x + pos |
404 |
| - end |
405 |
| - return x / length(positions) |
406 |
| -end |
407 |
| -``` |
408 |
| -so long as the `Type` of the rebound variable (`x`, above) does not change. |
409 |
| - |
410 |
| -On the other hand, the above code for mutable containers like `Array`, `MArray` |
411 |
| -or `SizedArray` is *not* very efficient. Mutable containers in Julia 0.5 must |
412 |
| -be *allocated* and later *garbage collected*, and for small, fixed-size arrays |
413 |
| -this can be a leading contribution to the cost. In the above code, a new array |
414 |
| -will be instantiated and allocated on each iteration of the loop. In order to |
415 |
| -avoid unnecessary allocations, it is best to allocate an array only once and |
416 |
| -apply mutating functions to it: |
417 |
| -```julia |
418 |
| -function average_position(positions::Vector{SVector{3,Float64}}) |
419 |
| - x = zeros(MVector{3,Float64}) |
420 |
| - for pos ∈ positions |
421 |
| - # Take advantage of Julia 0.5 broadcast fusion |
422 |
| - x .= (+).(x, pos) # same as broadcast!(+, x, x, positions[i]) |
423 |
| - end |
424 |
| - x .= (/).(x, length(positions)) |
425 |
| - return x |
426 |
| -end |
427 |
| -``` |
428 |
| -Keep in mind that Julia 0.5 does not fuse calls to `.+`, etc (or `.+=` etc), |
429 |
| -however the `.=` and `(+).()` syntaxes are fused into a single, efficient call |
430 |
| -to `broadcast!`. The simpler syntax `x .+= pos` is expected to be non-allocating |
431 |
| -(and therefore faster) in Julia 0.6. |
432 |
| - |
433 |
| -The functions `setindex`, `push`, `pop`, `shift`, `unshift`, `insert` and `deleteat` |
434 |
| -are provided for performing certain specific operations on static arrays, in |
435 |
| -analogy with the standard functions `setindex!`, `push!`, `pop!`, etc. (Note that |
436 |
| -if the size of the static array changes, the type of the output will differ from |
437 |
| -the input.) |
438 |
| - |
439 |
| -### SIMD optimizations |
440 |
| - |
441 |
| -It seems Julia and LLVM are smart enough to use processor vectorization |
442 |
| -extensions like SSE and AVX - however they are currently partially disabled by |
443 |
| -default. Run Julia with `julia -O` or `julia -O3` to enable these optimizations, |
444 |
| -and many of your (immutable) `StaticArray` methods *should* become significantly |
445 |
| -faster! |
446 |
| - |
447 | 144 | ## Relationship to *FixedSizeArrays* and *ImmutableArrays*
|
448 | 145 |
|
449 | 146 | Several existing packages for statically sized arrays have been developed for
|
|
0 commit comments