Next: Introduction [Index]
newra
newra
(version 1, updated 2021 October 6)
(c) lloda 2017–2021
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
newra
is a pure Scheme replacement for the built-in C-based array facility in Guile 3.0.
This document uses ‘array’ to refer both to the old built-in array type and to the new type introduced in newra
. The distinction is made as necessary.
A multidimensional array is a container (or rather a container view) whose elements can be looked up using a multi-index (i₀, i₁, ...). Each of the indices i₀, i₁, ... has constant bounds [l₀, h₀], [l₁, h₁], ... independent of the values of the other indices, so the array is ‘rectangular’. The number of indices in the multi-index is the rank of the array, and the list ([l₀ h₀] [l₁ h₁] ... [lᵣ₋₁ hᵣ₋₁]) is the shape of the array. We speak of a rank-r array or of an r-array.
This is a 2-array with bounds [0, 2] on both axes:
┌───────┬───────┬───────┐ │A(0, 0)│A(0, 1)│A(0, 2)│ ├───────┼───────┼───────┤ │A(1, 0)│A(1, 1)│A(1, 2)│ ├───────┼───────┼───────┤ │A(2, 0)│A(2, 1)│A(2, 2)│ └───────┴───────┴───────┘
This is a 3-array with bounds [0, 1] on axis 0, [2, 5] on axis 1, bounds [-2, 0] on axis 2:1
║───────────┬───────────┬──────────║───────────┬───────────┬──────────║ ║A(0, 2, -2)│A(0, 2, -1)│A(0, 2, 0)║A(1, 2, -2)│A(1, 2, -1)│A(1, 2, 0)║ ║───────────┼───────────┼──────────║───────────┼───────────┼──────────║ ║A(0, 3, -2)│A(0, 3, -1)│A(0, 3, 0)║A(1, 3, -2)│A(1, 3, -1)│A(1, 3, 0)║ ║───────────┼───────────┼──────────║───────────┼───────────┼──────────║ ║A(0, 4, -2)│A(0, 4, -1)│A(0, 4, 0)║A(1, 4, -2)│A(1, 4, -1)│A(1, 4, 0)║ ║───────────┼───────────┼──────────║───────────┼───────────┼──────────║ ║A(0, 5, -2)│A(0, 5, -1)│A(0, 5, 0)║A(1, 5, -2)│A(1, 5, -1)│A(1, 5, 0)║ ║───────────┴───────────┴──────────║───────────┴───────────┴──────────║
Sometimes we deal with multidimensional expressions where the elements aren’t stored anywhere, but are computed on demand when the expression is looked up. In this general sense, an ‘array’ is just a function of integers with a rectangular domain. Such an array would be immutable.
Arrays (in the form of matrices, vectors, or tensors) are very common objects in math and programming, and it is very useful to be able to manipulate arrays as individual entities rather than as aggregates — that is one of the main purposes of newra
.
The rest of this section discusses the motivation for newra
in more detail. To start using the library, please jump ahead to Low level.
Next: Rank extension, Up: Introduction [Index]
Rank polymorphism is the ability to treat an array of rank r as an array of lower rank where the elements are themselves arrays.
Think of a matrix A, a 2-array with lengths (l₀, l₁) where the elements A(i₀, i₁) are numbers. If we consider the subarrays (rows) A(0, ...), A(1, ...), ..., A(l₀-1, ...) as individual elements, then we have a new view of A as a 1-array of length l₀ with those rows as elements. We say that the rows A(i₀)≡A(i₀, ...) are the 1-cells of A, and the numbers A(i₀, i₁) are 0-cells of A. For an array of arbitrary rank r the (r-1)-cells of A are called its items. The prefix of the shape (l₀, l₁, ... lₙ₋₁₋ₖ) that is not taken up by the k-cell is called the (r-k)-frame.
┌───────┬───────┬───────┐ │A(0, 0)│A(0, 1)│A(0, 2)│ ├───────┼───────┼───────┤ │A(1, 0)│A(1, 1)│A(1, 2)│ ├───────┼───────┼───────┤ │A(2, 0)│A(2, 1)│A(2, 2)│ └───────┴───────┴───────┘ | ≡ | ──── A(0) ──── A(1) ──── A(2) ──── |
An obvious way to store an array in linearly addressed memory is to place its items one after another. So we would store a 3-array as
A: [A(0), A(1), ...]
and the items of A(i₀), etc. are in turn stored in the same way, so
A: [A(0): [A(0, 0), A(0, 1) ...], ...]
and the same for the items of A(i₀, i₁), etc.
A: [[A(0, 0): [A(0, 0, 0), A(0, 0, 1) ...], A(0, 1): [A(0, 1, 0), A(0, 1, 1) ...]], ...]
This way to lay out an array in memory is called row-major order or C-order, since it’s the default order for built-in arrays in C. A row-major array A with lengths (l₀, l₁, ... lᵣ₋₁) can be looked up like this:
A(i₀, i₁, ...) = (storage-of-A) [(((i₀l₁ + i₁)l₂ + i₂)l₃ + ...)+iᵣ₋₁] = (storage-of-A) [o + s₀·i₀ + s₁·i₁ + ...]
where the numbers (s₀, s₁, ...) are called the steps2. Note that the ‘linear’ or ‘raveled’ address [o + s₀·i₀ + s₁·i₁ + ...] is an affine function of (i₀, i₁, ...). If we represent an array as a tuple
A ≡ ((storage-of-A), o, (s₀, s₁, ...))
then any affine transformation of the indices can be achieved simply by modifying the numbers (o, (s₀, s₁, ...)), with no need to touch the storage. This includes very common operations such as: transposing axes, reversing the order along an axis, most cases of slicing, and sometimes even reshaping or tiling the array.
A basic example is obtaining the i₀-th item of A:
A(i₀) ≡ ((storage-of-A), o+s₀·i₀, (s₁, ...))
Note that we can iterate over these items by simply bumping the pointer o+s₀·i₀. This means that iterating over (k>0)-cells doesn’t have to cost any more than iterating over 0-cells (ra-slice-for-each
). Rank polymorphism isn’t just a high level property of arrays; it is enabled and supported by the way they are laid out in memory.
Next: The pieces of an array, Previous: Rank polymorphism, Up: Introduction [Index]
Rank extension is the mechanism that allows R+S
to be defined even when R
, S
may have different ranks. The idea is an interpolation of the following basic cases.
Suppose first that R
and S
have the same rank. We require that the shapes be the same. Then the shape of R+S
will be the same as the shape of either R
or S
and the elements of R+S
will be
(R+S)(i₀ i₁ ... i₍ᵣ₋₁₎) = R(i₀ i₁ ... i₍ᵣ₋₁₎) + S(i₀ i₁ ... i₍ᵣ₋₁₎)
where r
is the rank of R
.
Now suppose that S
has rank 0. The shape of R+S
is the same as the shape of R
and the elements of R+S
will be
(R+S)(i₀ i₁ ... i₍ᵣ₋₁₎) = R(i₀ i₁ ... i₍ᵣ₋₁₎) + S()
.
The two rules above are supported by all primitive array languages. But suppose that S
has rank s
, where 0<s<r
. Looking at the expressions above, it seems natural to define R+S
by
(R+S)(i₀ i₁ ... i₍ₛ₋₁₎ ... i₍ᵣ₋₁₎) = R(i₀ i₁ ... i₍ₛ₋₁₎ ... i₍ᵣ₋₁₎) + S(i₀ i₁ ... i₍ₛ₋₁₎)
.
That is, after we run out of indices in S
, we simply repeat the elements. We have aligned the shapes so:
[n₀ n₁ ... n₍ₛ₋₁₎ ... n₍ᵣ₋₁₎] [n₀ n₁ ... n₍ₛ₋₁₎]
This rank extension rule is used by the J language [J S] and is known as prefix agreement. The opposite rule of suffix agreement is used, for example, in Numpy [num17] .
As you can verify, the prefix agreement rule is distributive. Therefore it can be applied to nested expressions or to expressions with any number of arguments. It is applied systematically throughout newra
, even in assignments. For example,
(define a (make-ra-root #(3 5 9))) (define b (make-ra #f 3 2)) (ra-copy! b a) ; copy each aᵢ on each bᵢ
⇒ #%2:3:2((3 3) (5 5) (9 9))
(define a (make-ra 0 3)) (define b (ra-reshape (ra-iota 6 1) 0 3 2)) (ra-map! a + a b) ; sum the rows of b
⇒ #%1:3(3 7 11)
A weakness of prefix agreement is that the axes you want to match aren’t always the prefix axes. Other array systems (e.g. [num17] ) offer a feature similar to rank extension called ‘broadcasting’ that is a bit more flexible. For example an array of shape [A B 1 D] will match an array of shape [A B C D] for any value of C. The process of broadcasting consists in inserting so-called ‘singleton dimensions’ (axes with length one) to align the axes that one wishes to match. One may think of rank extension as a particular case of broadcasting where the singleton dimensions are added to the end of the shorter shapes automatically.
A drawback of singleton broadcasting is that it muddles the distinction between a scalar and a vector of length 1. Sometimes, an axis of length 1 is no more than that, and if 2≠3 is a size error, it isn’t obvious why 1≠2 shouldn’t be. For this reason newra
’s support for explicit broadcasting is based on dead axes.
(define a (ra-i 5 3)) (define b (make-ra 0 3)) (let ((b1 (ra-transpose b 1))) ; align axis 0 of b with axis 1 of a (ra-map! b1 + b1 a) ; sum the columns of a b)
⇒ b = #%1:5(30 35 40)
Next: Built-in Guile arrays, Previous: Rank extension, Up: Introduction [Index]
An newra
array is an aggregate of the following pieces:
Together, the dim vector and the zero define an affine function of array indices i₀, i₁, ..
that produces an index into the root. Thus, the array is a multidimensional view of the root.
For example, the following pieces
#(1 2 3 4 5 6 7)
#(#<<dim> len: 2 lo: 0 step: 2> #<<dim> len: 2 lo: 0 step: 1>)
define an array A(i₀, i₁) = v(1 + 2·i₀ + 1·i₁), 0≤i₀<2, 0≤i₁<2, that is A = [[2 3] [4 5]].
In newra
code,
(make-ra-root (vector 1 2 3 4 5 6 7) 1 (vector (make-dim 2 0 2) (make-dim 2 0 1)))
⇒ #%2:2:2((2 3) (4 5))
The default print style means #%RANK:LEN₀:LEN₁(...)
(Writing and reading).
It’s unusual to need to specify the dims directly. More commonly, one creates an array of whatever size
> (define a (make-ra #f 3 4)) > a
⇒ #%2:3:4((#f #f #f #f) (#f #f #f #f) (#f #f #f #f))
which automatically creates a root of the required size, so that all the array elements are distinct. Then one operates on the array without making reference to the underlying root,
> (ra-set! a 99 2 2)
⇒ #%2:3:4((#f #f #f #f) (#f #f 99 #f) (#f #f #f #f))
Still, since the array is just a view of the root, any changes on the array are reflected there as well
> (ra-root a)
⇒ #(#f #f #f #f #f #f #f #f #f #f 99 #f)
and the other way around,
> (define b (make-ra-root (vector 'x) 0 (vector (make-dim 3 0 0) (make-dim 2 0 0)))) > b
⇒ #%2:3:2((x x) (x x) (x x))
> (vector-set! (ra-root b) 0 'Z) > b
⇒ #%2:3:2((Z Z) (Z Z) (Z Z))
It is often important to know whether an operation on an array returns a different view of its argument, or instead it allocates a new root which can be modified without affecting the original argument. When we say that a function ‘creates a new array’, we mean that it allocates a new root.
Generally a given function will always do one or the other, e.g. the result of ra-tile
always shares the root of its argument, while ra-copy
always creates a new array. Some functions, like ra-ravel
or ra-from
, may do either, depending on their arguments. For example, the result of
(ra-ravel (ra-iota 3 4))
⇒ #%1d:12(0 1 2 3 4 5 6 7 8 9 10 11)
shares the root of (ra-iota 3 4)
, but
(ra-ravel (ra-transpose (ra-iota 3 4) 1 0))
⇒ #%1:12(0 4 8 1 5 9 2 6 10 3 7 11)
doesn’t.
Previous: The pieces of an array, Up: Introduction [Index]
Dense multidimensional arrays work similarly in every language that offers them, and built-in Guile arrays are no different — they also have a root (shared-array-root
), a zero (computable from shared-array-offset
and array-shape
), and a dim vector (array-shape
, shared-array-increments
). Functionally, they are entirely equivalent to the objects offered by newra
. Why replace them?
Built-in Guile arrays are implemented in C, as part of libguile. As a Guile type they have their own low-level type tag, and all the basic array operations are C stubs, even the most basic functions such as array-ref
or array-rank
. Obtaining any of the components of the array requires calling into C. There are several problems with this.
First, the built-in library offers a single function to manipulate array dims, make-shared-array
. Although this is a sufficient interface, it is excessively generic, and also very cumbersome and inefficient. The array dims cannot be manipulated directly from Scheme, so any alternative interface written in Scheme is forced to go through make-shared-array
.
Second, the C stubs create a barrier to optimization by the Scheme compiler. The main loop of an operation such as (array-map! c + a b)
has to be implemented in C (for the reasons given above) and then it has to call back to Scheme on each iteration in order to apply +
. Since the Scheme compiler doesn’t have any special support for array-map!
, it doesn’t know what the types of the arguments are, etc. and those checks and dispatches are repeated over and over. 3
Third, some of the the larger functions of the array interface, such as array-map!
, etc. are not interruptible. This is especially inconvenient when operating on large arrays.
These problems are solved if the built-in type is replaced with a new type defined in Scheme.
Next: High level, Previous: Introduction, Up: newra
[Index]
Next: Special arrays, Up: Low level [Index]
An array can be created anew (make-ra-new
, make-ra
, make-typed-ra
), or over an existing root (make-ra-root
).
make-ra
or make-typed-ra
take array lengths and use row-major order by default.
(make-ra 99 2 3)
⇒ #%2:3:2((9 9) (9 9) (9 9))
(make-typed-ra 'f64 99 2 3)
⇒ #%2f64:3:2((9.0 9.0) (9.0 9.0) (9.0 9.0))
make-ra-new
takes an array type, a fill value, and a dim vector. c-dims
can be used to create a row-major dim vector.
(make-ra-new #t 'x (vector (make-dim 3 0 2) (make-dim 2 0 1))) ; more simply (make-ra-new #t 'x (c-dims 3 2))
⇒ #%2:3:2((x x) (x x) (x x))
(make-ra-new 'f32 0.0 (c-dims 3 2))
⇒ #%2f32:3:2((0.0 0.0) (0.0 0.0) (0.0 0.0))
make-ra-root
takes the type from the root vector.
(make-ra-root (vector 1 2 3 4 5 6) (c-dims 3 2))
⇒ #%2:3:2((1 2) (3 4) (5 6))
newra
arrays are applicative; to look up or assign an element of an array, use it as a function of the indices.
(define a (make-ra #f 3 2)) (set! (a 0 0) 9) (set! (a 1 1) 3)
⇒ #%2:3:4((9 #f) (#f 3) (#f #f))
(a 0 0)
⇒ 9
If you give fewer indices than the rank, you get a prefix slice. This slice shares the root of the original array.
(a 1)
⇒ #%1:2(#f 3)
(set! ((a 1) 0) 'b)
⇒ #%1:2(b 3)
a
⇒ #%2:3:4((9 #f) (b 3) (#f #f))
You can also access arrays in the more usual way with the functions ra-ref
and ra-set!
. See Slicing for additional options.
Next: Writing and reading, Previous: Creating and accessing arrays, Up: Low level [Index]
Any type that is usable as the root of an old built-in Guile array is also usable as root of a newra
array. These include
(vector 3)
)
(c64vector 1 2+0i)
)
"hello"
)
(bitvector #f #t #f #t)
)
newra
supports an additional root vector type, <aseq>
, representing an unbounded arithmetic sequence.
Create an arithmetic sequence [org, org+inc, org+2·inc, ...
]. The default values of org
and inc
are respectively 0 and 1. For example:
(make-ra-root (make-aseq 0 3) (vector (make-dim 10)) 0)
⇒ #%1d:10(0 3 6 9 12 15 18 21 24 27)
(The example above can be written (ra-iota 10 0 3)
).
aseq
roots are immutable. The type tag of aseq
roots is d
. Arrays with integer-valued aseq
roots have a few special uses; one of them is as arguments in slicing.
To make <aseq>
even more useful, newra
supports unbounded axes.
(ra-ref (make-ra-root (make-aseq) (vector (make-dim #f)) 0) #e1e12) ; or more simply (ra-ref (ra-iota) #e1e12)
⇒ 1000000000000
These are treated especially when used in iteration, in that they match axes of any finite length (ra-map!
). Effectively this lets one use (
as a placeholder for the index over axis ra-transpose
(ra-iota) k)k
.
(ra-map! (make-ra 0 3) + (ra-iota 3) (ra-iota))
⇒ #1%3(0 2 4)
newra
also supports ‘dead axes’, which are axes with step 0 and undefined length. These axes can match axes of any length and can exist on arrays of any type, not only on arrays of type d
, because effectively only one position (the lower bound) is ever accessed.
Dead axes operate essentially as ‘singleton axes’ do in other array languages. The main diference is that the ability to match any finite length is explicit; an axis with length 1 will still fail to match an axis with length 2 (say).
Some functions work by creating axes with step 0, usually with defined lengths.
(define A (make-ra-root #(1 2 3) (c-dims 3))) (ra-tile A 0 2 2)
⇒ #%3d:2:2:3(((0 1 2) (0 1 2)) ((0 1 2) (0 1 2)))
(ra-dims (ra-tile A 0 2 2))
⇒ #(#<<dim> len: 2 lo: 0 step: 0> #<<dim> len: 2 lo: 0 step: 0> #<<dim> len: 3 lo: 0 step: 1>)
Next: Iteration, Previous: Special arrays, Up: Low level [Index]
The read syntax for arrays is the same as for built-in Guile arrays, except that #%
is used instead of #
. Full dimensions are printed by default, even when they are not required to read an array.
(call-with-input-string "#%1(2 2 2)" read)
⇒ #%1:3(2 2 2)
(call-with-input-string "#%1:3(2 2 2)" read)
⇒ #%1:3(2 2 2)
Dead axes print as d
, and unbounded (not dead) axes print as f
. These cannot be read back.
(display (ra-transpose (ra-copy (ra-iota 3)) 1))
⇒ #%2:d:3((0 1 2))
Arrays with root of type d
cannot be read back either.
(define s (format #f "~a" (ra-i 2 3))) s
⇒ "#%2d:2:3((0 1 2) (3 4 5))"
(call-with-input-string s read)
⇒ error: cannot make array of type d
Truncated output is not supported yet.
(format #f "~@y" (ra-i 2 3))
⇒ "#%2d:2:3((0 1 2) (3 4 5))"
; ok, but we didn't need to truncate
(format #f "~@y" (ra-i 99 99))
⇒ "#" ; ouch
The function ra-format
can be used to pretty print arrays. This type of output cannot be read back, either.
(ra-format (list->ra 2 '((1 hello) ("try" 2) (never 3.14))) #:fmt "~s")
⇒
#%2:3:2─────┐ │ 1│hello│ ├─────┼─────┤ │"try"│ 2│ ├─────┼─────┤ │never│ 3.14│ └─────┴─────┘
The writing mode can be configured with the following parameter.
Set the default printer for arrays. This parameter is available from (newra print)
.
The parameter can be set to a function (lambda (array port) ...)
or to one of the values #f
, 'default
, 'box
or 'box-compact
.
For example
(import (newra print)) (*ra-print* (lambda (ra o) (ra-print ra o #:dims? #f))) (ra-i 2 3)
⇒
$1 = #%2d((0 1 2) (3 4 5))
or
(*ra-print* (lambda (ra o) (newline o) (ra-format ra o))) ; (*ra-print* 'box) ; same thing (ra-i 2 3)
⇒
$1 = #%2d:2:3 │0│1│2│ ├─┼─┼─┤ │3│4│5│ └─┴─┴─┘
The default printer can be reset with (*ra-print* #f)
or (*ra-print* 'default)
.
By default, rank-0 arrays are printed like the built-in Guile arrays, with extra parentheses around the content. In the read syntax specified in [SRFI-163] , those parentheses are not used. The following parameter allows one to choose either behavior for both the printer and the reader.
Control read syntax of rank-0 arrays. This parameter is available from (newra print)
or (newra read)
.
If (*ra-parenthesized-rank-zero*)
is true, the read syntax for rank-0 arrays is
#%0TYPE(item)
If it is #f
, it is
#%0TYPE item
with TYPE
being optional in either case. Note that these are not compatible:
(ra-ref (parameterize ((*ra-parenthesized-rank-zero* #t)) (call-with-input-string "#%0(a)" read)))
⇒ a
(ra-ref (parameterize ((*ra-parenthesized-rank-zero* #f)) (call-with-input-string "#%0(a)" read)))
⇒ (a)
(ra-ref (parameterize ((*ra-parenthesized-rank-zero* #f)) (call-with-input-string "#%0 a" read)))
⇒ a
In the last example, the space is necessary (unlike in [SRFI-163] ) since the array type tag is optional in Guile.
(parameterize ((*ra-parenthesized-rank-zero* #f)) (call-with-input-string "#%0a" read))
⇒ Wrong type (expecting character): #<eof>
The printer always uses a space in this mode:
(parameterize ((*ra-parenthesized-rank-zero* #f)) (display (make-ra '(a))))
⇒ #%0 (a)
Note that setting this parameter to #f
still doesn’t make the array read syntax fully compatible with that of [SRFI-163]
, since the type tag a
is reserved (in Guile) for character arrays.
The default value of this parameter is #t
.
Next: Slicing, Previous: Writing and reading, Up: Low level [Index]
The basic array iteration operations in newra
all operate by effect. This gives you control of how the result is allocated. If one of the arguments is designated as destination, as is the case with ra-map!
, then that is the result of the whole iteration. For example:
(ra-map! (make-ra #f 3) - (ra-iota 3 1))
⇒ #%1:3(-1 -2 -3)
It is common to need the indices of the elements during array iteration. newra
iteration operations do not keep track of those indices4 because that has a cost. You need to pass the indices you need as arguments, but it’s easy to do so by using an unbounded index vector together with ra-transpose
.
(define i0 (ra-iota)) (define i1 (ra-transpose (ra-iota) 1)) (ra-map! (make-ra #f 2 2) list (list->ra 2 '((A B) (C D))) i0 i1)
⇒ #%2:2:2(((A 0 0) (B 0 1)) ((C 1 0) (D 1 1)))
One can iterate not only over the whole array, but also over any n
-frame (the first n
axes of an array), using ra-slice-for-each
. In this case the operation takes array slices as arguments, even when they are of rank 0; this allows writing to any of the arguments. When there are several arrays involved, all the frames must match.
In the following example, xys
is of rank 2, angle
is of rank 1, and their first axes have the same length.
(ra-slice-for-each 1 (lambda (xy angle) ; inside the op, xy is rank 1, angle is rank 0 (ra-set! angle (atan (ra-ref xy 1) (ra-ref xy 0)))) xys angles)
The iteration procedures in newra
all perform rank extension of their arguments through prefix matching (see Rank extension). In the following example, the shapes of the arguments are (5 5), (5) and (#f
5), and the common prefixes all match.
(ra-map! (make-ra 5 5) * (ra-iota 5 1) (ra-transpose (ra-iota 5 1) 1))
⇒ #%2:5:5((1 2 3 4 5) (2 4 6 8 10) (3 6 9 12 15) (4 8 12 16 20) (5 10 15 20 25))
Another example using ra-copy!
,
(ra-copy! (list->ra 2 '((a b) (p q) (x y))) (list->ra 1 '(1 2 3)))
⇒ #%2:3:2((1 1) (2 2) (3 3))
Slicing refers to the operation of taking a partial view of an array (e.g. a row or a column out of a matrix) through modification of the dim vector. This can be done with creative uses of ra-ravel
, ra-reshape
and ra-transpose
, and of course by direct modification of the dim vector, but the facilities described in this section are usually a lot clearer.
The simplest form of slicing uses ra-slice to produce ‘prefix slices’.
(define a (list->ra 3 '(((a b) (x y)) ((A B) (X Y)))))
⇒ #%3:2:2:2(((a b) (x y)) ((A B) (X Y)))
(ra-slice a 0 1 0)
⇒ #%0(x)
(ra-slice a 0 1)
⇒ #%1:2(x y)
(ra-slice a 0)
⇒ #%2:2:2((a b) (x y))
(ra-slice a)
⇒ #%3:2:2:2(((a b) (x y)) ((A B) (X Y)))
The prefix slice always shares the root of the source array, so it can be used to modify the source array.
(ra-fill! (ra-slice a 1 0) '99)
⇒ #%1:2(99 99)
a
⇒ #%3:2:2:2(((a b) (x y)) ((99 99) (X Y)))
The variant ra-cell
is identical to ra-slice
except that it returns an element (and not a rank 0 array) when the full set of indices is given.
(ra-slice a 0 1 0)
⇒ x
ra-cell
is a rank-polymorphic generalization of the basic element lookup function ra-ref
, which requires the full set of indices.
(ra-ref a 0 1 0) ; same as ra-cell
⇒ x
(ra-ref a 0 1)
⇒ "<unnamed port>":...: Throw to key `bad-number-of-indices' with args `(3 2)'.
Both ra-cell
and ra-slice
(and ra-ref
) take scalar indices as arguments. The more powerful function ra-from
is able to handle arrays of indices.
(ra-from a i₀ ...) ⇒ b
Each of the i₀...
is either 1. an integer; 2. an array of integers; 3. the special value #t
. Integer arguments contain indices into the respective axis of a
. #t
for iₖ
is a shortcut for ‘the whole of axis k
’5. The result b
has rank equal to the sum of all the ranks of the i₀...
, and is defined as
(ra-ref b j₀ ...) = (ra-ref a (ra-ref i₀ j₀ ...) ...)
In other words, ra-from
produces the outer product of the indices i₀...
with operator a
(if one thinks of (a i₀ ...)
as (ra-ref a i₀ ...)
).
If all of the i...
are integers or arrays of type d
(such as those produced by ra-iota
or ra-i
) then the result of ra-from
shares the root of a
. Otherwise newra
cannot tell whether the indices are an arithmetic sequence, so the result has to be copied to a new root. For example:
(define a (list->ra 2 '((a b c) (d e f))))
⇒ #%2:2:3((a b c) (d e f))
(ra-from a 0 #t) ; row 0, will share root
⇒ #%1:3(a b c)
(ra-from a #t 1) ; column 1, will share root
⇒ #%1:2(b e)
(ra-from a #t (make-ra-root #(2 0))) ; cols 2 & 0, won't share root
⇒ #%2:2:2((c a) (f d))
(ra-from a #t (ra-iota 2 2 -2)) ; cols 2 & 0, will share root
⇒ #%2:2:2((c a) (f d))
One may give fewer i
than the rank of a
. The missing arguments are taken as #t
(see Rank polymorphism).
(ra-from a 0) ; row 0, same as (ra-from a 0 #t)
⇒ #%1d:3(0 1 2)
When used as an argument to ra-from
(or ra-amend!
), the special object (ldots n)
stands for n
times #t
. (ldots)
alone will expand to fill the rank of the array argument, so the indices that come after are pushed to the last axes.
(ra-from A 0 (ldots 1) 1) ; same as (ra-from A 0 #t 1) (ra-from B 0 (ldots 2) 1) ; same as (ra-from B 0 #t #t 1) (ra-from C 0 (ldots) 1) ; same as (ra-from C 1 (ldots (- (ra-rank C) 2)) 1)
For instance:
(ra-i 4 3 2)
⇒ #%3d:4:3:2(((0 1) (2 3) (4 5)) ((6 7) (8 9) (10 11)) ((12 13) (14 15) (16 17)) ((18 19) (20 21) (22 23)))
(ra-from (ra-i 4 3 2) (ldots) 1) ; select second element on last axis
⇒ #%2d:4:3((1 3 5) (7 9 11) (13 15 17) (19 21 23))
When it is known that the result of ra-from
will share the root with its argument, that can be used to modify the original array. For example:
(ra-fill! (ra-from a 1) x)
⇒ #%2:3((a b c) (x x x)
a
⇒ #%2:3((a b c) (x x x))
ra-amend!
handles the general case:
(define a (list->ra 2 '((a b c) (d e f)))) (ra-amend! a 'Y #t (make-ra-root #(2 0)))
⇒ #%2:3((Y b Y) (Y e Y))
a
⇒ #%2:3((Y b Y) (Y e Y))
while on the other hand
(define a (list->ra 2 '((a b c) (d e f)))) (ra-fill! (ra-from a #t (make-ra-root #(2 0))) 'Y)
⇒ #%2:3((Y Y) (Y Y))
a
⇒ #%2:3((a b c) (d e f))
Next: Concatenation, Previous: Slicing, Up: Low level [Index]
To match APL ρ, newra
offers three separate functions.
ra-reshape
and ra-ravel
are in a way the inverse of each other. ra-reshape
folds an axis into (potentially) many, while ra-ravel
makes a block of axes into a single axis. Neither is able to increase the size of the array (although ra-reshape
can reduce it). For that purpose ra-tile
is provided.
(ra-dimensions (ra-i 2 3 4))
⇒ (2 3 4)
; insert new axis of size 5 before axis 0 (ra-dimensions (ra-tile (ra-i 2 3 4) 0 5))
⇒ (5 2 3 4)
; collapse axes 0 and 1 (ra-dimensions (ra-ravel (ra-tile (ra-i 2 3 4) 0 5) 2))
⇒ (10 3 4)
; reshape axis 0 into two axes with shape [3 3] (ra-dimensions (ra-reshape (ra-ravel (ra-tile (ra-i 2 3 4) 0 5) 2) 0 3 3))
⇒ (3 3 3 4)
ra-reshape
and ra-tile
always reuse the root of the argument. On the other hand ra-ravel
may not be able to, depending on the storage order of the array — this is one of the reasons to have three different functions instead of only one. You can check in advance whether ra-ravel
will reuse the root with the function ra-order-c?
.
Next: Transposition, Previous: Reshaping, Up: Low level [Index]
newra
offers two concatenation operations: ra-cat
(prefix cat) and ra-scat
(suffix cat).
For ra-cat
, the arguments are prefix-matched, and the concatenation axis is counted from the left. E.g. for three arrays r, s, t with shapes
(r₀ r₁ r₂ r₃) (s₀ s₁ s₂ s₃ s₄ s₅) (t₀ t₁)
Then (define val (ra-cat #t 1 r s t))
will prefix-match these to (it is an error if any of r₀=s₀=t₀
, r₂=s₂
, or r₃=s₃
don’t hold)
(s₀ |r₁| s₂ s₃ s₄ s₅) (s₀ |s₁| s₂ s₃ s₄ s₅) (s₀ |t₁| s₂ s₃ s₄ s₅)
and then concatenate them along axis 1 into an array of shape (s₀ (r₁+s1+t₁) s₂ s₃ s₄ s₅)
.
For ra-scat
, the arguments are suffix-matched, and the concatenation axis is counted from the right. For example
(define r (ra-i 2 3 2)) (define s (list->ra 1 '(a b))) (ra-scat #t 1 r s)
the axes are aligned as
(r₀ r₁ r₂) (s₀)
and suffix-matched (s₀ and r₂ must match)
(r₀ |r₁| r₂) (r₀ | 1| r₂)
for a result
(ra-scat #t 1 r s)
⇒ #%3(((0 1) (2 3) (4 5) (a b)) ((6 7) (8 9) (10 11) (a b)))
Note that the rank extension of s
on the concatenation axis yields a length of 1 (and not r₀
). It can be useful to think of the axis argument of ra-scat
as indicating cell rank, so the code above means ‘concatenate 1-cells’.
For both ra-cat
and ra-scat
, axes other than the concatenation axis must match across all of the arguments.
(ra-cat #t 0 (ra-i 3 2) (ra-i 2))
⇒ #%2:4:2((0 1) (2 3) (4 5) (0 0) (1 1))
(ra-scat #t 1 (ra-i 3 2) (ra-i 2))
⇒ #%2:4:2((0 1) (2 3) (4 5) (0 1))
In particular, it is not enough for the lengths to be the same; both bounds must match.
(ra-cat #t 0 (make-ra 'a '(1 1) '(1 4)) (make-ra 'b '(2 2) '(1 4)))
⇒ #%2:2@1:4((a a a a) (b b b b))
; axes 1 match, axes 0 don't need to
(ra-cat #t 1 (make-ra 'a '(1 1) '(1 4)) (make-ra 'b '(2 2) '(1 4)))
⇒ error ; axes 0 don't match
Here ra-reshape
is used to move axis 0 of the second argument into agreement.6
(ra-cat #t 1 (make-ra 'a '(1 1) '(1 4)) (ra-reshape (make-ra 'b '(2 2) '(1 4)) 0 '(1 1)))
⇒ #%2@1:1:8((a a a a b b b b))
On the concatenation axis, only lengths matter; for both ra-cat
and ra-scat
, the lower bound is 0 in the result, and the lower bounds of the arguments are ignored.
(define a (make-ra 'a '(1 2) '(2 3))) (define b (make-ra 'b '(1 2))) (define c (make-ra 'c '(1 2) '(-1 0))) (ra-format (ra-cat #t 1 a b c))
⇒
#%2@1:2:5─┐ │a│a│b│c│c│ ├─┼─┼─┼─┼─┤ │a│a│b│c│c│ └─┴─┴─┴─┴─┘
Both ra-cat
and ra-scat
accept a negative concatenation axis. That will rank-extend all the arguments to the left (ra-cat
) or to the right (ra-scat
) before concatenating on the leftmost (ra-cat
) or rightmost (ra-scat
) axis. In the same way, one may give a concatenation axis which is beyond the rank of the argument with the highest rank. Consider
(define abc (list->ra 1 #(a b c))) (ra-cat #t -1 (ra-i 3) abc)
⇒ #%2:2:3((0 1 2) (a b c))
(ra-cat #t 0 (ra-i 3) abc)
⇒ #%1:6(0 1 2 a b c)
(ra-cat #t 1 (ra-i 3) abc)
⇒ #%2:3:2((0 a) (1 b) (2 c))
vs
(ra-scat #t -1 (ra-i 3) abc)
⇒ #%2:3:2((0 a) (1 b) (2 c))
(ra-scat #t 0 (ra-i 3) abc)
⇒ #%1:6(0 1 2 a b c)
(ra-scat #t 1 (ra-i 3) abc)
⇒ #%2:2:3((0 1 2) (a b c))
Cf J append (,) stitch (,.).
Next: Other operations on arrays, Previous: Concatenation, Up: Low level [Index]
ra-transpose
takes a source array and one axis argument for each of the dimensions of the source array. The values of the arguments are the corresponding axes of the result array.
(ra-dimensions (ra-transpose (ra-i 10 20 30) 2 0 1))
⇒ '(20 30 10)
That is, axis 0 in the source array is mapped to axis 2 in the destination array, axis 1 to axis 0, and axis 2 to axis 1. The result array always shares the root of the source array.
As you’d expect
(ra-transpose (ra-i 2 3) 1 0)
⇒ #%2d:3:2((0 3) (1 4) (2 5))
One can map more than one axis of the source array to the same axis of the destination array. In that case the step of the destination axis becomes the sum of the steps of all the source axes. The classic example is
(define A (ra-copy #t (ra-i 3 3))) (ra-fill! (ra-transpose A 0 0) 'x) A
⇒ #%2:3:3((x 1 2) (3 x 5) (6 7 x))
If one doesn’t give values for all of the source axes, the missing axes are sent beyond the highest one that was given. These are equivalent:
(ra-transpose (ra-i 2 3 4) 1 0 2) (ra-transpose (ra-i 2 3 4) 1 0) ; fill with (+ 1 (max 1 0))
as are these:
(ra-transpose (ra-i 2 3) 1) ; fill with (+ 1 (max 1)) (ra-transpose (ra-i 2 3) 1 2)
Note that in the last example there is no source axis for destination axis 0. Destination axes not mentioned in the axis argument list become dead axes. The rank of the result array is always just large enough to fit all the destination axes.
(ra-dimensions (ra-transpose (ra-i 2 3) 1))
⇒ (#f 2 3)
In particular, (ra-transpose A)
is equivalent to (ra-transpose A 0 1 ... (- (ra-rank A) 1))
(which is of course the same array as A
).
This ability of ra-transpose
can be exploited to compute ‘outer products’. In the following example the shape [2 2]
of A
matches with the two leading dead axes of (ra-transpose B 2)
:
A : [2 2] (ra-transpose B 2) : [f f 2 2]
The trailing axes then match through prefix matching.
(define A (list->ra 2 '((a b) (c d)))) (define B (ra-i 2 2)) (ra-format (ra-map! (make-ra #f 2 2 2 2) (lambda i (format #f "~{~a~}" i)) A (ra-transpose B 2)))
⇒
#%4:2:2:2:2═╗ ║a0│a1║b0│b1║ ║──┼──║──┼──║ ║a2│a3║b2│b3║ ╠═════╬═════╣ ║c0│c1║d0│d1║ ║──┼──║──┼──║ ║c2│c3║d2│d3║ ╚═════╩═════╝
Another use is the creation of ‘index placeholders’, e.g.
(define (tensor-index i) (ra-transpose (ra-iota) i)) (ra-format (ra-map! (make-ra #f 3 4) list (tensor-index 0) (tensor-index 1)))
⇒
#%2:3:4─────┬─────┬─────┐ │(0 0)│(0 1)│(0 2)│(0 3)│ ├─────┼─────┼─────┼─────┤ │(1 0)│(1 1)│(1 2)│(1 3)│ ├─────┼─────┼─────┼─────┤ │(2 0)│(2 1)│(2 2)│(2 3)│ └─────┴─────┴─────┴─────┘
The function ra-untranspose
takes its axis arguments the other way from ra-transpose
; the value of each argument is the axis of the original array and the position in the argument list is the axis of the result array. This is less flexible than ra-transpose
, but can be used to reverse an application of ra-transpose
without having to sort (‘grade’) the original axis arguments.
(define a (ra-i 2 3 4)) (ra-equal? a (ra-untranspose (ra-transpose a 2 0 1) 2 0 1))
⇒ #t
Next: Automatic result arrays, Previous: Transposition, Up: Low level [Index]
Next: Foreign interface, Previous: Other operations on arrays, Up: Low level [Index]
Most of the functions of newra
do not create arrays, but instead they expect result arrays to be passed as arguments. This means that they must have been allocated before. However, there are a few functions, such as ra-copy
or ra-map
, that do create a result array. The type and shape of that result array is deduced from the source arguments, as follows.
'd
, however, the default type of the result array is #t
. Usually, a function will allow this default to be overriden with an explicit argument. If that argument is required, then #f
will select the default.
ra-copy
, then it follows that that is the shape of the result.)
For example:
(ra-map #t * ; shape '((0 1)) (ra-iota 2 1) ; shape '((#f #f) (1 3)) (ra-transpose (make-ra 9 '(1 3)) 1))
⇒
; shape of result is '((0 1) (1 3)) #%2:2@1:3((9 9 9) (18 18 18)
Next: Compatibility with old Guile arrays, Previous: Automatic result arrays, Up: Low level [Index]
One of the major reasons to use arrays instead of other Scheme data structures is that they let one pass a large amount of data through a C interface very efficiently. The data doesn’t need to be copied — one only needs to pass a pointer to the data, plus the lengths and the steps in some order. C doesn’t have a standard consolidated array type, so the particulars are variable. In any case, the required items can be obtained trivially from a newra
array object.
For example:
TODO
Previous: Foreign interface, Up: Low level [Index]
The functions ra->array
and array->ra
are provided to convert to and from newra
arrays and built-in Guile arrays. It is an error to use ra->array
on arrays whose root isn’t supported by the built-in arrays, or that have an unbounded axis. Except in those two cases, the conversion is transparent both ways, and the result always shares the root of the argument.
(define a (make-array 'o 2 3)) (define b (array->ra a)) (ra-set! b 'x 1 1) (array-set! a 'y 0 2) a
⇒ #2((o o y) (o x o))
b
⇒ #%2((o o y) (o x o))
<aseq>
-root arrays must be type converted before using ra->array
.
(ra->array (ra-copy #t (ra-i 2 3)))
⇒ #2((0 1 2) (3 4 5))
On dead axes, lengths can be set to 1 (with ra-singletonize
) to allow conversion with ra->array
or to other array systems that do singleton broadcasting.
(define a (ra-transpose (ra-i 2 3) 1 3)) a
⇒ #%4d:d:2:d:3((((0 1 2)) ((3 4 5))))
(ra-singletonize a)
⇒ #%4d:1:2:1:3((((0 1 2)) ((3 4 5))))
One important difference between the built-in array functions and newra
is that bounds matching in newra
is strict: finite bounds must be identical for axes to match, while for array-map!
, array-for-each
, array-copy!
, etc. the iteration range is the intersection of the ranges of the arguments7. newra
provides ra-clip
to match ranges easily.
(define a (make-ra-root (vector 'a 'b 'c 'd 'e 'f 'g))) ; range is 0..6 (define b (ra-reshape (ra-iota 4) 0 '(2 5))) ; range is 2..5 (ra-copy! a b)
⇒ Throw to key `mismatched-lens' with args `(7 4 at-dim 0)'.
(ra-copy! (ra-clip a b) b) a
⇒ #%1:7(a b 0 1 2 3 g)
NOTE This section is about a facility that hasn’t been implemented yet.
In array languages such as APL, scalar operations are implicitly extended to work on arrays, so one can just write (the equivalent of) (+ A B)
instead of (ra-map! (make-ra ...) + A B)
. The basic newra
iteration operations such as ra-map!
already perform rank extension of their arguments (so A
or B
can have a different rank from the result, as long as the prefix axes match). We still need ways to:
Next: Reference, Previous: High level, Up: newra
[Index]
Next: Common mistakes, Up: Hazards [Index]
If you come to newra
from another array language or library, you may want to be aware of some of these differences. See also the Cheatsheet.
Differences with built-in Guile arrays:
newra
map operations are rank-extending but require exact agreement of bounds, unlike operations on built-in arrays, which require exact rank agreement but admit overlapping bounds.
Differences with APL:
newra
is 0, as in ⎕io←0
. The lower bound isn’t global, but it may be different per axis.
newra
arrays of size 1 are not equivalent to scalars and always retain their rank. For example, (make-ra 99)
, (make-ra 99 1)
and (make-ra 99 1 1)
are all different from each other and from the scalar 99
, while in APL 99
, (1 ⍴ 99)
, and (1 1 ⍴ 99)
are all the same thing.
(ra-transpose a)
≡(ra-transpose a 0)
≡(ra-transpose a 0 1)
, but (assuming a
is of rank 2) ⍉a
≡2 1⍉a
. Please check the documentation for each function.
Differences with Fortran:
newra
is 0, not 1. Like in Fortran, the lower bound may be different for each axis of each array.
ra-ravel
) only support row-major order.
newra
uses prefix matching for rank extension on arguments on any rank, while Fortran only performs rank extension on scalar arguments.
Differences with NumPy:
newra
uses prefix matching for rank extension, while Numpy uses suffix matching. For example: FIXME.
newra
doesn’t use singleton broadcasting. Axes of length 1 only match axes of length 1. For example, (ra-map! (make-ra 0 2) + (make-ra 90 1) (make-ra 7 2))
is an error because the shapes (2), (1), (2) don’t agree.
Differences with Octave:
newra
is 0, not 1. Lower bounds may be 1 on a particular array (or particular axes of an array), but not globally.
newra
, so, for example, an array of rank 1 isn’t equivalent to an array of rank 2 with a single row (a ‘row vector’).
ra-ravel
) only support row-major order.
newra
uses prefix matching for rank extension on arguments on any rank, while Octave only performs rank extension on scalar arguments.
Next: Cheatsheet, Previous: Hazards, Up: newra
[Index]
Convert built-in Guile array a (anything that satisfies array?
) into a (newra)
array.
This function doesn’t create a copy of the array, but reuses the root (shared-array-root
) of a, that is, (eq? (ra-root (array->ra a)) (shared-array-root a))
is #t
.
(define a (make-array 'x 2 2)) (define b (array->ra v)) b
⇒ #%2:2:2((x x) (x x))
(ra-fill! b 'y) a
⇒ #2((y y) (y y))
See also: ra->array
.
Create dims for row-major order array (packed elements, last dimension changing fastest).
Each of the bounds may be a pair of bounds (lo hi) or a single length len, which implies zero lo.
(c-dims 2 3)
⇒ #(#<<dim> len: 2 lo: 0 step: 3> #<<dim> len: 3 lo: 0 step: 1>)
Placeholder for n full axes, used as argument to ra-from
or ra-amend!
. Without n, expand to fill the rank of the argument a of ra-from
or ra-amend!
.
Create an array of type #t
with the given bounds, filled with val.
(make-ra 0 '(2 3) 4)
⇒ #%2@2:2:4((0 0 0 0) (0 0 0 0))
See also: make-typed-ra
, ra-make-ra-new
, ra-shape
.
Create an array over a new root of the given type and size (according to dims), and fill it with value.
(make-ra-new 'u8 0 (c-dims 3 2))
⇒ #%2u8:3:2((0 0) (0 0) (0 0))
Create an array over the given root.
(make-ra-root (vector 1 2 3))
⇒ #%1d:3(1 2 3)
(make-ra-root (vector 1 2 3) (vector (make-dim 2)))
⇒ #%1d:2(1 2)
(make-ra-root (vector 1 2 3) (vector (make-dim 2)) 1)
⇒ #%1d:2(2 3)
Same as make-ra
, except that the result has the specified type.
See also: make-ra
, ra-make-ra-root
.
Slice a to the intersection of the bounds of a and b. If a and b have different ranks, only the common prefix of a is sliced.
(define f (make-ra " " '(-4 4) '(-5 3))) (define a (make-ra " A " '(-3 0) '(-4 1))) (define b (make-ra " B " '(-1 3) '(-1 2))) (ra-fill! (ra-clip a b) " x ") (ra-copy! (ra-clip f b) (ra-clip b f)) (ra-copy! (ra-clip f a) (ra-clip a f)) (ra-format f)
⇒
#%2@-4:9@-5:9───┬───┬───┬───┬───┬───┐ │ │ │ │ │ │ │ │ │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ A │ A │ A │ A │ A │ A │ │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ A │ A │ A │ A │ A │ A │ │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ A │ A │ A │ x │ x │ x │ B │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ A │ A │ A │ x │ x │ x │ B │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ │ │ │ B │ B │ B │ B │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ │ │ │ B │ B │ B │ B │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ │ │ │ B │ B │ B │ B │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ │ │ │ │ │ │ │ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┘
(ra-fill! (ra-clip f (ra-clip a b)) " o ") (ra-format f)
⇒
#%2@-4:9@-5:9───┬───┬───┬───┬───┬───┐ │ │ │ │ │ │ │ │ │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ A │ A │ A │ A │ A │ A │ │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ A │ A │ A │ A │ A │ A │ │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ A │ A │ A │ o │ o │ o │ B │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ A │ A │ A │ o │ o │ o │ B │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ │ │ │ B │ B │ B │ B │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ │ │ │ B │ B │ B │ B │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ │ │ │ B │ B │ B │ B │ │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ │ │ │ │ │ │ │ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┘
The result of ra-clip
always shares the root of its first argument a.
See also: Slicing, Compatibility with old Guile arrays.
Return the dimensions of a as a list, like ra-shape
, but the dimensions that have lower bound zero are represented by their length alone, instead of a two-element list (lo hi)
. This is a convenience for the common case when lower bounds are zero.
(ra-dimensions (make-ra 0 '(2 3) 4))
⇒ ((2 3) 4)
(ra-shape (make-ra 0 '(2 3) 4))
⇒ ((2 3) (0 3))
Pretty print array ra.
Each element x of ra is converted to a string using (fmt x)
, or (format #f fmt x)
8 if fmt is a string. fmt defauts to "~a"
. Elements that are arrays are formatted using ra-format
recursively.
If port is #f
, nothing is printed; instead, the function returns a rank-2 character array sc with the result of the printing. Otherwise the array is printed to port and the function returns unspecified values. The default for port is #t
, which works as current-output-port
.
If prefix? is true, the array print prefix ra-print-prefix
is printed over the first line of the printout if the rank of ra is 2 or greater, else it is printed on a separate line above the printout.
If compact? is true, the separators for the rank-0 cells are replaced by spaces and the separators for the rank-1 cells are omitted. This results in a more compact output at the cost of some clarity.
This function handles arrays of rank up to 8, or up to 10 in compact mode. The even dimensions (counting from the last one) are arranged horizontally, while the odd dimensions are arranged vertically. The dimensions are separated visually using box-drawing characters. This might look better or worse depending on the font.
A 0-array:
(ra-format (make-ra 'element))
⇒
#%0 element
A 1-array:
(ra-format (ra-i 4))
⇒
#%1d:4 │0│1│2│3│
Compare with the 2-array
(ra-format (ra-i 1 4))
⇒
#%2d:1:4┐ │0│1│2│3│ └─┴─┴─┴─┘
Another 2-array:
(ra-format (ra-i 3 4) #:prefix? #f)
⇒
┌─┬─┬──┬──┐ │0│1│ 2│ 3│ ├─┼─┼──┼──┤ │4│5│ 6│ 7│ ├─┼─┼──┼──┤ │8│9│10│11│ └─┴─┴──┴──┘
A 5-array:
(ra-format (ra-i 2 2 3 2 4) #:prefix? #f)
⇒
┃═══════════╦═══════════╦═══════════┃═══════════╦═══════════╦═══════════┃ ┃ 0│ 1│ 2│ 3║ 8│ 9│10│11║16│17│18│19┃48│49│50│51║56│57│58│59║64│65│66│67┃ ┃──┼──┼──┼──║──┼──┼──┼──║──┼──┼──┼──┃──┼──┼──┼──║──┼──┼──┼──║──┼──┼──┼──┃ ┃ 4│ 5│ 6│ 7║12│13│14│15║20│21│22│23┃52│53│54│55║60│61│62│63║68│69│70│71┃ ┃═══════════╬═══════════╬═══════════┃═══════════╬═══════════╬═══════════┃ ┃24│25│26│27║32│33│34│35║40│41│42│43┃72│73│74│75║80│81│82│83║88│89│90│91┃ ┃──┼──┼──┼──║──┼──┼──┼──║──┼──┼──┼──┃──┼──┼──┼──║──┼──┼──┼──║──┼──┼──┼──┃ ┃28│29│30│31║36│37│38│39║44│45│46│47┃76│77│78│79║84│85│86│87║92│93│94│95┃ ┃═══════════╩═══════════╩═══════════┃═══════════╩═══════════╩═══════════┃
The same 5-array in compact mode:
(ra-format (ra-i 2 2 3 2 4) #:prefix? #f #:compact? #t)
⇒
║───────────┬───────────┬───────────║───────────┬───────────┬───────────║ ║ 0 1 2 3│ 8 9 10 11│16 17 18 19║48 49 50 51│56 57 58 59│64 65 66 67║ ║ 4 5 6 7│12 13 14 15│20 21 22 23║52 53 54 55│60 61 62 63│68 69 70 71║ ║───────────┼───────────┼───────────║───────────┼───────────┼───────────║ ║24 25 26 27│32 33 34 35│40 41 42 43║72 73 74 75│80 81 82 83│88 89 90 91║ ║28 29 30 31│36 37 38 39│44 45 46 47║76 77 78 79│84 85 86 87│92 93 94 95║ ║───────────┴───────────┴───────────║───────────┴───────────┴───────────║
A nested array (example from [SRFI-163] ):
(ra-format (call-with-input-string "#%2@1:2@1:3((#%2((1 2) (3 4)) 9 #%2((3 4) (5 6))) (#%(42 43) #%2((8 7 6)) #%2((90 91) (100 101))))))" read))
⇒
#%2@1:2@1:3─────┬─────────┐ │#%2:2:2│ 9│ #%2:2:2│ ││1│2│ │ │ │3│4│ │ │├─┼─┤ │ │ ├─┼─┤ │ ││3│4│ │ │ │5│6│ │ │└─┴─┘ │ │ └─┴─┘ │ ├───────┼───────┼─────────┤ │#%1:2 │#%2:1:3│#%2:2:2─┐│ ││42│43│││8│7│6│││ 90│ 91││ │ │└─┴─┴─┘│├───┼───┤│ │ │ ││100│101││ │ │ │└───┴───┘│ └───────┴───────┴─────────┘
Looking at the return value when port is #f
:
(ra-format (ra-format (ra-i 2 3) #f #:prefix? #f) #:prefix? #f)
⇒
┌─┬─┬─┬─┬─┬─┬─┐ │┌│─│┬│─│┬│─│┐│ ├─┼─┼─┼─┼─┼─┼─┤ │││0│││1│││2│││ ├─┼─┼─┼─┼─┼─┼─┤ │├│─│┼│─│┼│─│┤│ ├─┼─┼─┼─┼─┼─┼─┤ │││3│││4│││5│││ ├─┼─┼─┼─┼─┼─┼─┤ │└│─│┴│─│┴│─│┘│ └─┴─┴─┴─┴─┴─┴─┘
Using a custom element formatter:
(ra-format (ra-map! (make-ra #f 4 4) sqrt (ra-reshape (ra-iota 20 -10) 0 4 4)) #:fmt (lambda (x) (format #f (cond ((real? x) "~4,2f") ((complex? x) "~4,2i") (else "~a")) x)))
⇒
#%2:4:4────┬──────────┬──────────┬──────────┐ │0.00+3.16i│0.00+3.00i│0.00+2.83i│0.00+2.65i│ ├──────────┼──────────┼──────────┼──────────┤ │0.00+2.45i│0.00+2.24i│0.00+2.00i│0.00+1.73i│ ├──────────┼──────────┼──────────┼──────────┤ │0.00+1.41i│0.00+1.00i│ 0.00│ 1.00│ ├──────────┼──────────┼──────────┼──────────┤ │ 1.41│ 1.73│ 2.00│ 2.24│ └──────────┴──────────┴──────────┴──────────┘
If any of the lengths of ra is 0, only the prefix is printed.
This function doesn’t handle large arrays in any particular way. User beware!
See also: ra-print
, *ra-print*
.
Create multidimensional index array with the given bounds.
The root is of type d
. The first upper bound that isn’t #f
may be #t
; this creates an unbounded axis.
(ra-i 2 3 4)
⇒ #%3d:2:3:4(((0 1 2 3) (4 5 6 7) (8 9 10 11)) ((12 13 14 15) (16 17 18 19) (20 21 22 23)))
See also: ra-iota
, ra-index-map!
. 9
Create rank-1 index array. The root is of type d
. lo defaults to 0 and step to 1.
(ra-iota 4 3 -1)
⇒ #%1d:4(3 2 1 0)
(ra-iota)
is unbounded both ways, e.g.
(ra-shape (ra-iota))
⇒ ((#f #f))
((ra-iota) -100000000000)
⇒ -100000000000
See also: ra-i
.
Concatenate arrays a ... along axis i. The arguments are prefix-matched and rank extended before concatenation. All axes must match, other than the concatenation axis.
This function always produces a new array, of the type given. If type is #f
then the type of the first a is used, unless that is d
, in which case #t
is used. The lower bound in the concatenation axis of the result is 0; the lower bounds of the arguments in the concatenation axis are ignored.
(ra-cat #f 1 #%1(a b) #%2((0 1) (2 3)))
⇒ #%2((a 0 1) (b 2 3))
See also: ra-scat
, ra-tile
, Concatenation.
Print an array to port. port defaults to (current-output-port)
.
This is the default array printer. The result is meant to be back-readable, although some special arrays are not supported yet.
See also: ra-format
, *ra-print*
, *ra-parenthesized-rank-zero*
.
Return the print prefix for array ra. This is a string that details the type, rank, and (optionally) the dimensions of ra, and is part of the default read syntax for arrays. This is the same syntax as that of the the built in Guile arrays, except that dims? defaults to true and #%
is used instead of #
.
This function is provided by the module (newra print)
.
(call-with-output-string (cut ra-print-prefix (make-ra 4 '(3 4) '(2 3)) <>))
⇒ "#%2@3:2@2:2"
(call-with-output-string (cut ra-print-prefix (make-ra 4 '(3 4) '(2 3)) <> #:dims? #t))
⇒ "#%2@3@2"
See also: ra-format
, Writing and reading.
Ravel axes [org ... org+n) of array a in row-major order. n defaults to the rank of a and org defaults to 0.
For example:
(ra-ravel (ra-i 2 3))
⇒ #%1d:6(0 1 2 3 4 5)
Consider this 3-array:
(ra-format (ra-i 2 3 4))
⇒
#%3d:2:3:4║──┬──┬──┬──║ ║0│1│ 2│ 3║12│13│14│15║ ║─┼─┼──┼──║──┼──┼──┼──║ ║4│5│ 6│ 7║16│17│18│19║ ║─┼─┼──┼──║──┼──┼──┼──║ ║8│9│10│11║20│21│22│23║ ║─┴─┴──┴──║──┴──┴──┴──║
Ravel axes 0..1:
(ra-format (ra-ravel (ra-i 2 3 4) 2)) ; or (ra-ravel ... 2 0)
⇒
#%2d:6:4─┬──┐ │ 0│ 1│ 2│ 3│ ├──┼──┼──┼──┤ │ 4│ 5│ 6│ 7│ ├──┼──┼──┼──┤ │ 8│ 9│10│11│ ├──┼──┼──┼──┤ │12│13│14│15│ ├──┼──┼──┼──┤ │16│17│18│19│ ├──┼──┼──┼──┤ │20│21│22│23│ └──┴──┴──┴──┘
Ravel axes 1..2:
(ra-format (ra-ravel (ra-i 2 3 4) 2 1))
⇒
#%2d:2:12┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ │ 0│ 1│ 2│ 3│ 4│ 5│ 6│ 7│ 8│ 9│10│11│ ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │12│13│14│15│16│17│18│19│20│21│22│23│ └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
To ravel other combinations of axes, use Transposition.
The full ravel of an array doesn’t necessarily result in a rank-1 ‘packed’ array, that is, one where the step is 1. If that is required, one can use (ra-ravel (ra-copy ra))
. 10.
See also: ra-reshape
, ra-tile
, Reshaping.
Reshape axis k of array a to bounds.
Each of the bounds may be an integer (a length) or a pair of integers (lower and upper bounds).
(define a (ra-i 4 3)) (ra-format a)
⇒
#%2d:4:3┐ │0│ 1│ 2│ ├─┼──┼──┤ │3│ 4│ 5│ ├─┼──┼──┤ │6│ 7│ 8│ ├─┼──┼──┤ │9│10│11│ └─┴──┴──┘
(ra-format (ra-reshape a 0 2 2))
⇒
#%3d:2:2:3─┬──║ ║0│1│2║6│ 7│ 8║ ║─┼─┼─║─┼──┼──║ ║3│4│5║9│10│11║ ║─┴─┴─║─┴──┴──║
The result of ra-reshape
always shares the root of a.
It is an error if the product of the lengths in bounds exceeds the length of axis k of a. For example
(ra-reshape (ra-i 8 2) 0 2 3)
⇒ #%3d:2:3:2(((0 1) (2 3) (4 5)) ((6 7) (8 9) (10 11))
; ok, 8 can be reshaped into 2·3
(ra-reshape (ra-i 8 2) 0 4 3)
⇒ error ; 8 cannot be reshaped into 4·3
ra-reshape
may be used to change either of the bounds of an axis, not only its length. For example
(ra-format (ra-i 2 3))
⇒
#%2d:2:3 │0│1│2│ ├─┼─┼─┤ │3│4│5│ └─┴─┴─┘
(ra-format (ra-reshape (ra-i 2 3) 0 '(1 2)))
⇒
#%2d@1:2:3 │0│1│2│ ├─┼─┼─┤ │3│4│5│ └─┴─┴─┘
Reverse the given axes of a.
(ra-reverse (ra-i 2 3) 0 1)
⇒ #%1d:2:3((5 4 3) (2 1 0))
The reversed array shares the root of a.
See also: ra-rotate
, ra-rotate!
.
(FIXME: not implemented) Rotate the first axis of a toward the lower indices (‘to the left’) n times. n may be any integer. The result has the type of a, unless that type is d
, in which case the result is of type #t
.
This function always returns a new array.
Example:
(ra-rotate 1 (ra-i 3 2))
⇒ #%1:3:2((2 3) (4 5) (0 1))
See also: ra-rotate!
, ra-reverse
.
Rotate in place the first axis of a to the left n times. n may be any integer. a must be writable. This function returns a.
Example:
(define a (ra-copy #t (ra-i 3 2))) (ra-rotate! 1 a) a
⇒ #%1:3:2((2 3) (4 5) (0 1))
See also: ra-rotate
, ra-reverse
.
Concatenate i-items of arrays a ... . The arguments are suffix-matched and rank extended before concatenation. All axes must match, other than the concatenation axis.
This function always produces a new array, of the type given. If type is #f
then the type of the first a is used, unless that is d
, in which case #t
is used. The lower bound in the concatenation axis of the result is 0; the lower bounds of the arguments in the concatenation axis are ignored.
(ra-scat #t 1 #%2((0 1) (2 3)) #%(a b))
⇒ #%2((0 1) (2 3) (a b))
See also: ra-cat
, ra-tile
, Concatenation.
Return the shape (a list of two-element lists, each containing the lower and upper bound of each axis) of array a.
(ra-shape (make-ra 0 '(2 3) 4))
⇒ ((2 3) (0 3))
See also: ra-dimensions
, make-ra
.
Replicate array a by inserting axes of bounds ... before axis k. If t is the shape of a, the shape of the result will be
[t₀ ... tₖ₋₁ s₀ ... tₖ ...]
Each of the bounds may be an integer (a length) or a pair of integers (lower and upper bounds).
(define a (ra-i 3 4)) (ra-format a)
⇒
#%2d:3:4──┐ │0│1│ 2│ 3│ ├─┼─┼──┼──┤ │4│5│ 6│ 7│ ├─┼─┼──┼──┤ │8│9│10│11│ └─┴─┴──┴──┘
(ra-format (ra-tile a 0 2))
⇒
#%3d:2:3:4║─┬─┬──┬──║ ║0│1│ 2│ 3║0│1│ 2│ 3║ ║─┼─┼──┼──║─┼─┼──┼──║ ║4│5│ 6│ 7║4│5│ 6│ 7║ ║─┼─┼──┼──║─┼─┼──┼──║ ║8│9│10│11║8│9│10│11║ ║─┴─┴──┴──║─┴─┴──┴──║
(ra-format (ra-tile a 1 2))
⇒
#%3d:3:2:4┬─┬─┬─║─┬─┬──┬──║ ║0│1│2│3║4│5│6│7║8│9│10│11║ ║─┼─┼─┼─║─┼─┼─┼─║─┼─┼──┼──║ ║0│1│2│3║4│5│6│7║8│9│10│11║ ║─┴─┴─┴─║─┴─┴─┴─║─┴─┴──┴──║
(ra-format (ra-tile a 2 2))
⇒
#%3d:3:4:2─┬──║ ║0│0║4│4║ 8│ 8║ ║─┼─║─┼─║──┼──║ ║1│1║5│5║ 9│ 9║ ║─┼─║─┼─║──┼──║ ║2│2║6│6║10│10║ ║─┼─║─┼─║──┼──║ ║3│3║7│7║11│11║ ║─┴─║─┴─║──┴──║
Either len or hi being #f
creates dead axes.
(define a (ra-tile (ra-i 2 2) 0 #f #f)) (define b (ra-transpose (ra-i 2 2) 2)) ; same thing
⇒ #%4d:d:d:2:2((((0 1) (2 3))))
The tiled array shares the root of a.
See also: ra-ravel
, ra-reshape
, Reshaping.
Transpose each axis 0, 1, ... of a to matching destination axes.
(ra-transpose (ra-i 2 3) 1 0)
⇒ #%1d:3:2((0 3) (1 4) (2 5))
The transposed array shares the root of a.
See also: ra-untranspose
, Transposition.
Transpose axes of a to matching destination axes 0, 1, ...
(ra-untranspose (ra-transpose (ra-i 2 3 4) 2 1 0) 2 1 0)
⇒ #%3d:2:3:4(((0 1 2 3) (4 5 6 7) (8 9 10 11)) ((12 13 14 15) (16 17 18 19) (20 21 22 23)))
but
(ra-transpose (ra-transpose (ra-i 2 3 4) 2 1 0) 2 1 0)
⇒ #%3d:4:2:3(((0 4 8) (12 16 20)) ((1 5 9) (13 17 21)) ((2 6 10) (14 18 22)) ((3 7 11) (15 19 23)))
The transposed array shares the root of a.
See also: ra-transpose
, Transposition.
Iterate over array a in unspecified order, assigning to each element the result of (op i₀ ...)
, where i₀ ...
are the indices of that element.
For example:
(ra-index-map! (make-ra #t 3 4) -) (ra-map! (make-ra #t 3 4) - (ra-iota) (ra-transpose (ra-iota) 1)) ; same thing
⇒ #%2:3:4((0 -1 -2 -3) (1 0 -1 -2) (2 1 0 -1))
Iterate over the k-frames of arrays a ..., applying op to the respective slices. The arguments a ... must have matching shapes over their k-frames.
Note that it isn’t necessary for arguments a to have rank ≥ k. Arguments with rank < k are rank-extended and the corresponding arguments are 0-cells. For example:
(ra-slice-for-each 1 (lambda (a b) (display (list (a) (b)))) (make-ra-root #(a b)) (ra-i 2 3))
⇒ (a #%1d:3(0 1 2))(b #%1d:3(3 4 5))
(ra-slice-for-each 2 (lambda (a b) (display (list (a) (b)))) (make-ra-root #(a b)) (ra-i 2 3))
⇒ (a 0)(a 1)(a 2)(b 3)(b 4)(b 5)
See also: ra-map!
, ra-for-each
, Iteration.
Iterate over arrays dst a ... applying op to the respective elements in a, and storing the result in the respective element of dst. The arguments must have matching shapes and the type of dst must be compatible with the results of op.
This is equivalent to
(apply ra-slice-for-each (rank dst) (lambda (dst . a) (ra-set! dst (apply op (map ra-ref a)))) dst a)
See also: ra-map
, ra-for-each
, ra-slice-for-each
, Iteration.
Same as ra-map!
, but create the result array from the arguments. Unlike ra-map!
, this function requires at least one source argument.
The type of dst is type unless that is #f
, in which case the type of a0 is used, unless that is 'd
, in which case the #f
is used. For the computation of the shape of dst see Automatic result arrays.
(ra-format (ra-map 'f64 + (ra-iota 3 1) (ra-i 3 4)))
⇒
#%2f64:3:4┬────┬────┐ │ 1.0│ 2.0│ 3.0│ 4.0│ ├────┼────┼────┼────┤ │ 6.0│ 7.0│ 8.0│ 9.0│ ├────┼────┼────┼────┤ │11.0│12.0│13.0│14.0│ └────┴────┴────┴────┘
Copy src to dst. The arguments must have matching shapes and be of compatible types.
For valid arguments, this is equivalent to any one of
(ra-map! dst identity src) (ra-amend! dst src)
Create a new array of type type and copy src into it. If type is #f
or isn’t given, use the type of src, unless that type is d
, in which case the result is of type #t
.
Swap the contents of a and b. The swap is executed in unspecified order, so the effect on a and b is undefined if a and b share storage.
(ra-swap! (make-ra 2 3) (make-typed-ra 'f64 -1 3))
⇒
#%1:3(-1.0 -1.0 -1.0)
See also ra-swap-in-order!
ra-copy!
.
Swap the contents of a and b. The swap is executed in row-major order.
Assign value to each element of dst. The arguments must be of compatible types.
This is equivalent to any one of
(ra-map! dst (const value)) (ra-copy! dst (make-ra value)) (ra-amend! dst (make-ra value)) (ra-amend! dst value) ; as long as value isn't an array
Compare
(ra-fill! (make-ra #f 2 2) (make-ra 'x))
⇒ #%2:2:2((#%0(x) #%0(x)) (#%0(x) #%0(x)))
(ra-amend! (make-ra #f 2 2) 'x)
⇒ #%2:2:2((x x) (x x))
(ra-amend! (make-ra #f 2 2) (make-ra 'x))
⇒ #%2:2:2((x x) (x x))
(ra-amend! (make-ra #f 2 2) (make-ra (make-ra 'x)))
⇒ #%2:2:2((#%0(x) #%0(x)) (#%0(x) #%0(x)))
Check whether axes [org ... org+n) of a are in row-major order. By default, check that the whole array is in row-major order, and additionally that the step on the last axis is 1 (i.e. the array is ‘packed’).
(ra-order-c? a n org)
implies (eq? (ra-root a) (ra-root (
. Note that the stronger condition ra-ravel
a n org)))(ra-order-c? a)
is not necessary for (eq? (ra-root a) (ra-root (ra-ravel a)))
to hold.
Look up array element. It is an error if the number of i ... doesn’t match the rank of a.
Assign array element. It is an error if the number of i ... doesn’t match the rank of a.
Look up array cell.
This function returns a view of a with rank k equal to the rank of a minus the number of i ..., even if that is 0.
It is an error if the number of i ... exceeds the rank of a.
Look up array cell.
Let k be the rank of a minus the number of i .... If k is zero, return the array element at i ..., like ra-ref
; else return a k-view of a, like ra-slice
.
It is an error if the number of i ... exceeds the rank of a.
(define a (ra-copy (ra-i 3 2))) (ra-cell a 0) (ra-slice a 0) ; same thing
⇒ #%1(0 1 2)
(ra-slice a 0 1)
⇒ #%0(4)
(ra-cell a 0 1) (ra-ref a 0 1) ; same thing
⇒ 4
Return #t
if the arrays a ... have the same shapes and types and their
corresponding elements are all equal?
to each other, or #f
otherwise.
Note that this function isn’t rank extending; the shapes of the arguments must be the same, not just match. Here’s a rank-extending version:
(import (ice-9 control)) (define (ra-equal?* . a) (let/ec exit (apply ra-for-each (lambda a (unless (apply equal? a) (exit #f))) a) #t))
See also: ra-for-each
, ra-fold
, ra-every
, ra-any
.
See also: ra-slice-for-each
, ra-map
, Iteration.
Outer product slice of a by indices i ...
The shape of b is the concatenation of the shapes of i... and the contents are obtained by looking up in each dimension of a by the indices i, that is
b(j₀₀ j₀₁ ... j₁₀ j₁₁ ...) = a(i₀(j₀₀ j₀₁ ...) i₁(j₁₀ j₁₁ ...) ...)
where i : i₀ i₁ ... The special value #t
is understood as the full range of a on that axis.
Additionally, if each of the i ... is one of
#t
d
then the result b shares the root of a. In all other cases a new root is allocated for the result. For example
(define a (list->ra 2 '((1 2 3) (a b c)))) (define b (ra-from a #t (ra-iota 3 2 -1))) ; same as (ra-reverse a 1) b
⇒ #%2:2:3((3 2 1) (c b a))
(eq? (ra-root a) (ra-root b))
⇒ #t
(define c (ra-from a #t (list->ra 1 '(2 1 0)))) b
⇒ #%2:2:3((3 2 1) (c b a))
(eq? (ra-root a) (ra-root c))
⇒ #f
Unbounded indices aren’t treated especially, so they are only valid if the relevant axis of ra is itself unbounded.
(ra-i #t 4)
⇒
#%2d:f:4──┐ │0│1│ 2│ 3│ ├─┼─┼──┼──┤ │4│5│ 6│ 7│ ├─┼─┼──┼──┤ │8│9│10│11│ ...........
(ra-from (ra-i #t 4) (ra-iota #f 0 2))
⇒
#%2d:f:4─┬──┐ │ 0│ 1│ 2│ 3│ ├──┼──┼──┼──┤ │ 8│ 9│10│11│ ├──┼──┼──┼──┤ │16│17│18│19│ .............
If the result would have rank 0, then an array element is returned, and not a 0-rank view of a. Use ra-amend!
to assign to cells of an array regardless of whether they are of 0-rank or higher.
The type of b is the same as that of a, with the only exception that if the type of a is d
and the root of b cannot be shared with the root of a, then the type of b is #t
.
Like ra-from
, but always return a newly allocated array. This is equivalent to (ra-copy (ra-from a i ...))
, but it doesn’t incur a second copy in case ra-from
already allocates a new array.
See also: ra-from
.
Copy c to the outer product slice of a by indices i ...
a(i₀(j₀₀ j₀₁ ...) i₁(j₁₀ j₁₁ ...) ...) ← c(j₀₀ j₀₁ ... j₁₀ j₁₁ ...)
where i : i₀ i₁ ...
This is equivalent to (ra-copy! (ra-from a i ...) c)
if (ra-from a i ...)
would
return a shared ra of a, but it also works in other cases, as long as a is
writable. i may take any of the special values accepted by ra-from
.
(define a (list->ra 1 '(1 2 3))) (define x 1) (define y (array->ra #(1))) (ra-amend! a 9 x) ; modifies a (ra-amend! a (array->ra #0(9)) x) ; same thing (ra-copy! (ra-from a x) (array->ra #0(9))) ; modifies a (ra-amend! a 9 y) ; modifies a (ra-copy! (ra-from a y) (array->ra #0(9))) ; (ra-from a y) is a new array, so a is NOT modified (ra-amend! (array->ra #(2 3 4)) 9 1) ; error, (array->ra #(2 3 4)) is not mutable
If i contains repeated indices or the steps of a make it so that the same elements of a
are referenced more than once, then the value that ends up in a may correspond to any of the
indices that match those elements. newra
will not check that each element of a is represented uniquely in its root vector.
(ra-amend! a (array->ra #(6 7 8)) (ra-tile (array->ra #0(1)) 0 3))
⇒ #%1:3(1 8 3) ; do not rely on 8 ending up there
This function returns the modified array a.
See also: ra-from
, ra-copy!
ra-cell
ra-ref
ra-slice
ra-set!
.
Convert (newra
) array a into built-in Guile array.
This function does not create a copy of the array, but reuses the root (ra-root
) of a, that is, (eq? (ra-root a) (shared-array-root (ra->array a)))
is #t
.
Not all arrays can be converted into built-in Guile arrays. For example, type d
arrays, or arrays with unbounded axes, are not convertible.
(ra->array (ra-i 3)) ; error, type d not convertible (ra->array (ra-copy (ra-i 3))) ; ok, (ra-copy (ra-i 3)) has type #t (ra->array (ra-transpose (ra-i 2 3) 1)) ; error, not convertible (ra->array (ra-singletonize (ra-transpose (ra-i 2 3) 1))) ; ok
See also: array->ra
, ra-singletonize
, ra-copy!
.
Return an array with the same root and dimensions as a, except that dead axes (axes with step 0 and undefined length) have their length set to 1.
(ra-dimensions (ra-transpose (ra-i 2 3) 1))
⇒ (#f 2 3)
(ra-dimensions (ra-singletonize (ra-transpose (ra-i 2 3) 1)))
⇒ (1 2 3)
(ra-dimensions (ra-singletonize (ra-tile (ra-i 2 3) 0 4)))
⇒ (4 2 3) ; no change
Next: Indices, Previous: Cheatsheet, Up: newra
[Index]
[Abr70] | Philip S. Abrams. An APL machine. Technical report SLAC-114 UC-32 (MISC), Stanford Linear Accelerator Center, Stanford University, Stanford, CA, USA, February 1970. |
[Ber87] | Robert Bernecky. An introduction to function rank. ACM SIGAPL APL Quote Quad, 18(2):39–43, December 1987. |
[bli17] | The Blitz++ meta-template library. http://blitz.sourceforge.net, November 2017. |
[Cha86] | Gregory J. Chaitin. Physics in APL2, June 1986. |
[FI68] | Adin D. Falkoff and Kenneth Eugene Iverson. APL\360 User’s manual. IBM Thomas J. Watson Research Center, August 1968. |
[FI73] | Adin D. Falkoff and Kenneth Eugene Iverson. The design of APL. IBM Journal of Research and Development, 17(4):5–14, July 1973. |
[FI78] | Adin D. Falkoff and Kenneth Eugene Iverson. The evolution of APL. ACM SIGAPL APL, 9(1):30– 44, 1978. |
[J S] | J Primer. J Software, https://www.jsoftware.com/help/primer/contents.htm, November 2017. |
[Mat] | MathWorks. MATLAB documentation, https://www.mathworks.com/help/matlab/, November 2017. |
[num17] | NumPy. http://www.numpy.org, November 2017. |
[Ric08] | Henry Rich. J for C programmers, February 2008. |
[SSM14] | Justin Slepak, Olin Shivers, and Panagiotis Manolios. An array-oriented language with static rank polymorphism. In Z. Shao, editor, ESOP 2014, LNCS 8410, pages 27–46, 2014. |
[Vel01] | Todd Veldhuizen. Blitz++ user’s guide, February 2001. |
[Wad90] | Philip Wadler. Deforestation: transforming programs to eliminate trees. Theoretical Computer Science, 73(2): 231–248, June 1990. https://doi.org/10.1016/0304-3975%2890%2990147-A |
[SRFI-4] | Marc Feeley. SRFI-4: Homogeneous numeric vector datatypes, May 1999. https://srfi.schemers.org/srfi-4/srfi-4.html |
[SRFI-25] | Jussi Piitulainen. SRFI-25: Multi-dimensional array primitives, May 2002. https://srfi.schemers.org/srfi-25/srfi-25.html |
[SRFI-122] | Bradley J. Lucier. SRFI-122: Nonempty intervals and generalized arrays, December 2016. https://srfi.schemers.org/srfi-122/srfi-122.html |
[SRFI-160] | John Cowan and Shiro Kawai. SRFI-160: Homogeneous numeric vector libraries, November 2020. https://srfi.schemers.org/srfi-160/srfi-160.html |
[SRFI-163] | Per Bothner. SRFI-163: Enhanced array literals, January 2019. https://srfi.schemers.org/srfi-163/srfi-163.html |
[SRFI-164] | Per Bothner. SRFI-164: Enhanced multi-dimensional arrays, August 2019. https://srfi.schemers.org/srfi-164/srfi-164.html |
Jump to: | *
,
{
⊖
⌽
⍉
⍋
⍴
A B C D F G I L M N O P R S T U |
---|
Jump to: | *
,
{
⊖
⌽
⍉
⍋
⍴
A B C D F G I L M N O P R S T U |
---|
(import (newra)) (define element (lambda i (format #f "A(~{~a~^, ~})" i))) (define (ti k) (ra-transpose (ra-iota) k)) (ra-format (ra-map! (make-ra #f 3 3) element (ti 0) (ti 1)) #:prefix? #f) (ra-format (ra-map! (make-ra #f 2 '(2 5) '(-2 0)) element (ti 0) (ti 1) (ti 2)) #:prefix? #f)
Cf. dope vector
Guile (before v1.8) used to offer dedicated operations to sum arrays, etc. but obviously that isn’t any kind of solution.
An exception is ra-index-map!
, where passing the indices is the purpose.
Note that this is not the same as (let ((d (vector-ref (ra-dims a) k))) (ra-iota (dim-len d) (dim-lo d)))
, because the lower bound of (ra-iota ...)
(not its content) is 0, not (dim-lo d)
, so the corresponding lower bound on the result array would also be 0, while #t
preserves the lower bound of a
.
An example of how using lower bounds other than 0 is generally worthless trouble, not least for the library author.
I decided against this approach for newra
because in my experience it results in errors going undetected much more often than it saves any work.
Cf. array-index
from [SRFI-164]
.
This works because ra-order-c?
is always true for the result of ra-copy
. Note that the ravel operation in (ra-ravel (ra-copy ra))
is free, while it might not be in (ra-copy (ra-ravel ra))
, so it is preferable, in principle, to copy first.
Cf. array-index-ref
from [SRFI-164]
.