by Bradley J. Lucier

This SRFI is currently in *draft* status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to `srfi-231@nospamsrfi.schemers.org`

. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

- Received: 2022-01-05
- 60-day deadline: 2022-03-08
- Draft #1 published: 2022-01-07
- Bradley Lucier's personal Git repo for this SRFI for reference while the SRFI is in
*draft*status.

This SRFI specifies an array mechanism for Scheme. Arrays as defined here are quite general; at their most basic, an array is simply a mapping, or function, from multi-indices of exact integers $i_0,\ldots,i_{d-1}$ to Scheme values. The set of multi-indices $i_0,\ldots,i_{d-1}$ that are valid for a given array form the *domain* of the array. In this SRFI, each array's domain consists of the cross product of nonempty intervals of exact integers $[l_0,u_0)\times[l_1,u_1)\times\cdots\times[l_{d-1},u_{d-1})$ of $\mathbb Z^d$, $d$-tuples of integers. Thus, we introduce a data type called $d$-*intervals*, or more briefly *intervals*, that encapsulates this notion. (We borrow this terminology from, e.g., Elias Zakon's Basic Concepts of Mathematics.) Specialized variants of arrays provide portable programs with efficient representations for common use cases.

This is a revised version of SRFI 179.

This SRFI was motivated by a number of somewhat independent notions, which we outline here and which are explained below.

- Provide a
**general API**(Application Program Interface) that specifies the minimal required properties of any given array, without requiring any specific implementation strategy from the programmer for that array. - Provide a
**single, efficient implementation for dense arrays**(which we call*specialized arrays*). - Provide
**useful array transformations**. - Separate
**the procedures that specify the work to be done**(`array-map`

,`array-outer-product`

, etc.) from**the procedures that actually do the work**(`array-copy`

,`array-assign!`

,`array-foldl`

, etc.). This approach**avoids temporary intermediate arrays**in computations. - Encourage
**bulk processing of arrays**rather than word-by-word operations.

This SRFI differs from the finalized SRFI 179 in the following ways:

`specialized-array-default-safe?`

and`specialized-array-default-mutable?`

are now SRFI 39 parameters.`array-copy`

no longer allows changing the domain of the result, use`(specialized-array-reshape (array-copy ...)`

instead.`new-domain`)`make-specialized-array`

now accepts an optional initial value with which to fill the new array.- The SRFI 179 procedures
`array-fold`

and`array-fold-right`

have been replaced by`array-foldl`

and`array-foldr`

, which follow the definition of the left and right folds in Ocaml and Haskell. The left folds of Ocaml and Haskell differ from the (left) fold of SRFI 1, so`array-foldl`

from this SRFI has different semantics to`array-fold`

from SRFI 179. `array-assign!`

now requires that the source and destination have the same domain. Use`specialized-array-reshape`

on the destination array to mimic the SRFI 179 version.- If the first argument to
`array-copy`

is a specialized array, then omitted arguments are taken from the argument array and do not default to`generic-storage-class`

,`(specialized-array-default-mutable?)`

, and`(specialized-array-default-safe?)`

. Thus, by default,`array-copy`

makes a true copy of a specialized array. - Procedures that generate useful permutations have been added:
`index-rotate`

,`index-first`

, and`index-last`

. `interval-rotate`

and`array-rotate`

have been removed; use`(array-permute A (index-rotate (array-dimension A) k))`

instead of`(array-rotate A k)`

.- Introduced new routines
`array-inner-product`

,`array-stack`

, and`array-append`

. - A new set of "Introductory remarks" surveys some of the more important procedures in this SRFI.

The next few sections talk perhaps too much about the mathematical ideas that underpin many of the procedures in this SRFI, so I discuss here some of the procedures and compare them to operations on spreadsheets, matrices, and imaging.

There are two procedures that simply create new arrays, and one procedure that converts a list to an array:

`make-array`

: Takes as arguments a specification of the valid indices $i\ j\ k$ etc. of the array, together with a Scheme procedure, which, when presented with indices in the valid range, computes the array element. The elements of the array are not precomputed and stored somewhere, the specified procedure is recalculated each time that element is needed. A procedure that modifies which element is returned at a given set of indices is allowed as a third argument. See the sparse matrix example below to see how this is useful. We call the result a*generalized array*.`make-specialized-array`

: Takes as an argument a specification of a valid range of indices and reserves a block of memory in which to store elements of the matrix; optionally, one can restrict which objects can be stored as elements in the array or generate code to precheck that all the indices are in range on each access, and to precheck that values stored as array elements actually comply with any given restrictions. Elements are stored in row-major order, as in C. We call the result a*specialized array*.`list->array`

: Takes as arguments a list and a specification of valid indices, returns a specialized array.

In the next group of procedures, the new and old arrays share elements, so modifications to one affects the others. Also, none of these procedures move any data: for specialized arrays they just change how the data are indexed, while for generalized arrays they manipulate the arguments of the getter and setter. For specialized arrays, these procedures can be combined in any way without increasing unreasonably the number of operations required to access an array element. The procedures that build a new array (`array-curry`

and `array-tile`

) return a *generalized array*.

`array-extract`

: Constructs a rectangular "window" or "view" into an existing array, like a rectangular region of a spreadsheet, or a submatrix of a matrix.`array-tile`

: Builds an array of subarrays of the original array, like breaking a large matrix into smaller matrices for block matrix operations.`array-translate`

: Slides an array around, like changing the zero-based indexing of C arrays to the 1-based indexing of Fortran arrays. If you wanted to compare two subimages of the same number of rows and columns of pixels, for example, you could use array-extract to select each of the subimages, and then use array-translate to overlay one on the other, i.e., to use the same indexing for both.`array-permute`

: Swaps rows, columns, sheets, etc., of the original array, like swapping rows and columns in a spreadsheet or transposing a matrix. The auxiliary routines`index-rotate`

,`index-first`

, and`index-last`

create commonly used permutations.`array-curry`

: Slices an array into a collection of arrays of smaller dimension; returns a new array containing those slices. Like looking at a collection of two-dimensional slices of a three dimensional CT scan or thinking of a matrix as a collection of rows. You could combine this operation with array-permute to think of a matrix as a collection of columns, or look at slices in different orientations of a three-dimensional CT scan. Thinking of a video as a one-dimensional sequence (in time) of two-dimensional stills (in space) is another example of currying.`array-reverse`

: Reverses the order of rows or columns (or both) of a spreadsheet. Like flipping an image vertically or horizontally.`array-sample`

: Accesses every second (or third, etc.) row or column, or both, of an array.

The next few procedures set up operations to be executed in the future. They build *generalized* arrays.

`array-map`

: Specifies an operation to be applied componentwise on arrays, so if

and`A`

are matrices,`B``(array-map +`

sets up a new generalized array that adds elements of the arrays componentwise. You can chain these operations, so have`A``B`)`(array-map + (array-map (lambda (`

without immediately computing and storing all the values of those arrays.`x`) (*`alpha x`))`A`)`B`)`array-outer-product`

: Applies an operation to all possible pairs of elements of two original arrays. Like considering an $m$-vector as a column vector and an $n$-vector as a row vector, and multiplying them together to compute an $m\times n$ matrix.`array-inner-product`

: Like APL's inner product; multiplying two matrices is an example of an operation implemented using`array-inner-product`

.

Then, there are procedures that *do* generate all elements of an array and either store them somewhere, or combine them in some way:

`array-copy`

: Evaluates the argument array at all valid indices and stores those values into a new specialized array.`array-assign!`

: Evaluates the argument array at all valid indices and assigns their values to the elements of an existing array. In the Gaussian Elimination example below, we combine`array-map`

,`array-outer-product`

,`array-extract`

, and`array-assign!`

to do one step of the elimination.`array-stack`

: Like taking the individually rendered frames of an animated movie and combining them in time to make a complete video. Can be considered a partial inverse to`array-curry`

. Returns a specialized array.`array-append`

: Like concatenating a number of images left to right, or top to bottom. Returns a specialized array.`array-foldl`

,`array-foldr`

,`array-reduce`

,`array-for-each`

,`array-any`

,`array-every`

, and`array->list`

: Evaluates all elements of an array (for`array-every`

and`array-any`

, as many as needed to know the result) and combine them in certain ways.

I hope this brief discussion gives a flavor for the design of this SRFI.

In a 1993 post to the news group comp.lang.scheme, Alan Bawden gave a simple implementation of multi-dimensional arrays in R4RS scheme. The only constructor of new arrays required specifying an initial value, and he provided the three low-level primitives `array-ref`

, `array-set!`

, and `array?`

, as well as `make-array`

and `make-shared-array`

. His arrays were defined on rectangular intervals in $\mathbb Z^d$ of the form $[l_0,u_0)\times\cdots\times [l_{d-1},u_{d-1})$. I'll note that his procedure `array-set!`

puts the value to be entered into the array at the front of the variable-length list of indices that indicate where to place the new value. He offered an intriguing way to "share" arrays in the form of a procedure `make-shared-array`

that took a mapping from a new interval of indices into the domain of the array to be shared. His implementation incorporated what he called an *indexer*, which was a procedure from the interval $[l_0,u_0)\times\cdots\times [l_{d-1},u_{d-1})$ to an interval $[0,N)$, where the *body* of the array consisted of a single Scheme vector of length $N$. Bawden called the mapping specified in `make-shared-array`

*linear*, but I prefer the term *affine*, as I explain later.

Mathematically, Bawden's arrays can be described as follows. We'll use the vector notation $\vec i$ for a multi-index $i_0,\ldots,i_{d-1}$. (Multi-indices correspond to Scheme `values`

.) Arrays will be denoted by capital letters $A,B,\ldots$, the domain of the array $A$ will be denoted by $D_A$, and the indexer of $A$, mapping $D_A$ to the interval $[0,N)$, will be denoted by $I_A$. Initially, Bawden constructs $I_A$ such that $I_A(\vec i)$ steps consecutively through the values $0,1,\ldots,N-1$ as $\vec i$ steps through the multi-indices $(l_0,\ldots,l_{d-2},l_{d-1})$, $(l_0,\ldots,l_{d-2},l_{d-1}+1)$, $\ldots$, $(l_0,\ldots,l_{d-2}+1,l_{d-1})$, etc., in lexicographical order, which means that if $\vec i$ and $\vec j$ are two multi-indices, then $\vec i<\vec j$ if and only if the least coordinate $k$ where $\vec i$ and $\vec j$ differ satisfies $\vec i_k<\vec j_k$. This ordering of multi-indices is also known as row-major order, which is used in the programming language C to order the elements of multi-dimensional arrays. In contrast, the programming language Fortran uses column-major order to order the elements of multi-dimensional arrays.

In `make-shared-array`

, Bawden allows you to specify a new $r$-dimensional interval $D_B$ as the domain of a new array $B$, and a mapping $T_{BA}:D_B\to D_A$ of the form $T_{BA}(\vec i)=M\vec i+\vec b$; here $M$ is a $d\times r$ matrix of integer values and $\vec b$ is a $d$-vector. So this mapping $T_{BA}$ is *affine*, in that $T_{BA}(\vec i)-T_{BA}(\vec j)=M(\vec i-\vec j)$ is *linear* (in a linear algebra sense) in $\vec i-\vec j$. The new indexer of $B$ satisfies $I_B(\vec i)=I_A(T_{BA}(\vec i))$.

A fact Bawden exploits in the code, but doesn't point out in the short post, is that $I_B$ is again an affine map, and indeed, the composition of *any* two affine maps is again affine.

We incorporate Bawden-style arrays into this SRFI, but extend them in one minor way that we find useful.

We introduce the notion of a *storage class*, an object that contains procedures that manipulate, store, check, etc., different types of values. The `generic-storage-class`

can manipulate any Scheme value, whereas, e.g., a `u1-storage-class`

can store only the values 0 and 1 in each element of a body.

We also require that our affine maps be one-to-one, so that if $\vec i\neq\vec j$ then $T(\vec i)\neq T(\vec j)$. Without this property, modifying the $\vec i$th component of $A$ would cause the $\vec j$th component to change.

Requiring the transformations $T_{BA}:D_B\to D_A$ to be affine may seem esoteric and restricting, but in fact many common and useful array transformations can be expressed in this way. We give several examples below:

**Restricting the domain of an array:**If the domain of $B$, $D_B$, is a subset of the domain of $A$, then $T_{BA}(\vec i)=\vec i$ is a one-to-one affine mapping. We define`array-extract`

to define this common operation; it's like looking at a rectangular sub-part of a spreadsheet. We use it to extract the common part of overlapping domains of three arrays in an image processing example below.**Tiling an array:**For various reasons (parallel processing, optimizing cache localization, GPU programming, etc.) one may wish to process a large array as a number of subarrays of the same dimensions, which we call*tiling*the array. The procedure`array-tile`

returns a new array, each entry of which is a subarray extracted (in the sense of`array-extract`

) from the input array.**Translating the domain of an array:**If $\vec d$ is a vector of integers, then $T_{BA}(\vec i)=\vec i-\vec d$ is a one-to-one affine map of $D_B=\{\vec i+\vec d\mid \vec i\in D_A\}$ onto $D_A$. We call $D_B$ the*translate*of $D_A$, and we define`array-translate`

to provide this operation.**Permuting the coordinates of an array:**If $\pi$ permutes the coordinates of a multi-index $\vec i$, and $\pi^{-1}$ is the inverse of $\pi$, then $T_{BA}(\vec i)=\pi (\vec i)$ is a one-to-one affine map from $D_B=\{\pi^{-1}(\vec i)\mid \vec i\in D_A\}$ onto $D_A$. We provide`array-permute`

for this operation. Several procedures build commonly used permutations:`(index-rotate`

rotates`n``k`)

indices`n`

places to the left;`k``(index-first`

moves the $k$th of $n$ indices to be the first index, leaving the others in order; and`n``k`)`(index-last`

moves the $k$th of $n$ indices to be the last index, again leaving the others in order.`n``k`)**Currying an array:**Let's denote the cross product of two intervals $\text{Int}_1$ and $\text{Int}_2$ by $\text{Int}_1\times\text{Int}_2$; if $\vec j=(j_0,\ldots,j_{r-1})\in \text{Int}_1$ and $\vec i=(i_0,\ldots,i_{s-1})\in \text{Int}_2$, then $\vec j\times\vec i$, which we define to be $(j_0,\ldots,j_{r-1},i_0,\ldots,i_{s-1})$, is in $\text{Int}_1\times\text{Int}_2$. If $D_A=\text{Int}_1\times\text{Int}_2$ and $\vec j\in\text{Int}_1$, then $T_{BA}(\vec i)=\vec j\times\vec i$ is a one-to-one affine mapping from $D_B=\text{Int}_2$ into $D_A$. For each vector $\vec j$ we can compute a new array in this way; we provide`array-curry`

for this operation, which returns an array whose domain is $\text{Int}_1$ and whose elements are themselves arrays, each of which is defined on $\text{Int}_2$. Currying a two-dimensional array would be like organizing a spreadsheet into a one-dimensional array of rows of the spreadsheet.**Traversing some indices in a multi-index in reverse order:**Consider an array $A$ with domain $D_A=[l_0,u_0)\times\cdots\times[l_{d-1},u_{d-1})$. Fix $D_B=D_A$ and assume we're given a vector of booleans $F$ ($F$ for "flip?"). Then define $T_{BA}:D_B\to D_A$ by $i_j\to i_j$ if $F_j$ is`#f`

and $i_j\to u_j+l_j-1-i_j$ if $F_j$ is`#t`

. In other words, we reverse the ordering of the $j$th coordinate of $\vec i$ if and only if $F_j$ is true. $T_{BA}$ is an affine mapping from $D_B\to D_A$, which defines a new array $B$, and we can provide`array-reverse`

for this operation. Applying`array-reverse`

to a two-dimensional spreadsheet might reverse the order of the rows or columns (or both).**Uniformly sampling an array:**Assume that $A$ is an array with domain $[0,u_1)\times\cdots\times[0,u_{d-1})$ (i.e., an interval all of whose lower bounds are zero). We'll also assume the existence of vector $S$ of scale factors, which are positive exact integers. Let $D_B$ be a new interval with $j$th lower bound equal to zero and $j$th upper bound equal to $\operatorname{ceiling}(u_j/S_j)$ and let $T_{BA}(\vec i)_j=i_j\times S_j$, i.e., the $j$th coordinate is scaled by $S_j$. ($D_B$ contains precisely those multi-indices that $T_{BA}$ maps into $D_A$.) Then $T_{BA}$ is an affine one-to-one mapping, and we provide`interval-scale`

and`array-sample`

for these operations.

We make several remarks. First, all these operations could have been computed by specifying the particular mapping $T_{BA}$ explicitly, so that these procedures are in some sense "convenience" procedures. Second, because the composition of any number of affine mappings is again affine, accessing or changing the elements of a restricted, translated, curried, permuted array is no slower than accessing or changing the elements of the original array itself. Finally, we note that by combining array currying and permuting, say, one can come up with simple expressions of powerful algorithms, such as extending one-dimensional transforms to multi-dimensional separable transforms, or quickly generating two-dimensional slices of three-dimensional image data. Examples are given below.

Bawden-style arrays are clearly useful as a programming construct, but they do not fulfill all our needs in this area. An array, as commonly understood, provides a mapping from multi-indices $(i_0,\ldots,i_{d-1})$ of exact integers
in a nonempty, rectangular, $d$-dimensional interval $[l_0,u_0)\times[l_1,u_1)\times\cdots\times[l_{d-1},u_{d-1})$ (the *domain* of the array) to Scheme objects.
Thus, two things are necessary to specify an array: an interval and a mapping that has that interval as its domain.

Since these two things are often sufficient for certain algorithms, we introduce in this SRFI a minimal set of interfaces for dealing with such arrays.

Specifically, an array specifies a nonempty, multi-dimensional interval, called its *domain*, and a mapping from this domain to Scheme objects. This mapping is called the *getter* of the array, accessed with the procedure `array-getter`

; the domain of the array (more precisely, the domain of the array's getter) is accessed with the procedure `array-domain`

.

If this mapping can be changed, the array is said to be *mutable* and the mutation is effected
by the array's *setter*, accessed by the procedure `array-setter`

. We call an object of this type a mutable array. Note: If an array does not have a setter, then we call it immutable even though the array's getter might not be a "pure" procedure, i.e., the value it returns may not depend solely on the arguments passed to the getter.

In general, we leave the implementation of generalized arrays completely open. They may be defined simply by closures, or they may have hash tables or databases behind an implementation, one may read the values from a file, etc.

In this SRFI, Bawden-style arrays are called *specialized*. A specialized array may be either mutable or immutable.

Even if an array $A$ is not a specialized array, then it could be "shared" by specifying a new interval $D_B$ as the domain of a new array $B$ and an affine map $T_{BA}:D_B\to D_A$. Each call to $B$ would then be computed as $B(\vec i)=A(T_{BA}(\vec i))$.

One could again "share" $B$, given a new interval $D_C$ as the domain of a new array $C$ and an affine transform $T_{CB}:D_C\to D_B$, and then each access $C(\vec i)=A(T_{BA}(T_{CB}(\vec i)))$. The composition $T_{BA}\circ T_{CB}:D_C\to D_A$, being itself affine, could be precomputed and stored as $T_{CA}:D_C\to D_A$, and $C(\vec i)=A(T_{CA}(\vec i))$ can be computed with the overhead of computing a single affine transformation.

So, if we wanted, we could share generalized arrays with constant overhead by adding a single layer of (multi-valued) affine transformations on top of evaluating generalized arrays. Even though this could be done transparently to the user, we do not do that here; it would be a compatible extension of this SRFI to do so. We provide only the procedure `specialized-array-share`

, not a more general `array-share`

.

Certain ways of sharing generalized arrays, however, are relatively easy to code and not that expensive. If we denote `(array-getter `

by `A`)

, then if `A_`

is the result of `B``array-extract`

applied to

, then `A``(array-getter `

is simply `B`)

. Similarly, if `A_`

is a two-dimensional array, and `A`

is derived from `B`

by applying the permutation $\pi((i,j))=(j,i)$, then `A``(array-getter `

is `B`)`(lambda (i j) (`

. Translation and currying also lead to transformed arrays whose getters are relatively efficiently derived from `A_` j i))

, at least for arrays of small dimension.`A_`

Thus, while we do not provide for sharing of generalized arrays for general one-to-one affine maps $T$, we do allow it for the specific procedures `array-extract`

, `array-translate`

, `array-permute`

, `array-curry`

, `array-reverse`

, `array-tile`

, and `array-sample`

, and we provide relatively efficient implementations of these procedures for arrays of dimension no greater than four.

If

is an array, then we generally define `A`

to be `A_``(array-getter `

and `A`)

to be `A!``(array-setter `

. The latter notation is motivated by the general Scheme convention that the names of procedures that modify the contents of data structures end in `A`)

, while the notation for the getter of an array is motivated by the TeX notation for subscripts. See particularly the Haar transform example.`!`

- Should eager comprehensions in the style of SRFI 42 be added to this SRFI, as suggested by Jens Axel Søgaard? My opinion is yes, but I would need major help in designing and implementing such things.
- Should
`specialized-array-default-mutable?`

and`specialized-array-default-safe?`

be SRFI 39 parameters or R7RS parameters? Or would some other way of specifying, and changing, the default safety and mutability of specialized arrays be better? - Could
`array-elements-in-order?`

have a better name or description? - The naming convention is not entirely uniform; most array functions begin with
`array-`

but there are also`make-array`

,`make-interval`

,`make-storage-class`

, and`make-specialized-array`

`mutable-array?`

**Relationship to nonstrict arrays in Racket.**It appears that what we call simply arrays in this SRFI are called nonstrict arrays in the math/array library of Racket, which in turn was influenced by an array proposal for Haskell. Our "specialized" arrays are related to Racket's "strict" arrays.**Indexers.**The argument

to`new-domain->old-domain``specialized-array-share`

is, conceptually, the getter of a multi-valued array.**Source of procedure names.**The procedure`array-curry`

gets its name from the curry operator in programmingâ€”we are currying the getter of the array and keeping careful track of the domains.`interval-projections`

can be thought of as currying the characteristic procedure of the interval, encapsulated here as`interval-contains-multi-index?`

.**Choice of procedures on intervals.**The choice of procedures for both arrays and intervals was motivated almost solely by what I needed for arrays.**No empty intervals.**This SRFI considers arrays over only nonempty intervals of positive dimension. The author of this proposal acknowledges that other languages and array systems allow either zero-dimensional intervals or empty intervals of positive dimension, but prefers to leave such empty intervals as possibly compatible extensions to the current proposal.**Multi-valued arrays.**While this SRFI restricts attention to single-valued arrays, wherein the getter of each array returns a single value, allowing multi-valued immutable arrays would be a compatible extension of this SRFI.**No low-level specialized array constructor.**While the author of the SRFI uses mainly`(make-array ...)`

,`array-map`

, and`array-copy`

to construct arrays, and while there are several other ways to construct arrays, there is no really low-level interface given for constructing specialized arrays (where one specifies a body, an indexer, etc.). It was felt that certain difficulties, some surmountable (such as checking that a given body is compatible with a given storage class) and some not (such as checking that an indexer is indeed affine), made a low-level interface less useful. At the same time, the simple`(make-array ...)`

mechanism is so general, allowing one to specify getters and setters as general procedures, as to cover nearly all needs.

- Miscellaneous Procedures
- translation?, permutation?, index-rotate, index-first, index-last.
- Intervals
- make-interval, interval?, interval-dimension, interval-lower-bound, interval-upper-bound, interval-lower-bounds->list, interval-upper-bounds->list, interval-lower-bounds->vector, interval-upper-bounds->vector, interval=, interval-volume, interval-subset?, interval-contains-multi-index?, interval-projections, interval-for-each, interval-dilate, interval-intersect, interval-translate, interval-permute, interval-scale, interval-cartesian-product.
- Storage Classes
- make-storage-class, storage-class?, storage-class-getter, storage-class-setter, storage-class-checker, storage-class-maker, storage-class-copier, storage-class-length, storage-class-default, generic-storage-class, s8-storage-class, s16-storage-class, s32-storage-class, s64-storage-class, u1-storage-class, u8-storage-class, u16-storage-class, u32-storage-class, u64-storage-class, f8-storage-class, f16-storage-class, f32-storage-class, f64-storage-class, c64-storage-class, c128-storage-class.
- Arrays
- specialized-array-default-safe?, specialized-array-default-mutable?, make-array, array?, array-domain, array-getter, array-dimension, mutable-array?, array-setter, make-specialized-array, specialized-array?, array-storage-class, array-indexer, array-body, array-safe?, array-elements-in-order?, specialized-array-share, array-copy, array-curry, array-extract, array-tile, array-translate, array-permute, array-reverse, array-sample, array-outer-product, array-inner-product, array-map, array-for-each, array-foldl, array-foldr, array-reduce, array-any, array-every, array->list, list->array, array-assign!, array-append, array-stack, array-ref, array-set!, specialized-array-reshape.

We use `take`

and `drop`

from SRFI 1 to define various procedures.

This document refers to *translations* and *permutations*.
A translation is a vector of exact integers. A permutation of dimension $n$
is a vector whose entries are the exact integers $0,1,\ldots,n-1$, each occurring once, in any order.
We provide three procedures that return useful permutations.

**Procedure: **`translation? `

`object`

Returns `#t`

if

is a translation, and `object``#f`

otherwise.

**Procedure: **`permutation? `

`object`

Returns `#t`

if

is a permutation, and `object``#f`

otherwise.

**Procedure: **`index-rotate `

`n` `k`

Assumes that `n` is a positive exact integer and that `k` is an exact integer between 0 (inclusive) and `n` (exclusive). Returns a permutation that rotates `n` indices `k` places to the left:

```
(define (index-rotate n k)
(let ((identity-permutation (iota n)))
(list->vector (append (drop identity-permutation k)
(take identity-permutation k)))))
```

For example, `(index-rotate 5 3)`

returns `'#(3 4 0 1 2)`

. It is an error of the arguments do not satisfy these conditions.

**Procedure: **`index-first `

`n` `k`

Assumes that `n` is a positive exact integer and that `k` is an exact integer between 0 (inclusive) and `n` (exclusive). Returns a permutation of length `n` that moves index `k` (with count beginning at 0) to be first and leaves the other indices in order:

```
(define (index-first n k)
(let ((identity-permutation (iota n)))
(list->vector (cons k
(append (take identity-permutation k)
(drop identity-permutation (fx+ k 1)))))))
```

For example, `(index-first 5 3)`

returns `'#(3 0 1 2 4)`

. It is an error if the arguments do not satisfy these conditions.

**Procedure: **`index-last `

`n` `k`

Assumes that `n` is a positive exact integer and that `k` is an exact integer between 0 (inclusive) and `n` (exclusive). Returns a permutation of length `n` that moves index `k` (with count beginning at 0) to be last and leaves the other indices in order:

```
(define (index-last n k)
(let ((identity-permutation (iota n)))
(list->vector (append (take identity-permutation k)
(drop identity-permutation (fx+ k 1))
(list k)))))
```

For example, `(index-last 5 3)`

returns `'#(0 1 2 4 3)`

. It is an error if the arguments do not satisfy these conditions.

An interval represents the set of all multi-indices of exact integers
$i_0,\ldots,i_{d-1}$
satisfying
$l_0\leq i_0<u_0,\ldots,l_{d-1}\leq i_{d-1}<u_{d-1}$,
where the *lower bounds*
$l_0,\ldots,l_{d-1}$
and the *upper bounds*
$u_0,\ldots,u_{d-1}$
are exact integers. The positive integer $d$ is the *dimension*
of the interval. It is required that
$l_0<u_0,\ldots,l_{d-1}<u_{d-1}$.

Intervals are a data type distinct from other Scheme data types.

**Procedure: **`make-interval `

`arg1` #!optional `arg2`

Create a new interval.

and `arg1`

(if given) are nonempty vectors (of the same length) of exact integers.`arg2`

If

is not given, then the entries of `arg2`

must be positive, and they are taken as the `arg1`

of the interval, and `upper-bounds`

is set to a vector of the same length with exact zero entries.`lower-bounds`

If

is given, then `arg2`

is taken to be `arg1`

and `lower-bounds`

is taken to be `arg2`

, which must satisfy`upper-bounds`

`(< (vector-ref ``lower-bounds` i) (vector-ref `upper-bounds` i))

for
$0\leq i<{}$`(vector-length `

. It is an error if
`lower-bounds`)

and `lower-bounds`

do not satisfy these conditions.`upper-bounds`

**Procedure: **`interval? `

`obj`

Returns `#t`

if

is an interval, and `obj``#f`

otherwise.

**Procedure: **`interval-dimension `

`interval`

If

is an interval built with `interval`

`(make-interval ``lower-bounds` `upper-bounds`)

then `interval-dimension`

returns `(vector-length `

. It is an error to call `lower-bounds`)`interval-dimension`

if

is not an interval.`interval`

**Procedure: **`interval-lower-bound `

`interval` `i`

**Procedure: **`interval-upper-bound `

`interval` `i`

If

is an interval built with `interval`

`(make-interval ``lower-bounds` `upper-bounds`)

and

is an exact integer that satisfies`i`

`$0 \leq i<$ ``(vector-length ``lower-bounds`)

,

then `interval-lower-bound`

returns
`(vector-ref `

and `lower-bounds` `i`)`interval-upper-bound`

returns
`(vector-ref `

. It is an error to call `upper-bounds` `i`)`interval-lower-bound`

or `interval-upper-bound`

if

and `interval`

do not satisfy these conditions.`i`

**Procedure: **`interval-lower-bounds->list `

`interval`

**Procedure: **`interval-upper-bounds->list `

`interval`

If

is an interval built with `interval`

`(make-interval ``lower-bounds` `upper-bounds`)

then `interval-lower-bounds->list`

returns `(vector->list `

and `lower-bounds`)`interval-upper-bounds->list`

returns `(vector->list `

. It is an error to call
`upper-bounds`)`interval-lower-bounds->list`

or `interval-upper-bounds->list`

if

does not satisfy these conditions.`interval`

**Procedure: **`interval-lower-bounds->vector `

`interval`

**Procedure: **`interval-upper-bounds->vector `

`interval`

If

is an interval built with `interval`

`(make-interval ``lower-bounds` `upper-bounds`)

then `interval-lower-bounds->vector`

returns a copy of

and `lower-bounds``interval-upper-bounds->vector`

returns a copy of

. It is an error to call
`upper-bounds``interval-lower-bounds->vector`

or `interval-upper-bounds->vector`

if

does not satisfy these conditions.`interval`

**Procedure: **`interval-volume `

`interval`

If

is an interval built with `interval`

`(make-interval ``lower-bounds` `upper-bounds`)

then, assuming the existence of `vector-map`

, `interval-volume`

returns

`(apply * (vector->list (vector-map - ``upper-bounds` `lower-bounds`)))

It is an error to call `interval-volume`

if

does not satisfy this condition.`interval`

**Procedure: **`interval= `

`interval1` `interval2`

If

and `interval1`

are intervals built with `interval2`

`(make-interval ``lower-bounds1` `upper-bounds1`)

and

`(make-interval ``lower-bounds2` `upper-bounds2`)

respectively, then `interval=`

returns

`(and (equal? ``lower-bounds1` `lower-bounds2`) (equal? `upper-bounds1` `upper-bounds2`))

It is an error to call `interval=`

if

or `interval1`

do not satisfy this condition.`interval2`

**Procedure: **`interval-subset? `

`interval1` `interval2`

If

and `interval1`

are intervals of the same dimension $d$, then `interval2``interval-subset?`

returns `#t`

if

`(>= (interval-lower-bound ``interval1` j) (interval-lower-bound `interval2` j))

and

`(<= (interval-upper-bound ``interval1` j) (interval-upper-bound `interval2` j))

for all $0\leq j<d$, otherwise it returns `#f`

. It is an error if the arguments do not satisfy these conditions.

**Procedure: **`interval-contains-multi-index? `

`interval` `index-0` `index-1` `...`

If

is an interval with dimension $d$ and `interval`

, `index-0`

, ..., is a multi-index of length $d$,
then `index-1``interval-contains-multi-index?`

returns `#t`

if

`(interval-lower-bound`

$\leq$intervalj)`$<$`

index-j`(interval-upper-bound`

intervalj)

for $0\leq j < d$, and `#f`

otherwise.

It is an error to call `interval-contains-multi-index?`

if

and `interval`

,..., do not satisfy this condition.`index-0`

**Procedure: **`interval-projections `

`interval` `right-dimension`

Conceptually, `interval-projections`

takes a $d$-dimensional interval
$[l_0,u_0)\times [l_1,u_1)\times\cdots\times[l_{d-1},u_{d-1})$
and splits it into two parts

$[l_0,u_0)\times\cdots\times[l_{d-\text{right-dimension}-1},u_{d-\text{right-dimension}-1})$

and

$[l_{d-\text{right-dimension}},u_{d-\text{right-dimension}})\times\cdots\times[l_{d-1},u_{d-1})$

This procedure, the inverse of Cartesian products or cross products of intervals, is used to keep track of the domains of curried arrays.

More precisely, if

is an interval and `interval`

is an exact integer that satisfies `right-dimension``0 < `

then `right-dimension` < `d``interval-projections`

returns two intervals:

```
(let ((left-dimension
(- (interval-dimension interval right-dimension)))
(lowers
(interval-lower-bounds->list interval))
(uppers
(interval-upper-bounds->list interval)))
(values
(make-interval
(list->vector (take lowers left-dimension))
(list->vector (take uppers left-dimension)))
(make-interval
(list->vector (drop lowers left-dimension))
(list->vector (drop uppers left-dimension)))))
```

It is an error to call `interval-projections`

if its arguments do not satisfy these conditions.

**Procedure: **`interval-for-each `

`f` `interval`

This procedure assumes that

is an interval and `interval`

is a procedure whose domain includes elements of `f`

. It is an error to call
`interval``interval-for-each`

if

and `interval`

do not satisfy these conditions.`f`

`interval-for-each`

calls

with each multi-index of `f`

as arguments, all in lexicographical order.`interval`

**Procedure: **`interval-dilate `

`interval` `lower-diffs` `upper-diffs`

If

is an interval with
lower bounds $\ell_0,\dots,\ell_{d-1}$ and
upper bounds $u_0,\dots,u_{d-1}$, and `interval`

is a vector of exact integers $L_0,\dots,L_{d-1}$ and `lower-diffs`

is a vector of exact integers $U_0,\dots,U_{d-1}$, then `upper-diffs``interval-dilate`

returns a new interval with
lower bounds $\ell_0+L_0,\dots,\ell_{d-1}+L_{d-1}$ and
upper bounds $u_0+U_0,\dots,u_{d-1}+U_{d-1}$, as long as this is a
nonempty interval. It is an error if the arguments do not satisfy these conditions.

Examples:

```
(interval=
(interval-dilate (make-interval '#(100 100))
'#(1 1) '#(1 1))
(make-interval '#(1 1) '#(101 101))) => #t
(interval=
(interval-dilate (make-interval '#(100 100))
'#(-1 -1) '#(1 1))
(make-interval '#(-1 -1) '#(101 101))) => #t
(interval=
(interval-dilate (make-interval '#(100 100))
'#(0 0) '#(-50 -50))
(make-interval '#(50 50))) => #t
(interval-dilate
(make-interval '#(100 100))
'#(0 0) '#(-500 -50)) => error
```

**Procedure: **`interval-intersect `

`interval-1` `interval-2` `...`

If all the arguments are intervals of the same dimension and they have a nonempty intersection,
then `interval-intersect`

returns that intersection; otherwise it returns `#f`

.

It is an error if the arguments are not all intervals with the same dimension.

**Procedure: **`interval-translate `

`interval` `translation`

If

is an interval with
lower bounds $\ell_0,\dots,\ell_{d-1}$ and
upper bounds $u_0,\dots,u_{d-1}$, and `interval`

is a translation with entries $T_0,\dots,T_{d-1}$
, then `translation``interval-translate`

returns a new interval with
lower bounds $\ell_0+T_0,\dots,\ell_{d-1}+T_{d-1}$ and
upper bounds $u_0+T_0,\dots,u_{d-1}+T_{d-1}$.
It is an error if the arguments do not satisfy these conditions.

One could define `(interval-translate interval translation)`

by `(interval-dilate interval translation translation)`

.

**Procedure: **`interval-permute `

`interval` `permutation`

The argument

must be an interval, and the argument `interval`

must be a valid permutation with the same dimension as `permutation`

. It is an error if the arguments do not satisfy these conditions.`interval`

Heuristically, this procedure returns a new interval whose axes have been permuted in a way consistent with

.
But we have to say how the entries of `permutation`

are associated with the new interval.`permutation`

We have chosen the following convention: If the permutation is $(\pi_0,\ldots,\pi_{d-1})$, and the argument interval represents the cross product $[l_0,u_0)\times[l_1,u_1)\times\cdots\times[l_{d-1},u_{d-1})$, then the result represents the cross product $[l_{\pi_0},u_{\pi_0})\times[l_{\pi_1},u_{\pi_1})\times\cdots\times[l_{\pi_{d-1}},u_{\pi_{d-1}})$.

For example, if the argument interval represents $[0,4)\times[0,8)\times[0,21)\times [0,16)$ and the
permutation is `#(3 0 1 2)`

, then the result of `(interval-permute `

will be
the representation of $[0,16)\times [0,4)\times[0,8)\times[0,21)$.`interval` `permutation`)

**Procedure: **`interval-scale `

`interval` `scales`

If

is a $d$-dimensional interval $[0,u_1)\times\cdots\times[0,u_{d-1})$ with all lower bounds zero, and `interval`

is a length-$d$ vector of positive exact integers, which we'll denote by $\vec s$, then `scales``interval-scale`

returns the interval $[0,\operatorname{ceiling}(u_1/s_1))\times\cdots\times[0,\operatorname{ceiling}(u_{d-1}/s_{d-1}))$.

It is an error if

and `interval`

do not satisfy this condition.`scales`

**Procedure: **`interval-cartesian-product `

`interval` . `intervals`

Implements the Cartesian product of the intervals in `(cons `

. Returns`interval` `intervals`)

```
(make-interval
(list->vector
(apply append (map interval-lower-bounds->list (cons interval intervals))))
(list->vector
(apply append (map interval-upper-bounds->list (cons interval intervals)))))
```

It is an error if any argument is not an interval.

Conceptually, a storage-class is a set of procedures to manage the backing store of a specialized array. The procedures allow one to make a backing store, to get values from the store and to set new values, to return the length of the store, and to specify a default value for initial elements of the backing store. Typically, a backing store is a (heterogeneous or homogeneous) vector. A storage-class has a type distinct from other Scheme types.

**Procedure: **`make-storage-class `

`getter` `setter` `checker` `maker` `copier` `length` `default`

Here we assume the following relationships between the arguments of `make-storage-class`

. Assume that the "elements" of
the backing store are of some "type", either heterogeneous (all Scheme types) or homogeneous (of some restricted type).

`(`

returns a linearly addressed object containing`maker n``value`)

elements of value`n`

.`value``copier`may be #f or a procedure; if a procedure then if

and`to`

were created by`from`

, then`maker``(`

copies elements from`copier to at from start end`)

beginning at`from`

(inclusive) and ending at`start`

(exclusive) to`end`

beginning at`to`

. It is assumed that all the indices involved are within the domain of`at`

and`from`

, as needed. The order in which the elements are copied is unspecified.`to`- If

is an object created by`v``(`

and 0 <=`maker n value`)

<`i`

, then`n``(`

returns the current value of the`getter v i`)

'th element of`i`

, and`v``(`

.`checker`(`getter v i`)) => #t - If

is an object created by`v``(`

, 0 <=`maker n value`)

<`i`

, and`n``(`

, then`checker``val`) => #t`(`

sets the value of the`setter v i val`)

'th element of`i`

to`v`

.`val` - If

is an object created by`v``(`

then`maker n value`)`(`

returns`length v`)

.`n`

If the arguments do not satisfy these conditions, then it is an error to call `make-storage-class`

.

Note that we assume that

and `getter`

generally take `setter`*O*(1) time to execute.

**Procedure: **`storage-class? `

`m`

Returns `#t`

if

is a storage class, and `m``#f`

otherwise.

**Procedure: **`storage-class-getter `

`m`

**Procedure: **`storage-class-setter `

`m`

**Procedure: **`storage-class-checker `

`m`

**Procedure: **`storage-class-maker `

`m`

**Procedure: **`storage-class-copier `

`m`

**Procedure: **`storage-class-length `

`m`

**Procedure: **`storage-class-default `

`m`

If

is an object created by`m`

`(make-storage-class ``getter setter checker maker copier length default`)

then `storage-class-getter`

returns

, `getter``storage-class-setter`

returns

, `setter``storage-class-checker`

returns

, `checker``storage-class-maker`

returns

, `maker``storage-class-copier`

returns

, `copier``storage-class-length`

returns

, and `length``storage-class-default`

returns

. Otherwise, it is an error to call any of these procedures.`default`

**Variable: **`generic-storage-class`

**Variable: **`s8-storage-class`

**Variable: **`s16-storage-class`

**Variable: **`s32-storage-class`

**Variable: **`s64-storage-class`

**Variable: **`u1-storage-class`

**Variable: **`u8-storage-class`

**Variable: **`u16-storage-class`

**Variable: **`u32-storage-class`

**Variable: **`u64-storage-class`

**Variable: **`f8-storage-class`

**Variable: **`f16-storage-class`

**Variable: **`f32-storage-class`

**Variable: **`f64-storage-class`

**Variable: **`c64-storage-class`

**Variable: **`c128-storage-class`

`generic-storage-class`

is defined as if by

```
(define generic-storage-class
(make-storage-class vector-ref
vector-set!
(lambda (arg) #t)
make-vector
vector-copy!
vector-length
#f))
```

Implementations shall define `s`

for `X`-storage-class

=8, 16, 32, and 64 (which have default values 0 and
manipulate exact integer values between -2`X`^{X-1} and
2^{X-1}-1 inclusive),
`u`

for `X`-storage-class

=1, 8, 16, 32, and 64 (which have default values 0 and manipulate exact integer values between 0 and
2`X`^{X}-1 inclusive),
`f`

for `X`-storage-class

= 8, 16, 32, and 64 (which have default value 0.0 and manipulate 8-, 16-, 32-, and 64-bit floating-point numbers), and
`X``c`

for `X`-storage-class

= 64 and 128 (which have default value 0.0+0.0i and manipulate complex numbers with, respectively, 32- and 64-bit floating-point numbers as real and imaginary parts).`X`

Implementations with an appropriate homogeneous vector type should define the associated global variable using `make-storage-class`

, otherwise they shall define the associated global variable to `#f`

.

Arrays are a data type distinct from other Scheme data types.

**Parameter: **`specialized-array-default-safe?`

A parameter as specified in SRFI 39. Initially, `(specialized-array-default-safe?)`

returns `#f`

. It is an error to call `(specialized-array-default-safe? `

if `arg`)

is not a boolean.`arg`

**Parameter: **`specialized-array-default-mutable?`

A parameter as specified in SRFI 39. Initially, `(specialized-array-default-mutable?)`

returns `#t`

. It is an error to call `(specialized-array-default-mutable? `

if `arg`)

is not a boolean.`arg`

**Procedure: **`make-array `

`interval` `getter` [ `setter` ]

Assume first that the optional argument `setter`

is not given.

If

is an interval and `interval`

is a procedure from
`getter`

to Scheme objects, then `interval``make-array`

returns an array with domain

and getter `interval`

.`getter`

It is an error to call `make-array`

if

and `interval`

do not satisfy these conditions.`getter`

If now

is specified, assume that it is a procedure such that getter and setter satisfy: If`setter`

`(`

$\neq$i_{1},...,i_{n})`(`

j_{1},...,j_{n})

are elements of

and `interval`

`(getter ``j`_{1} ... `j`_{n}) => x

then "after"

`(setter v ``i`_{1} ... `i`_{n})

we have

`(getter ``j`_{1} ... `j`_{n}) => x

and

`(getter ``i`_{1},...,`i`_{n}) => v

Then `make-array`

builds a mutable array with domain

, getter `interval`

, and
setter `getter`

. It is an error to call `setter``make-array`

if its arguments do not satisfy these conditions.

Example:

```
(define a (make-array (make-interval '#(1 1) '#(11 11))
(lambda (i j)
(if (= i j)
1
0))))
```

defines an array for which `(array-getter a)`

returns 1 when i=j and 0 otherwise.

Example:

```
(define a ;; a sparse array
(let ((domain
(make-interval '#(1000000 1000000)))
(sparse-rows
(make-vector 1000000 '())))
(make-array
domain
(lambda (i j)
(cond ((assv j (vector-ref sparse-rows i))
=> cdr)
(else
0.0)))
(lambda (v i j)
(cond ((assv j (vector-ref sparse-rows i))
=> (lambda (pair)
(set-cdr! pair v)))
(else
(vector-set!
sparse-rows
i
(cons (cons j v)
(vector-ref sparse-rows i)))))))))
(define a_ (array-getter a))
(define a! (array-setter a))
(a_ 12345 6789) => 0.
(a_ 0 0) => 0.
(a! 1.0 0 0) => undefined
(a_ 12345 6789) => 0.
(a_ 0 0) => 1.
```

**Procedure: **`array? `

`obj`

Returns `#t`

if

is an array and `obj``#f`

otherwise.

**Procedure: **`array-domain `

`array`

**Procedure: **`array-getter `

`array`

If

is an array built by`array`

`(make-array ``interval` `getter` [`setter`])

(with or without the optional

argument) then `setter``array-domain`

returns

and `interval``array-getter`

returns

.
It is an error to call `getter``array-domain`

or `array-getter`

if

is not an array.`array`

Example:

```
(define a (make-array (make-interval '#(1 1) '#(11 11))
(lambda (i j)
(if (= i j)
1
0))))
(define a_ (array-getter a))
(a_ 3 3) => 1
(a_ 2 3) => 0
(a_ 11 0) => is an error
```

**Procedure: **`array-dimension `

`array`

Shorthand for `(interval-dimension (array-domain `

. It is an error to call this procedure if `array`))

is not an array.`array`

**Procedure: **`mutable-array? `

`obj`

Returns `#t`

if

is a mutable array and `obj``#f`

otherwise.

**Procedure: **`array-setter `

`array`

If

is an array built by`array`

`(make-array ``interval` `getter` `setter`)

then `array-setter`

returns

. It is an error to call `setter``array-setter`

if

is not a mutable array.`array`

**Procedure: **`make-specialized-array `

`interval` [ `storage-class` generic-storage-class ] [ `initial-value` (storage-class-default `storage-class` ) ] [ `safe?` (specialized-array-default-safe?) ]

Constructs a mutable specialized array from its arguments.

must be given as a nonempty interval. If given, `interval`

must be a storage class; if it is not given it defaults to `storage-class``generic-storage-class`

. If given,

must be a value that can be manipulated by `initial-value`

; if it is not given it defaults to `storage-class``(storage-class-default `

. If given, `storage-class`)

must be a boolean; if it is not given it defaults to the current value of `safe?``(specialized-array-default-safe?)`

.

The body of the result is constructed as

` ((storage-class-maker ``storage-class`)
(interval-volume `interval`)
`initial-value`)

The indexer of the resulting array is constructed as the lexicographical mapping of

onto the interval `interval``[0,(interval-volume `

.`interval`))

If

is `safe``#t`

, then the arguments of the getter and setter (including the value to be stored) of the resulting array are checked for correctness.

After correctness checking (if needed), `(array-getter `

is defined simply as `array`)

```
(lambda multi-index
((storage-class-getter
````storage-class`)
(array-body `array`)
(apply (array-indexer `array`) multi-index)))

and `(array-setter `

is defined as `array`)

```
(lambda (val . multi-index)
((storage-class-setter
````storage-class`)
(array-body `array`)
(apply (array-indexer `array`) multi-index)
val))

It is an error if the arguments of `make-specialized-array`

do not satisfy these conditions.

**Examples. **A simple array that can hold any type of element can be defined with `(make-specialized-array (make-interval '#(3 3)))`

. If you find that you're using a lot of unsafe arrays of unsigned 16-bit integers, one could define

```
(define (make-u16-array interval)
(make-specialized-array interval u16-storage-class 0 #f))
```

and then simply call, e.g., `(make-u16-array (make-interval '#(3 3)))`

.

**Procedure: **`specialized-array? `

`obj`

Returns `#t`

if

is a specialized-array, and `obj``#f`

otherwise. A specialized-array is an array.

**Procedure: **`array-storage-class `

`array`

**Procedure: **`array-indexer `

`array`

**Procedure: **`array-body `

`array`

**Procedure: **`array-safe? `

`array`

`array-storage-class`

returns the storage-class of

. `array``array-safe?`

is true if and only if the arguments of `(array-getter `

and `array`)`(array-setter `

(including the value to be stored in the array) are checked for correctness.`array`)

`(array-body `

is a linearly indexed, vector-like object (e.g., a vector, string, u8vector, etc.) indexed from 0.`array`)

`(array-indexer `

is assumed to be a one-to-one, but not necessarily onto, affine mapping from `array`)`(array-domain `

into the indexing domain of `array`)`(array-body `

.`array`)

Please see `make-specialized-array`

for how `(array-body `

, etc., are used.`array`)

It is an error to call any of these procedures if

is not a specialized array.`array`

**Procedure: **`array-elements-in-order? `

`A`

Assumes that

is a specialized array, in which case it returns `A``#t`

if the elements of

are stored in `A``(array-body `

with increasing and consecutive indices, and `A`)`#f`

otherwise.

It is an error if

is not a specialized array.`A`

**Procedure: **`specialized-array-share `

`array` `new-domain` `new-domain->old-domain`

Constructs a new specialized array that shares the body of the specialized array

.
Returns an object that is behaviorally equivalent to a specialized array with the following fields:`array`

```
domain: new-domain
storage-class: (array-storage-class
````array`)
body: (array-body `array`)
indexer: (lambda multi-index
(call-with-values
(lambda ()
(apply `new-domain->old-domain`
multi-index))
(array-indexer `array`)))

The resulting array inherits its safety and mutability from

.`array`

Note: It is assumed that the affine structure of the composition of

and `new-domain->old-domain``(array-indexer `

will be used to simplify:`array`)

```
(lambda multi-index
(call-with-values
(lambda ()
(apply
````new-domain->old-domain` multi-index))
(array-indexer `array`)))

It is an error if

is not a specialized array, or if `array`

is not an interval, or if `new-domain`

is not a one-to-one affine mapping from `new-domain->old-domain`

to
`new-domain``(array-domain `

.`array`)

**Example: **One can apply a "shearing" operation to an array as follows:

```
(define a
(array-copy
(make-array (make-interval '#(5 10))
list)))
(define b
(specialized-array-share
a
(make-interval '#(5 5))
(lambda (i j)
(values i (+ i j)))))
;; Print the "rows" of b
(array-for-each (lambda (row)
(pretty-print (array->list row)))
(array-curry b 1))
;; which prints
;; ((0 0) (0 1) (0 2) (0 3) (0 4))
;; ((1 1) (1 2) (1 3) (1 4) (1 5))
;; ((2 2) (2 3) (2 4) (2 5) (2 6))
;; ((3 3) (3 4) (3 5) (3 6) (3 7))
;; ((4 4) (4 5) (4 6) (4 7) (4 8))
```

This "shearing" operation cannot be achieved by combining the procedures `array-extract`

, `array-translate`

, `array-permute`

, `array-translate`

, `array-curry`

, `array-reverse`

, and `array-sample`

.

**Procedure: **`array-copy `

`array` [ `result-storage-class` [ `mutable?` [ `safe?` ] ] ]

Assumes that

is an array, `array`

is a storage class that can manipulate all the elements of `result-storage-class`

, and `array`

and `mutable?`

are booleans.`safe?`

The specialized array returned by `array-copy`

can be defined conceptually by:

```
(list->array (array->list array)
(array-domain array)
result-storage-class
mutable?
safe?)
```

If

is a specialized array, then if any of `array`

, `result-storage-class`

, `mutable?`

are omitted, their values are assigned `safe?``(array-storage-class `

, `array`)`(mutable-array? `

, and `array`)`(array-safe? `

, respectively.`array`)

Otherwise, omitted arguments are assigned the values `generic-storage-class`

, `(specialized-array-default-mutable?)`

, and `(specialized-array-default-safe?)`

, respectively.

It is an error if the arguments do not satisfy these conditions.

**Procedure: **`array-curry `

`array` `inner-dimension`

If

is an array whose domain is an interval $[l_0,u_0)\times\cdots\times[l_{d-1},u_{d-1})$, and `array`

is an exact integer strictly between $0$ and $d$, then `inner-dimension``array-curry`

returns an immutable array with domain $[l_0,u_0)\times\cdots\times[l_{d-\text{inner-dimension}-1},u_{d-\text{inner-dimension}-1})$, each of whose entries is in itself an array with domain $[l_{d-\text{inner-dimension}},u_{d-\text{inner-dimension}})\times\cdots\times[l_{d-1},u_{d-1})$.

For example, if `A`

and `B`

are defined by

```
(define interval (make-interval '#(10 10 10 10)))
(define A (make-array interval list))
(define B (array-curry A 1))
(define A_ (array-getter A))
(define B_ (array-getter B))
```

so

`(A_ i j k l) => (list i j k l)`

then `B`

is an immutable array with domain `(make-interval '#(10 10 10))`

, each
of whose elements is itself an (immutable) array and

```
(equal?
(A_ i j k l)
((array-getter (B_ i j k)) l)) => #t
```

for all multi-indices `i j k l`

in `interval`

.

The subarrays are immutable, mutable, or specialized according to whether the array argument is immutable, mutable, or specialized.

More precisely, if

`0 < ``inner-dimension` < (interval-dimension (array-domain `array`))

then `array-curry`

returns a result as follows.

If the input array is specialized, then array-curry returns

```
(call-with-values
(lambda () (interval-projections (array-domain
````array`)
`inner-dimension`))
(lambda (outer-interval inner-interval)
(make-array
outer-interval
(lambda outer-multi-index
(specialized-array-share
`array`
inner-interval
(lambda inner-multi-index
(apply values
(append outer-multi-index
inner-multi-index))))))))

Otherwise, if the input array is mutable, then array-curry returns

```
(call-with-values
(lambda () (interval-projections (array-domain
````array`)
`inner-dimension`))
(lambda (outer-interval inner-interval)
(make-array
outer-interval
(lambda outer-multi-index
(make-array
inner-interval
(lambda inner-multi-index
(apply (array-getter `array`)
(append outer-multi-index
inner-multi-index)))
(lambda (v . inner-multi-index)
(apply (array-setter `array`)
v
(append outer-multi-index
inner-multi-index))))))))

Otherwise, array-curry returns

```
(call-with-values
(lambda () (interval-projections (array-domain
````array`)
`inner-dimension`))
(lambda (outer-interval inner-interval)
(make-array
outer-interval
(lambda outer-multi-index
(make-array
inner-interval
(lambda inner-multi-index
(apply (array-getter `array`)
(append outer-multi-index
inner-multi-index))))))))

It is an error to call `array-curry`

if its arguments do not satisfy these conditions.

If

is a specialized array, the subarrays of the result inherit their safety and mutability from `array`

.`array`

**Note: **Let's denote by

the result of `B``(array-curry `

. While the result of calling `A` `k`)`(array-getter `

is an immutable, mutable, or specialized array according to whether `B`)

itself is immutable, mutable, or specialized, `A`

is always an immutable array, where `B``(array-getter `

, which returns an array, is computed anew for each call. If `B`)`(array-getter `

will be called multiple times with the same arguments, it may be useful to store these results in a specialized array for fast repeated access.`B`)

Please see the note in the discussion of array-tile.

Example:

```
(define a (make-array (make-interval '#(10 10))
list))
(define a_ (array-getter a))
(a_ 3 4) => (3 4)
(define curried-a (array-curry a 1))
(define curried-a_ (array-getter curried-a))
((array-getter (curried-a_ 3)) 4)
=> (3 4)
```

**Procedure: **`array-extract `

`array` `new-domain`

Returns a new array with the same getter (and setter, if appropriate) of the first argument, defined on the second argument.

Assumes that

is an array and `array`

is an interval that is a sub-interval of `new-domain``(array-domain `

. If `array`)

is a specialized array, then returns `array`

` (specialized-array-share ``array`
`new-domain`
values)

Otherwise, if

is a mutable array, then `array``array-extract`

returns

` (make-array ``new-domain`
(array-getter `array`)
(array-setter `array`))

Finally, if

is an immutable array, then `array``array-extract`

returns

` (make-array ``new-domain`
(array-getter `array`))

It is an error if the arguments of `array-extract`

do not satisfy these conditions.

If

is a specialized array, the resulting array inherits its mutability and safety from `array`

.`array`

**Procedure: **`array-tile `

`A` `S`

Assume that

is an array and `A`

is a vector of positive, exact integers. The procedure `S``array-tile`

returns a new immutable array $T$, each entry of which is a subarray of `A`

whose domain has sidelengths given (mostly) by the entries of

. These subarrays completely "tile" `S`

, in the sense that every entry in `A`

is an entry of precisely one entry of the result $T$.`A`

More formally, if

is the vector $(s_0,\ldots,s_{d-1})$, and the domain of `S`

is the interval $[l_0,u_0)\times\cdots\times [l_{d-1},u_{d-1})$, then $T$ is an immutable array with all lower bounds zero and upper bounds given by
$$
\operatorname{ceiling}((u_0-l_0)/s_0),\ldots,\operatorname{ceiling}((u_{d-1}-l_{d-1})/s_{d-1}).
$$
The $i_0,\ldots,i_{d-1}$ entry of $T$ is `A``(array-extract `

with the interval `A` D_i)`D_i`

given by
$$
[l_0+i_0*s_0,\min(l_0+(i_0+1)s_0,u_0))\times\cdots\times[l_{d-1}+i_{d-1}*s_{d-1},\min(l_{d-1}+(i_{d-1}+1)s_{d-1},u_{d-1})).
$$
(The "minimum" operators are necessary if $u_j-l_j$ is not divisible by $s_j$.) Thus, each entry of $T$ will be a specialized, mutable, or immutable array, depending on the type of the input array

.`A`

It is an error if the arguments of `array-tile`

do not satisfy these conditions.

If

is a specialized array, the subarrays of the result inherit safety and mutability from `A`

.`A`

**Note: **The procedures `array-tile`

and `array-curry`

both decompose an array into subarrays, but in different ways. For example, if

is defined as `A``(make-array (make-interval '#(10 10)) list)`

, then `(array-tile `

returns an array with domain `A` '#(1 10))`(make-interval '#(10 1))`

for which the value at the multi-index `(`

is an array with domain `i` 0)`(make-interval (vector `

(i.e., a two-dimensional array whose elements are two-dimensional arrays), while `i` 0) (vector (+ `i` 1) 10))`(array-curry `

returns an array with domain `A` 1)`(make-interval '#(10))`

, each element of which has domain `(make-interval '#(10))`

(i.e., a one-dimensional array whose elements are one-dimensional arrays).

**Procedure: **`array-translate `

`array` `translation`

Assumes that

is an array, `array`

is a translation, and that the dimensions of the array and the translation are the same. The resulting array will have domain `translation``(interval-translate (array-domain array) translation)`

.

If

is a specialized array, returns a new specialized array`array`

```
(specialized-array-share
````array`
(interval-translate (array-domain `array`)
`translation`)
(lambda multi-index
(apply values
(map -
multi-index
(vector->list `translation`)))))

that shares the body of

, as well as inheriting its safety and mutability.`array`

If

is not a specialized array but is a mutable array, returns a new mutable array`array`

```
(make-array
(interval-translate (array-domain
````array`)
`translation`)
(lambda multi-index
(apply (array-getter `array`)
(map -
multi-index
(vector->list `translation`))))
(lambda (val . multi-index)
(apply (array-setter `array`)
val
(map -
multi-index
(vector->list `translation`)))))

that employs the same getter and setter as the original array argument.

If

is not a mutable array, returns a new array`array`

```
(make-array
(interval-translate (array-domain
````array`)
`translation`)
(lambda multi-index
(apply (array-getter `array`)
(map - multi-index (vector->list `translation`)))))

that employs the same getter as the original array.

It is an error if the arguments do not satisfy these conditions.

**Procedure: **`array-permute `

`array` `permutation`

Assumes that

is a valid array, `array`

is a valid permutation, and that the dimensions of the array and the permutation are the same. The resulting array will have domain `permutation``(interval-permute (array-domain array) permutation)`

.

We begin with an example. Assume that the domain of

is represented by the interval $[0,4)\times[0,8)\times[0,21)\times [0,16)$, as in the example for `array``interval-permute`

, and the permutation is `#(3 0 1 2)`

. Then the domain of the new array is the interval $[0,16)\times [0,4)\times[0,8)\times[0,21)$.

So the multi-index argument of the `getter`

of the result of `array-permute`

must lie in the new domain of the array, the interval $[0,16)\times [0,4)\times[0,8)\times[0,21)$. So if we define

as `old-getter``(array-getter `

, the definition of the new array must be in fact`array`)

`(make-array (interval-permute (array-domain ``array`)
'#(3 0 1 2))
(lambda (l i j k)
(old-getter i j k l)))

So you see that if the first argument if the new getter is in $[0,16)$, then indeed the fourth argument of

is also in $[0,16)$, as it should be. This is a subtlety that I don't see how to overcome. It is the listing of the arguments of the new getter, the `old-getter``lambda`

, that must be permuted.

Mathematically, we can define $\pi^{-1}$, the inverse of a permutation $\pi$, such that $\pi^{-1}$ composed with $\pi$ gives the identity permutation. Then the getter of the new array is, in pseudo-code, `(lambda multi-index (apply `

$\pi^{-1}$`old-getter` (` multi-index)))`

. We have assumed that $\pi^{-1}$ takes a list as an argument and returns a list as a result.

Employing this same pseudo-code, if

is a specialized array and we denote the permutation by $\pi$, then `array``array-permute`

returns the new specialized array

`(specialized-array-share ``array`
(interval-permute (array-domain `array`) π)
(lambda multi-index
(apply values (π^{-1} multi-index))))

The resulting array shares the body of

, as well as its safety and mutability.`array`

Again employing this same pseudo-code, if

is not a specialized array, but is
a mutable array, then `array``array-permute`

returns the new mutable array

`(make-array (interval-permute (array-domain ``array`) π)
(lambda multi-index
(apply (array-getter `array`)
(π^{-1} multi-index)))
(lambda (val . multi-index)
(apply (array-setter `array`)
val
(π^{-1} multi-index))))

which employs the setter and the getter of the argument to `array-permute`

.

Finally, if

is not a mutable array, then `array``array-permute`

returns

`(make-array (interval-permute (array-domain ``array`) π)
(lambda multi-index
(apply (array-getter `array`)
(π^{-1} multi-index))))

It is an error to call `array-permute`

if its arguments do not satisfy these conditions.

**Procedure: **`array-reverse `

`array` #!optional `flip?`

We assume that

is an array and `array`

, if given, is a vector of booleans whose length is the same as the dimension of `flip?`

. If `array`

is not given, it is set to a vector with length the same as the dimension of `flip?`

, all of whose elements are `array``#t`

.

`array-reverse`

returns a new array that is specialized, mutable, or immutable according to whether

is specialized, mutable, or immutable, respectively. Informally, if `array``(vector-ref `

is true, then the ordering of multi-indices in the k'th coordinate direction is reversed, and is left undisturbed otherwise.`flip?` k)

More formally, we introduce the procedure

```
(define flip-multi-index
(let* ((domain (array-domain
````array`

))
(lowers (interval-lower-bounds->list domain))
(uppers (interval-upper-bounds->list domain)))
(lambda (multi-index)
(map (lambda (i_k flip?_k l_k u_k)
(if flip?_k
(- (+ l_k u_k -1) i_k)
i_k))
multi-index
(vector->list `flip?`)
lowers
uppers))))

Then if

is specialized, `array``array-reverse`

returns

```
(specialized-array-share
````array`

domain
(lambda multi-index
(apply values
(flip-multi-index multi-index))))

and the result inherits the safety and mutability of

.`array`

Otherwise, if

is mutable, then `array``array-reverse`

returns

```
(make-array
domain
(lambda multi-index
(apply (array-getter
````array`

)
(flip-multi-index multi-index)))
(lambda (v . multi-index)
(apply (array-setter `array`

)
v
(flip-multi-index multi-index)))))

Finally, if

is immutable, then `array``array-reverse`

returns

```
(make-array
domain
(lambda multi-index
(apply (array-getter
````array`

)
(flip-multi-index multi-index)))))

It is an error if

and `array`

don't satisfy these requirements.`flip?`

The following example using `array-reverse`

was motivated by a blog post by Joe Marshall.

```
(define (palindrome? s)
(let ((n (string-length s)))
(or (< n 2)
(let* ((a
;; an array accessing the characters of s
(make-array (make-interval (vector n))
(lambda (i)
(string-ref s i))))
(ra
;; the array accessed in reverse order
(array-reverse a))
(half-domain
(make-interval (vector (quotient n 2)))))
(array-every
char=?
;; the first half of s
(array-extract a half-domain)
;; the reversed second half of s
(array-extract ra half-domain))))))
(palindrome? "") => #t
(palindrome? "a") => #t
(palindrome? "aa") => #t
(palindrome? "ab") => #f
(palindrome? "aba") => #t
(palindrome? "abc") => #f
(palindrome? "abba") => #t
(palindrome? "abca") => #f
(palindrome? "abbc") => #f
```

**Procedure: **`array-sample `

`array` `scales`

We assume that

is an array all of whose lower bounds are zero, and `array`

is a vector of positive exact integers whose length is the same as the dimension of `scales`

.`array`

Informally, if we construct a new matrix $S$ with the entries of

on the main diagonal, then the $\vec i$th element of `scales``(array-sample `

is the $S\vec i$th element of `array` `scales`)

.`array`

More formally, if

is specialized, then `array``array-sample`

returns

```
(specialized-array-share
````array`

(interval-scale (array-domain `array`

)
`scales`

)
(lambda multi-index
(apply values
(map * multi-index (vector->list `scales`

)))))

with the result inheriting the safety and mutability of

.`array`

Otherwise, if

is mutable, then `array``array-sample`

returns

```
(make-array
(interval-scale (array-domain
````array`

)
`scales`

)
(lambda multi-index
(apply (array-getter `array`

)
(map * multi-index (vector->list `scales`

))))
(lambda (v . multi-index)
(apply (array-setter `array`

)
v
(map * multi-index (vector->list `scales`

)))))

Finally, if

is immutable, then `array``array-sample`

returns

```
(make-array
(interval-scale (array-domain
````array`

)
`scales`

)
(lambda multi-index
(apply (array-getter `array`

)
(map * multi-index (vector->list `scales`

)))))

It is an error if

and `array`

don't satisfy these requirements.`scales`

**Procedure: **`array-outer-product `

`op` `array1` `array2`

Implements the outer product of

and `array1`

with the operator `array2`

, similar to the APL function with the same name.`op`

Assume that

and `array1`

are arrays and that `array2`

is a procedure of two arguments. `op`

returns the immutable array`array-outer-product`

```
(make-array (interval-cartesian-product (array-domain array1)
(array-domain array2))
(lambda args
(op (apply (array-getter array1) (take args (array-dimension array1)))
(apply (array-getter array2) (drop args (array-dimension array1))))))
```

This operation can be considered a partial inverse to `array-curry`

. It is an error if the arguments do not satisfy these assumptions.

**Note: **You can see from the above definition that if

is `C``(array-outer-product `

, then each call to `op` `A` `B`)`(array-getter `

will call `C`)

as well as `op``(array-getter `

and `A`)`(array-getter `

. This implies that if all elements of `B`)

are eventually accessed, then `C``(array-getter `

will be called `A`)`(array-volume `

times; similarly `B`)`(array-getter `

will be called `B`)`(array-volume `

times. `A`)

This implies that if `(array-getter `

is expensive to compute (for example, if it's returning an array, as does `A`)`array-curry`

) then the elements of

should be pre-computed if necessary and stored in a specialized array, typically using `A``array-copy`

, before that specialized array is passed as an argument to `array-outer-product`

. In the examples below, the code for Gaussian elimination applies `array-outer-product`

to shared specialized arrays, which are of course themselves specialized arrays; the code for `array-inner-product`

applies `array-outer-product`

to curried arrays, so we apply `array-copy`

to the arguments before passage to `array-outer-product`

.

**Procedure: **`array-inner-product `

`A` `f` `g` `B`

Assumes that

and `f`

are procedures of two arguments and `g`

and `A`

are arrays, with the upper and lower bounds of the last axis of `B`

the same as those of the first axis of `A`

. Computes the equivalent of`B`

`(define (array-inner-product ``A f g B`)
(array-outer-product
(lambda (`a b`)
(array-reduce `f` (array-map `g a b`)))
(array-copy (array-curry `A` 1))
(array-copy (array-curry (array-permute `B` (index-rotate (array-dimension `B`) 1)))))

We precompute and store the curried arrays using `array-copy`

for efficiency reasons, as described in `array-outer-product`

.

It is an error if the arguments do not satisfy these constraints.

**Procedure: **`array-map `

`f` `array` . `arrays`

If

, `array``(car `

, ... all have the same domain and `arrays`)

is a procedure, then `f``array-map`

returns a new immutable array with the same domain and getter

```
(lambda multi-index
(apply
````f`
(map (lambda (g)
(apply g multi-index))
(map array-getter
(cons `array` `arrays`)))))

It is assumed that

is appropriately defined to be evaluated in this context.`f`

It is expected that `array-map`

and `array-for-each`

will specialize the construction of

```
(lambda multi-index
(apply
````f`
(map (lambda (g)
(apply g multi-index))
(map array-getter
(cons `array`
`arrays`)))))

It is an error to call `array-map`

if its arguments do not satisfy these conditions.

**Note: **The ease of constructing temporary arrays without allocating storage makes it trivial to imitate, e.g., Javascript's map with index. For example, we can add the index to each element of an array

by `a`

```
(array-map +
a
(make-array (array-domain a)
(lambda (i) i)))
```

or even

```
(make-array (array-domain a)
(let ((a_ (array-getter a)))
(lambda (i)
(+ (a_ i) i))))
```

**Procedure: **`array-for-each `

`f` `array` . `arrays`

If

, `array``(car `

, ... all have the same domain and `arrays`)

is an appropriate procedure, then `f``array-for-each`

calls

```
(interval-for-each
(lambda multi-index
(apply
````f`
(map (lambda (g)
(apply g multi-index))
(map array-getter
(cons `array`
`arrays`)))))
(array-domain `array`))

In particular, `array-for-each`

always walks the indices of the arrays in lexicographical order.

It is expected that `array-map`

and `array-for-each`

will specialize the construction of

```
(lambda multi-index
(apply
````f`
(map (lambda (g)
(apply g multi-index))
(map array-getter
(cons `array`
`arrays`)))))

It is an error to call `array-for-each`

if its arguments do not satisfy these conditions.

**Procedure: **`array-foldl `

`op` `id` `array`

This procedure is analogous to the left fold of Ocaml or Haskell, which can be defined on lists in Scheme as:

`(define (foldl ``op` `id` `l`)
(if (null? `l`)
`id`
(foldl `op` (`op` `id` (car `l`)) (cdr `l`))))

Then conceptually `(array-foldl `

returns `op` `id` `array`)

`(foldl ``op` `id` (array->list `array`))

It is an error if

is not an array, or if `array`

is not a procedure of two variables.`op`

**Procedure: **`array-foldr `

`op` `id` `array`

This procedure is analogous to the right fold of Ocaml or Haskell, which can be defined on lists in Scheme as:

`(define (foldr ``op` `id` `l`)
(if (null? `l`)
`id`
(`op` (car `l`) (foldr `op` `id` (cdr `l`))))

Then conceptually `(array-foldr `

returns `op` `id` `array`)

`(foldr ``op` `id` (array->list `array`))

It is an error if

is not an array, or if `array`

is not a procedure of two variables.`op`

**Note: **Both `array-foldl`

and `array-foldr`

are implemented using tail-recursive algorithms in the sample implementation.

**Note: **If

is associative with two-sided identity `op`

, then `id``array-foldl`

and `array-foldr`

return the same results, but see:

```
(define a (make-array (make-interval '#(10)) (lambda (i) i)))
(array-foldl cons '() a)
=> ((((((((((() . 0) . 1) . 2) . 3) . 4) . 5) . 6) . 7) . 8) . 9)
(array-foldr cons '() a)
=> (0 1 2 3 4 5 6 7 8 9)
(array-foldl - 0 a)
=> -45
(array-foldr - 0 a)
=> -5
```

**Procedure: **`array-reduce `

`op` `A`

We assume that

is an array and `A`

is a procedure of two arguments that is associative, i.e., `op``(`

is the same as `op` (`op` `x` `y`) `z`)`(`

.`op` `x` (`op` `y` `z`))

Then `(array-reduce `

returns`op` `A`)

```
(let ((box '())
(A_ (array-getter A)))
(interval-for-each
(lambda multi-index
(if (null? box)
(set! box (list (apply A_ multi-index)))
(set-car! box (op (car box)
(apply A_ multi-index)))))
(array-domain A))
(car box))
```

The implementation is allowed to use the associativity of

to reorder the computations in `op``array-reduce`

. It is an error if the arguments do not satisfy these conditions.

**Example: **We consider the finite sum:
$$
S_m=\sum_{k=1}^m \frac 1{k^2}.
$$
One can show that
$$
\frac 1 {m+1}<\frac{\pi^2}6-S_m<\frac 1m.
$$
We attempt to compute this in floating-point arithmetic in two ways. In the first, we apply `array-reduce`

to an array containing the terms of the series, basically a serial computation. In the second, we divide the series into blocks of no more than 1,000 consecutive terms, apply `array-reduce`

to get a new sequence of terms, and repeat the process. The second way is approximately what might happen with GPU computing.

We find with $m=1{,}000{,}000{,}000$:

```
(define A (make-array (make-interval '#(1) '#(1000000001))
(lambda (k)
(fl/ (flsquare (inexact k))))))
(define (block-sum A)
(let ((N (interval-volume (array-domain A))))
(cond ((<= N 1000)
(array-reduce fl+ A))
((<= N (square 1000))
(block-sum (array-map block-sum
(array-tile A (vector (integer-sqrt N))))))
(else
(block-sum (array-map block-sum
(array-tile A (vector (quotient N 1000)))))))))
(array-reduce fl+ A) => 1.644934057834575
(block-sum A) => 1.6449340658482325
```

Since $\pi^2/6\approx{}$`1.6449340668482264`

, we see using the first method that the difference $\pi^2/6-{}$`1.644934057834575`

${}\approx{}$`9.013651380840315e-9`

and with the second we have $\pi^2/6-{}$`1.6449340658482325`

${}\approx{}$`9.99993865491433e-10`

. The true difference should be between $\frac 1{1{,}000{,}000{,}001}\approx{}$`9.99999999e-10`

and $\frac 1{1{,}000{,}000{,}000}={}$`1e-9`

. The difference for the first method is about 10 times too big, and, in fact, will not change further because any further terms, when added to the partial sum, are too small to increase the sum after rounding-to-nearest in double-precision IEEE-754 floating-point arithmetic.

**Procedure: **`array-any `

`pred` `array1` `array2` ...

Assumes that

, `array1`

, etc., are arrays, all with the same domain, which we'll call `array2``interval`

. Also assumes that

is a procedure that takes as many arguments as there are arrays and returns a single value.`pred`

`array-any`

first applies `(array-getter `

, etc., to the first element of `array1`)`interval`

in lexicographical order, to which value it then applies

.`pred`

If the result of

is not `pred``#f`

, then that result is returned by `array-any`

. If the result of

is `pred``#f`

, then `array-any`

continues with the second element of `interval`

, etc., returning the first nonfalse value of

.`pred`

If

always returns `pred``#f`

, then `array-any`

returns `#f`

.

If it happens that

is applied to the results of applying `pred``(array-getter `

, etc., to the last element of `array1`)`interval`

, then this last call to

is in tail position.`pred`

The procedures `(array-getter `

, etc., are applied only to those values of `array1`)`interval`

necessary to determine the result of `array-any`

.

It is an error if the arguments do not satisfy these assumptions.

**Procedure: **`array-every `

`pred` `array1` `array2` ...

Assumes that

, `array1`

, etc., are arrays, all with the same domain, which we'll call `array2``interval`

. Also assumes that

is a procedure that takes as many arguments as there are arrays and returns a single value.`pred`

`array-every`

first applies `(array-getter `

, etc., to the first element of `array1`)`interval`

in lexicographical order, to which values it then applies

.`pred`

If the result of

is `pred``#f`

, then that result is returned by `array-every`

. If the result of

is nonfalse, then `pred``array-every`

continues with the second element of `interval`

, etc., returning the first value of

that is `pred``#f`

.

If

always returns a nonfalse value, then the last nonfalse value returned by `pred`

is also returned by `pred``array-every`

.

If it happens that

is applied to the results of applying `pred``(array-getter `

, etc., to the last element of `array1`)`interval`

, then this last call to

is in tail position.`pred`

The procedures `(array-getter `

, etc., are applied only to those values of `array1`)`interval`

necessary to determine the result of `array-every`

.

It is an error if the arguments do not satisfy these assumptions.

**Procedure: **`array->list `

`array`

Stores the elements of

into a newly allocated list in lexicographical order. It is an error if `array`

is not an array.`array`

It is guaranteed that `(array-getter `

is called precisely once for each multi-index in `array`)`(array-domain `

in lexicographical order.`array`)

**Procedure: **`list->array `

`l` `domain` [ `result-storage-class` generic-storage-class ] [ `mutable?` (specialized-array-default-mutable?) ] [ `safe?` (specialized-array-default-safe?) ]

Assumes that

is an list, `l`

is an interval with volume the same as the length of `domain`

, `l`

is a storage class that can manipulate all the elements of `result-storage-class`

, and `l`

and `mutable?`

are booleans.`safe?`

Returns a specialized array with domain

whose elements are the elements of the list `domain`

stored in lexicographical order. The result is mutable or safe depending on the values of `l`

and `mutable?`

.`safe?`

It is an error if the arguments do not satisfy these assumptions, or if any element of

cannot be stored in the body of `l`

, and this last error shall be detected and raised.`result-storage-class`

**Procedure: **`array-assign! `

`destination` `source`

Assumes that

is a mutable array and `destination`

is an array with the same domain, and that the elements of `source`

can be stored into `source`

.`destination`

Evaluates `(array-getter `

on the multi-indices in `source`)`(array-domain `

in lexicographical order, and assigns each value to the multi-index in `source`)

in the same lexicographical order.`destination`

It is an error if the arguments don't satisfy these assumptions.

If assigning any element of

affects the value of any element of `destination`

, then the result is undefined.`source`

**Procedure: **`array-stack [ `

`storage-class` [ `mutable?` [ `safe?` ] ] ] `k` `array` . `arrays`

Assumes that `(cons `

is a list of arrays with identical domains, ` array arrays`)

is an exact integer between 0 (inclusive) and the dimension of the array domains (inclusive), and, if given, `k`

is a storage class, `storage-class`

is a boolean, and `mutable?`

is a boolean.`safe?`

Returns a specialized array equivalent to

```
(array-copy
(make-array
(let ((
````lowers` (interval-lower-bounds->list (array-domain `array`)))
(`uppers` (interval-upper-bounds->list (array-domain `array`)))
(`N` (length (cons `array arrays`))))
(make-interval (append (take `lowers k`) (cons 0 (drop `lowers k`)))
(append (take `uppers k`) (cons `N` (drop `uppers k`)))))
(let ((`getters` (map array-getter (cons `array arrays`))))
(lambda indices
(let ((`i` (list-ref `indices k`)))
(apply (list-ref `getters i`)
(append (take `indices k`)
(drop `indices` (+ `k` 1)))))))))

In other words we "stack" the argument arrays along a new

'th axis, the lower bound of which is set to 0.`k`

If all optional arguments are given, the resultant array has storage class

, mutability `storage-class`

, and safety `mutable?`

.`safe?`

We determine the values of missing optional arguments as follows: If the argument arrays are *all* specialized arrays with the same storage class, mutability, *and* safety, then any missing optional arguments are assigned the associated values from

; otherwise, any missing optional arguments are assigned `array``generic-storage-class`

, `(specialized-array-default-mutable?)`

, and `(specialized-array-default-safe?)`

, respectively.

It is an error if the arguments do not satisfy these constraints.

**Example: **Let's say we have a spreadsheet

and we want to make a new spreadsheet `A`

with the same rows but with the data from only columns 1, 2, 5, and 8. Using the routine `B``array-display`

we define below, code to do this can look like:

```
(let* ((A
(make-array
(make-interval '#(4 10))
list))
(column_
(array-getter ;; the getter of ...
(array-curry ;; a 1-D array of the columns of A
(array-permute A '#(1 0))
1)))
(B
(apply
array-stack ;; stack into a new 2-D array ...
1 ;; along axis 1 (i.e., columns) ...
(map column_ '(1 2 5 8))))) ;; the columns of A you want
(array-display B))
;;; Displays
(0 1) (0 2) (0 5) (0 8)
(1 1) (1 2) (1 5) (1 8)
(2 1) (2 2) (2 5) (2 8)
(3 1) (3 2) (3 5) (3 8)
```

In fact, because

is a generalized array, the only elements of `A`

that are generated are the ones that are assigned as elements of `A`

. The result could also be computed in one (rather long) line:`B`

`(apply array-stack 1 (map (array-getter (array-curry (array-permute A '#(1 0)) 1)) '(1 2 5 8)))`

**Procedure: **`array-append [ `

`storage-class` [ `mutable?` [ `safe?` ] ] ] `k` `array` . `arrays`

Assumes that `(cons `

is a list of arrays with domains that differ at most in the ` array arrays`)

'th axis, `k`

is an exact integer between 0 (inclusive) and the dimension of the array domains (exclusive), and, if given, `k`

is a storage class, `storage-class`

is a boolean, and `mutable?`

is a boolean.`safe?`

This routine appends, or concatenates, the argument arrays along the `k`'th axis, with the lower bound of this axis set to 0.

Returns a specialized array equivalent to the result of

```
(define (array-append k array . arrays)
(let*-values (((arrays)
;; all array arguments
(cons array arrays))
((axis-subdividers kth-size)
;; compute lower and upper bounds of where along the
;; k'th axis we'll copy each array argument, plus
;; the total size of the kth axis of the result array
(let loop ((result '(0))
(arrays arrays))
(if (null? arrays)
(values (reverse result) (car result))
(let ((interval (array-domain (car arrays))))
(loop (cons (+ (car result)
(- (interval-upper-bound interval k)
(interval-lower-bound interval k)))
result)
(cdr arrays))))))
((lowers)
;; the domains of the arrays differ only in the kth axis
(interval-lower-bounds->vector (array-domain array)))
((uppers)
(interval-upper-bounds->vector (array-domain array)))
((result)
;; the result array
(make-specialized-array
(let ()
(vector-set! lowers k 0)
(vector-set! uppers k kth-size)
(make-interval lowers uppers))))
((translation)
;; a vector we'll use to align each argument
;; array into the proper subarray of the result
(make-vector (array-dimension array) 0)))
(let loop ((arrays arrays)
(subdividers axis-subdividers))
(if (null? arrays)
;; we've assigned every array to the appropriate subarray of result
result
(let ((array (car arrays)))
;; the lower and upper bounds in the kth axis of the result where we copy the next array
(vector-set! lowers k (car subdividers))
(vector-set! uppers k (cadr subdividers))
;; the translation that aligns the next array with the subarray of the result
(vector-set! translation k (- (car subdividers)
(interval-lower-bound (array-domain array) k)))
(array-assign!
(array-extract result (make-interval lowers uppers))
(array-translate array translation))
(loop (cdr arrays)
(cdr subdividers)))))))
```

If all optional arguments are given, the resultant array has storage class

, mutability `storage-class`

, and safety `mutable?`

.`safe?`

We determine the values of missing optional arguments as follows: If the argument arrays are *all* specialized arrays with the same storage class, mutability, *and* safety, then any missing optional arguments are assigned the associated values from

; otherwise, any missing optional arguments are assigned `array``generic-storage-class`

, `(specialized-array-default-mutable?)`

, and `(specialized-array-default-safe?)`

, respectively.

It is an error if the arguments do not satisfy these constraints.

**Procedure: **`array-ref `

`A` `i0` . `i-tail`

Assumes that

is an array, and every element of `A``(cons `

is an exact integer.`i0 i-tail`)

Returns `(apply (array-getter `

.`A`) `i0 i-tail`)

It is an error if

is not an array, or if the number of arguments specified is not the correct number for `A``(array-getter `

.`A`)

**Procedure: **`array-set! `

`A` `v` `i0` . `i-tail`

Assumes that

is a mutable array, that `A`

is a value that can be stored within that array, and that every element of `v``(cons `

is an exact integer.`i0 i-tail`)

Returns `(apply (array-setter `

.`A`) `v i0 i-tail`)

It is an error if

is not a mutable array, if `A``v`

is not an appropriate value to be stored in that array, or if the number of arguments specified is not the correct number for `(array-setter `

.`A`)

**Note: **In the sample implementation, because `array-ref`

and `array-set!`

take a variable number of arguments and they must check that

is an array of the appropriate type, programs written in a style using these procedures, rather than the style in which `A``1D-Haar-loop`

is coded below, can take up to three times as long runtime.

**Note: **In the sample implementation, checking whether the multi-indices are exact integers and within the domain of the array, and checking whether the value is appropriate for storage into the array, is delegated to the underlying definition of the array argument. If the first argument is a safe specialized array, then these items are checked; if it is an unsafe specialized array, they are not. If it is a generalized array, it is up to the programmer whether to define the getter and setter of the array to check the correctness of the arguments.

**Procedure: **`specialized-array-reshape `

`array` `new-domain` [ `copy-on-failure?` #f ]

Assumes that

is a specialized array, `array`

is an interval with the same volume as `new-domain``(array-domain `

, and `array`)

, if given, is a boolean.`copy-on-failure?`

If there is an affine map that takes the multi-indices in

to the cells in `new-domain``(array-body `

storing the elements of `array`)

in lexicographical order, returns a new specialized array, with the same body and elements as `array`

and domain `array`

. The result inherits its mutability and safety from `new-domain`

.`array`

If there is not an affine map that takes the multi-indices in

to the cells storing the elements of `new-domain`

in lexicographical order and `array`

is `copy-on-failure?``#t`

, then returns a specialized array copy of

with domain `array`

, storage class `new-domain``(array-storage-class `

, mutability `array`)`(mutable-array? `

, and safety `array`)`(array-safe? `

.`array`)

It is an error if these conditions on the arguments are not met.

**Note: **The code in the sample implementation to determine whether there exists an affine map from

to the multi-indices of the elements of `new-domain`

in lexicographical order is modeled on the corresponding code in the Python library NumPy.`array`

**Note: **In the sample implementation, if an array cannot be reshaped and

is `copy-on-failure?``#f`

, an error is raised in tail position. An implementation might want to replace this error call with a continuable exception to give the programmer more flexibility.

**Examples: **Reshaping an array is not a Bawden-type array transform. For example, we use `array-display`

defined below to see:

```
;;; The entries of A are the multi-indices of the locations
(define A (array-copy (make-array (make-interval '#(3 4)) list)))
(array-display A)
;;; Displays
;;; (0 0) (0 1) (0 2) (0 3)
;;; (1 0) (1 1) (1 2) (1 3)
;;; (2 0) (2 1) (2 2) (2 3)
(array-display (array-permute A '#(1 0)))
;;; Displays
;;; (0 0) (1 0) (2 0)
;;; (0 1) (1 1) (2 1)
;;; (0 2) (1 2) (2 2)
;;; (0 3) (1 3) (2 3)
(array-display (specialized-array-reshape A (make-interval '#(4 3))))
;;; Displays
;;; (0 0) (0 1) (0 2)
;;; (0 3) (1 0) (1 1)
;;; (1 2) (1 3) (2 0)
;;; (2 1) (2 2) (2 3)
(define B (array-sample A '#(2 1)))
(array-display B)
;;; Displays
;;; (0 0) (0 1) (0 2) (0 3)
;;; (2 0) (2 1) (2 2) (2 3)
(array-display (specialized-array-reshape B (make-interval '#(8)))) => fails
(array-display (specialized-array-reshape B (make-interval '#(8)) #t))
;;; Displays
;;; (0 0) (0 1) (0 2) (0 3) (2 0) (2 1) (2 2) (2 3)
```

The following examples succeed:

```
(specialized-array-reshape
(array-copy (make-array (make-interval '#(2 1 3 1)) list))
(make-interval '#(6)))
(specialized-array-reshape
(array-copy (make-array (make-interval '#(2 1 3 1)) list))
(make-interval '#(3 2)))
(specialized-array-reshape
(array-reverse (array-copy (make-array (make-interval '#(2 1 3 1)) list)))
(make-interval '#(6)))
(specialized-array-reshape
(array-reverse (array-copy (make-array (make-interval '#(2 1 3 1)) list)))
(make-interval '#(3 2)))
(specialized-array-reshape
(array-reverse (array-copy (make-array (make-interval '#(2 1 3 1)) list)) '#(#f #f #f #t))
(make-interval '#(3 2)))
(specialized-array-reshape
(array-reverse (array-copy (make-array (make-interval '#(2 1 3 1)) list)) '#(#f #f #f #t))
(make-interval '#(3 1 2 1)))
(specialized-array-reshape
(array-sample (array-reverse (array-copy (make-array (make-interval '#(2 1 4 1)) list)) '#(#f #f #f #t)) '#(1 1 2 1))
(make-interval '#(4)))
(specialized-array-reshape
(array-sample (array-reverse (array-copy (make-array (make-interval '#(2 1 4 1)) list)) '#(#t #f #t #t)) '#(1 1 2 1))
(make-interval '#(4)))
```

The following examples raise an exception:

```
(specialized-array-reshape
(array-reverse (array-copy (make-array (make-interval '#(2 1 3 1)) list)) '#(#t #f #f #f))
(make-interval '#(6)))
(specialized-array-reshape
(array-reverse (array-copy (make-array (make-interval '#(2 1 3 1)) list)) '#(#t #f #f #f))
(make-interval '#(3 2)))
(specialized-array-reshape
(array-reverse (array-copy (make-array (make-interval '#(2 1 3 1)) list)) '#(#f #f #t #f))
(make-interval '#(6)))
(specialized-array-reshape
(array-reverse (array-copy (make-array (make-interval '#(2 1 3 1)) list)) '#(#f #f #t #t))
(make-interval '#(3 2)))
(specialized-array-reshape
(array-sample (array-reverse (array-copy (make-array (make-interval '#(2 1 3 1)) list)) '#(#f #f #f #t)) '#(1 1 2 1))
(make-interval '#(4)) )
(specialized-array-reshape
(array-sample (array-reverse (array-copy (make-array (make-interval '#(2 1 4 1)) list)) '#(#f #f #t #t)) '#(1 1 2 1))
(make-interval '#(4)))
```

In the next examples, we start with vector fields, $100\times 100$ arrays of 4-vectors. In one example, we reshape each large array to $100\times 100\times2\times2$ vector fields (so we consider each 4-vector as a $2\times 2$ matrix), and multiply the $2\times 2$ matrices together. In the second example, we reshape each 4-vector to a $2\times 2$ matrix individually, and compare the times.

```
(define interval-flat (make-interval '#(100 100 4)))
(define interval-2x2 (make-interval '#(100 100 2 2)))
(define A (array-copy (make-array interval-flat (lambda args (random-integer 5)))))
(define B (array-copy (make-array interval-flat (lambda args (random-integer 5)))))
(define C (array-copy (make-array interval-flat (lambda args 0))))
(define (2x2-matrix-multiply-into! A B C)
(let ((C! (array-setter C))
(A_ (array-getter A))
(B_ (array-getter B)))
(C! (+ (* (A_ 0 0) (B_ 0 0))
(* (A_ 0 1) (B_ 1 0)))
0 0)
(C! (+ (* (A_ 0 0) (B_ 0 1))
(* (A_ 0 1) (B_ 1 1)))
0 1)
(C! (+ (* (A_ 1 0) (B_ 0 0))
(* (A_ 1 1) (B_ 1 0)))
1 0)
(C! (+ (* (A_ 1 0) (B_ 0 1))
(* (A_ 1 1) (B_ 1 1)))
1 1)))
;;; Reshape A, B, and C to change all the 4-vectors to 2x2 matrices
(time
(array-for-each 2x2-matrix-multiply-into!
(array-curry (specialized-array-reshape A interval-2x2) 2)
(array-curry (specialized-array-reshape B interval-2x2) 2)
(array-curry (specialized-array-reshape C interval-2x2) 2)))
;;; Displays
;;; 0.015186 secs real time
;;; 0.015186 secs cpu time (0.015186 user, 0.000000 system)
;;; 4 collections accounting for 0.004735 secs real time (0.004732 user, 0.000000 system)
;;; 46089024 bytes allocated
;;; no minor faults
;;; no major faults
;;; Reshape each 4-vector to a 2x2 matrix individually
(time
(array-for-each (lambda (A B C)
(2x2-matrix-multiply-into!
(specialized-array-reshape A (make-interval '#(2 2)))
(specialized-array-reshape B (make-interval '#(2 2)))
(specialized-array-reshape C (make-interval '#(2 2)))))
(array-curry A 1)
(array-curry B 1)
(array-curry C 1)))
;;; Displays
;;; 0.039193 secs real time
;;; 0.039193 secs cpu time (0.039191 user, 0.000002 system)
;;; 6 collections accounting for 0.006855 secs real time (0.006851 user, 0.000001 system)
;;; 71043024 bytes allocated
;;; no minor faults
;;; no major faults
```

We provide an implementation in Gambit Scheme; the nonstandard techniques used
in the implementation are `define-structure`

, `define-macro`

, and DSSSL optional arguments.

There is a git repository of this document, a sample implementation, a test file, and other materials.

Final SRFIs 25, 47, 58, and 63 deal with "Multi-dimensional Array Primitives", "Array", "Array Notation", and "Homogeneous and Heterogeneous Arrays", respectively. Each of these previous SRFIs deal with what we call in this SRFI specialized arrays. Many of the procedures in these previous SRFIs have corresponding forms in this SRFI. For example, from SRFI 63, we can translate:

`(array? obj)`

`(array? obj)`

`(array-rank A)`

`(array-dimension A)`

`(make-array prototype k1 ...)`

`(make-specialized-array (make-interval (vector k1 ...)) storage-class)`

.`(make-shared-array A mapper k1 ...)`

`(specialized-array-share A (make-interval (vector k1 ...)) mapper)`

`(array-in-bounds? A index1 ...)`

`(interval-contains-multi-index? (array-domain A) index1 ...)`

`(array-ref A k1 ...)`

`(let ((A_ (array-getter A))) ... (A_ k1 ...) ... )`

or`(array-ref A k1 ...)`

`(array-set! A obj k1 ...)`

`(let ((A! (array-setter A))) ... (A! obj k1 ...) ...)`

or`(array-set! A obj k1 ...)`

Racket has an extensive array library, written by Neil Toronto, as part of its "Math Library". We give a superficial comparison of some aspects of Racket's library with this proposal:

- Racket's library allows zero-dimensional arrays, which are simply a Scheme value; this proposal does not.
- Racket's library has what it calls broadcasting and slicing; this proposal lacks these features as primitives.
- Racket's nonstrict arrays correspond to our "generalized arrays".
- Racket's arrays axes are indexed from zero; this SRFI allows nonzero lower bounds. Thus Racket's library has no need for array-translate.
- Racket's array-map is similar to the one in this proposal, except that ours always returns a generalized array.
- Racket's array-axis-ref can be implemented in this SRFI with array-permute and array-curry. Racket's array-axis-permute is similar to our array-permute. Both have array-reshape and array-append. Racket's array-flatten is the same as (list->vector (array->list array)) in this proposal.
- Racket's array-axis-fold can be implemented in this SRFI as

If one wants what Racket calls a "strict" array as a result, apply array-copy to the result. One can define Racket's "*-axis-*" routines similarly.`(define (array-axis-fold arr k f init) (array-map (lambda (pencil) (array-foldl f init pencil)) (array-curry (array-permute arr (index-last (array-dimension arr) k)) 1)))`

- Racket's library has specialized mathematical array operations for many math procedures; this library does not.
- Racket's library has flonum and complex flonum arrays; this library has similar features, including for various other homogeneous storage types, and is extendable.
- Racket has many routines to select and recombine data from various axes of arrays, some of which can be simulated in this SRFI with array-permute, array-curry, and array-stack.
- I don't see procedures in Racket's library corresponding to array-curry, array-reverse, or array-sample.
- I don't see a procedure in Racket's library that corresponds to specialized-array-share in this SRFI.

Image processing applications provided significant motivation for this SRFI.

**Manipulating images in PGM format. **On a system with eight-bit chars, one
can write procedures to read and write greyscale images in the PGM format of the Netpbm package as follows. The lexicographical
order in `array-copy`

guarantees the the correct order of execution of the input procedures:

```
(define make-pgm cons)
(define pgm-greys car)
(define pgm-pixels cdr)
(define (read-pgm file)
(define (read-pgm-object port)
(skip-white-space port)
(let ((o (read port)))
;; to skip the newline or next whitespace
(read-char port)
(if (eof-object? o)
(error "reached end of pgm file")
o)))
(define (skip-to-end-of-line port)
(let loop ((ch (read-char port)))
(if (not (eq? ch #\newline))
(loop (read-char port)))))
(define (white-space? ch)
(case ch
((#\newline #\space #\tab) #t)
(else #f)))
(define (skip-white-space port)
(let ((ch (peek-char port)))
(cond ((white-space? ch)
(read-char port)
(skip-white-space port))
((eq? ch #\#)
(skip-to-end-of-line port)
(skip-white-space port))
(else #f))))
;; The image file formats defined in netpbm
;; are problematical because they read the data
;; in the header as variable-length ISO-8859-1 text,
;; including arbitrary whitespace and comments,
;; and then they may read the rest of the file
;; as binary data.
;; So we give here a solution of how to deal
;; with these subtleties in Gambit Scheme.
(call-with-input-file
(list path: file
char-encoding: 'ISO-8859-1
eol-encoding: 'lf)
(lambda (port)
;; We're going to read text for a while,
;; then switch to binary.
;; So we need to turn off buffering until
;; we switch to binary.
(port-settings-set! port '(buffering: #f))
(let* ((header (read-pgm-object port))
(columns (read-pgm-object port))
(rows (read-pgm-object port))
(greys (read-pgm-object port)))
;; Now we switch back to buffering
;; to speed things up.
(port-settings-set! port '(buffering: #t))
(make-pgm
greys
(array-copy
(make-array
(make-interval (vector rows columns))
(cond ((or (eq? header 'p5)
(eq? header 'P5))
;; pgm binary
(if (< greys 256)
;; one byte/pixel
(lambda (i j)
(char->integer
(read-char port)))
;; two bytes/pixel,
;;little-endian
(lambda (i j)
(let* ((first-byte
(char->integer
(read-char port)))
(second-byte
(char->integer
(read-char port))))
(+ (* second-byte 256)
first-byte)))))
;; pgm ascii
((or (eq? header 'p2)
(eq? header 'P2))
(lambda (i j)
(read port)))
(else
(error "not a pgm file"))))
(if (< greys 256)
u8-storage-class
u16-storage-class)))))))
(define (write-pgm pgm-data file #!optional force-ascii)
(call-with-output-file
(list path: file
char-encoding: 'ISO-8859-1
eol-encoding: 'lf)
(lambda (port)
(let* ((greys
(pgm-greys pgm-data))
(pgm-array
(pgm-pixels pgm-data))
(domain
(array-domain pgm-array))
(rows
(fx- (interval-upper-bound domain 0)
(interval-lower-bound domain 0)))
(columns
(fx- (interval-upper-bound domain 1)
(interval-lower-bound domain 1))))
(if force-ascii
(display "P2" port)
(display "P5" port))
(newline port)
(display columns port) (display port)
(display rows port) (newline port)
(display greys port) (newline port)
(array-for-each
(if force-ascii
(let ((next-pixel-in-line 1))
(lambda (p)
(write p port)
(if (fxzero? (fxand next-pixel-in-line 15))
(begin
(newline port)
(set! next-pixel-in-line 1))
(begin
(display port)
(set! next-pixel-in-line
(fx+ 1 next-pixel-in-line))))))
(if (fx< greys 256)
(lambda (p)
(write-u8 p port))
(lambda (p)
(write-u8 (fxand p 255) port)
(write-u8 (fxarithmetic-shift-right p 8)
port))))
pgm-array)))))
```

One can write a a procedure to convolve an image with a filter as follows:

```
(define (array-convolve source filter)
(let* ((source-domain
(array-domain source))
(S_
(array-getter source))
(filter-domain
(array-domain filter))
(F_
(array-getter filter))
(result-domain
(interval-dilate
source-domain
;; the left bound of an interval is an equality,
;; the right bound is an inequality, hence the
;; the difference in the following two expressions
(vector-map -
(interval-lower-bounds->vector filter-domain))
(vector-map (lambda (x)
(- 1 x))
(interval-upper-bounds->vector filter-domain)))))
(make-array result-domain
(lambda (i j)
(array-foldl
(lambda (p q)
(+ p q))
0
(make-array
filter-domain
(lambda (k l)
(* (S_ (+ i k)
(+ j l))
(F_ k l))))))
)))
```

together with some filters

```
(define sharpen-filter
(list->array
'(0 -1 0
-1 5 -1
0 -1 0)
(make-interval '#(-1 -1) '#(2 2))))
(define edge-filter
(list->array
'(0 -1 0
-1 4 -1
0 -1 0)
(make-interval '#(-1 -1) '#(2 2))))
```

Our computations might results in pixel values outside the valid range, so we define

```
(define (round-and-clip pixel max-grey)
(max 0 (min (exact (round pixel)) max-grey)))
```

We can then compute edges and sharpen a test image as follows:

```
(define test-pgm (read-pgm "girl.pgm"))
(let ((greys (pgm-greys test-pgm)))
(write-pgm
(make-pgm
greys
(array-map (lambda (p)
(round-and-clip p greys))
(array-convolve
(pgm-pixels test-pgm)
sharpen-filter)))
"sharper-test.pgm"))
(let* ((greys (pgm-greys test-pgm))
(edge-array
(array-copy
(array-map
abs
(array-convolve
(pgm-pixels test-pgm)
edge-filter))))
(max-pixel
(array-foldl max 0 edge-array))
(normalizer
(inexact (/ greys max-pixel))))
(write-pgm
(make-pgm
greys
(array-map (lambda (p)
(- greys
(round-and-clip (* p normalizer) greys)))
edge-array))
"edge-test.pgm"))
```

**Viewing two-dimensional slices of three-dimensional data. **One example might be viewing two-dimensional slices of three-dimensional data in different ways. If one has a $1024 \times 512\times 512$ 3D image of the body stored as a variable

, then one could get 1024 axial views, each $512\times512$, of this 3D body by `body``(array-curry `

; or 512 median views, each $1024\times512$, by `body` 2)`(array-curry (array-permute `

; or finally 512 frontal views, each again $1024\times512$ pixels, by `body` (index-first 3 1)) 2)`(array-curry (array-permute `

; see Anatomical plane. Note that you want to have the head up in both the median and frontal views, so we use `body` (index-first 3 2)) 2)`index-first`

to provide the appropriate permutations.

**Calculating second differences of images. **For another example, if a real-valued function is defined
on a two-dimensional interval $I$, its second difference in the direction $d$ at the point $x$ is defined as $\Delta^2_df(x)=f(x+2d)-2f(x+d)+f(x)$,
and this function is defined only for those $x$ for which $x$, $x+d$, and $x+2d$ are all in $I$. See the beginning of the section on "Moduli of smoothness" in these notes on wavelets and approximation theory for more details.

Using this definition, the following code computes all second-order forward differences of an image in the directions $d,2 d,3 d,\ldots$, defined only on the domains where this makes sense:

```
(define (all-second-differences image direction)
(let ((image-domain (array-domain image)))
(let loop ((i 1)
(result '()))
(let ((negative-scaled-direction
(vector-map (lambda (j)
(* -1 j i))
direction))
(twice-negative-scaled-direction
(vector-map (lambda (j)
(* -2 j i))
direction)))
(cond ((interval-intersect
image-domain
(interval-translate
image-domain
negative-scaled-direction)
(interval-translate
image-domain
twice-negative-scaled-direction))
=>
(lambda (subdomain)
(loop
(+ i 1)
(cons
(array-copy
(array-map
(lambda (f_i f_i+d f_i+2d)
(+ f_i+2d
(* -2. f_i+d)
f_i))
(array-extract
image
subdomain)
(array-extract
(array-translate
image
negative-scaled-direction)
subdomain)
(array-extract
(array-translate
image
twice-negative-scaled-direction)
subdomain)))
result))))
(else
(reverse result)))))))
```

We can define a small synthetic image of size 8x8 pixels and compute its second differences in various directions:

```
(define image
(array-copy
(make-array (make-interval '#(8 8))
(lambda (i j)
(exact->inexact (+ (* i i) (* j j)))))))
(define (expose difference-images)
(pretty-print (map (lambda (difference-image)
(list (array-domain difference-image)
(array->list difference-image)))
difference-images)))
(begin
(display
"\nSecond-differences in the direction $k\times (1,0)$:\n")
(expose (all-second-differences image '#(1 0)))
(display
"\nSecond-differences in the direction $k\times (1,1)$:\n")
(expose (all-second-differences image '#(1 1)))
(display
"\nSecond-differences in the direction $k\times (1,-1)$:\n")
(expose (all-second-differences image '#(1 -1))))
```

On Gambit 4.8.5, this yields (after some hand editing):

Second-differences in the direction $k\times (1,0)$: ((#<##interval #2 lower-bounds: #(0 0) upper-bounds: #(6 8)> (2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2.)) (#<##interval #3 lower-bounds: #(0 0) upper-bounds: #(4 8)> (8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8. 8.)) (#<##interval #4 lower-bounds: #(0 0) upper-bounds: #(2 8)> (18. 18. 18. 18. 18. 18. 18. 18. 18. 18. 18. 18. 18. 18. 18. 18.))) Second-differences in the direction $k\times (1,1)$: ((#<##interval #5 lower-bounds: #(0 0) upper-bounds: #(6 6)> (4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4.)) (#<##interval #6 lower-bounds: #(0 0) upper-bounds: #(4 4)> (16. 16. 16. 16. 16. 16. 16. 16. 16. 16. 16. 16. 16. 16. 16. 16.)) (#<##interval #7 lower-bounds: #(0 0) upper-bounds: #(2 2)> (36. 36. 36. 36.))) Second-differences in the direction $k\times (1,-1)$: ((#<##interval #8 lower-bounds: #(0 2) upper-bounds: #(6 8)> (4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4. 4.)) (#<##interval #9 lower-bounds: #(0 4) upper-bounds: #(4 8)> (16. 16. 16. 16. 16. 16. 16. 16. 16. 16. 16. 16. 16. 16. 16. 16.)) (#<##interval #10 lower-bounds: #(0 6) upper-bounds: #(2 8)> (36. 36. 36. 36.)))

You can see that with differences in the direction of only the first coordinate, the domains of the difference arrays get smaller in the first coordinate while staying the same in the second coordinate, and with differences in the diagonal directions, the domains of the difference arrays get smaller in both coordinates.

**Separable operators. **Many multi-dimensional transforms in signal processing are *separable*, in that the multi-dimensional transform can be computed by applying one-dimensional transforms in each of the coordinate directions. Examples of such transforms include the Fast Fourier Transform and the Fast Hyperbolic Wavelet Transform. Each one-dimensional subdomain of the complete domain is called a *pencil*, and the same one-dimensional transform is applied to all pencils in a given direction. Given the one-dimensional array transform, one can define the multidimensional transform as follows:

```
(define (make-separable-transform 1D-transform)
(lambda (a)
(let ((n (array-dimension a)))
(do ((d 0 (fx+ d 1)))
((fx= d n))
(array-for-each
1D-transform
(array-curry (array-permute a (index-last n d)) 1))))))
```

Here we put each axis in turn at the end and then apply `1D-transform`

to each of the pencils along that axis.

Wavelet transforms in particular are calculated by recursively applying a transform to an array and then downsampling the array; the inverse transform recursively downsamples and then applies a transform. So we define the following primitives:

```
(define (recursively-apply-transform-and-downsample transform)
(lambda (a)
(let ((sample-vector (make-vector (array-dimension a) 2)))
(define (helper a)
(if (fx< 1 (interval-upper-bound (array-domain a) 0))
(begin
(transform a)
(helper (array-sample a sample-vector)))))
(helper a))))
(define (recursively-downsample-and-apply-transform transform)
(lambda (a)
(let ((sample-vector (make-vector (array-dimension a) 2)))
(define (helper a)
(if (fx< 1 (interval-upper-bound (array-domain a) 0))
(begin
(helper (array-sample a sample-vector))
(transform a))))
(helper a))))
```

By adding a single loop that calculates scaled sums and differences of adjacent elements in a one-dimensional array, we can define various Haar wavelet transforms as follows:

```
(define (1D-Haar-loop a)
(let ((a_ (array-getter a))
(a! (array-setter a))
(n (interval-upper-bound (array-domain a) 0)))
(do ((i 0 (fx+ i 2)))
((fx= i n))
(let* ((a_i (a_ i))
(a_i+1 (a_ (fx+ i 1)))
(scaled-sum (fl/ (fl+ a_i a_i+1) (flsqrt 2.0)))
(scaled-difference (fl/ (fl- a_i a_i+1) (flsqrt 2.0))))
(a! scaled-sum i)
(a! scaled-difference (fx+ i 1))))))
(define 1D-Haar-transform
(recursively-apply-transform-and-downsample 1D-Haar-loop))
(define 1D-Haar-inverse-transform
(recursively-downsample-and-apply-transform 1D-Haar-loop))
(define hyperbolic-Haar-transform
(make-separable-transform 1D-Haar-transform))
(define hyperbolic-Haar-inverse-transform
(make-separable-transform 1D-Haar-inverse-transform))
(define Haar-transform
(recursively-apply-transform-and-downsample
(make-separable-transform 1D-Haar-loop)))
(define Haar-inverse-transform
(recursively-downsample-and-apply-transform
(make-separable-transform 1D-Haar-loop)))
```

We then define an image that is a multiple of a single, two-dimensional hyperbolic Haar wavelet, compute its hyperbolic Haar transform (which should have only one nonzero coefficient), and then the inverse transform:

```
(let ((image
(array-copy
(make-array (make-interval '#(4 4))
(lambda (i j)
(case i
((0) 1.)
((1) -1.)
(else 0.)))))))
(display "
Initial image:
")
(pretty-print (list (array-domain image)
(array->list image)))
(hyperbolic-Haar-transform image)
(display "\nArray of hyperbolic Haar wavelet coefficients: \n")
(pretty-print (list (array-domain image)
(array->list image)))
(hyperbolic-Haar-inverse-transform image)
(display "\nReconstructed image: \n")
(pretty-print (list (array-domain image)
(array->list image))))
```

This yields:

Initial image: (#<##interval #11 lower-bounds: #(0 0) upper-bounds: #(4 4)> (1. 1. 1. 1. -1. -1. -1. -1. 0. 0. 0. 0. 0. 0. 0. 0.)) Array of hyperbolic Haar wavelet coefficients: (#<##interval #11 lower-bounds: #(0 0) upper-bounds: #(4 4)> (0. 0. 0. 0. 2.8284271247461894 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.)) Reconstructed image: (#<##interval #11 lower-bounds: #(0 0) upper-bounds: #(4 4)> (.9999999999999996 .9999999999999996 .9999999999999996 .9999999999999996 -.9999999999999996 -.9999999999999996 -.9999999999999996 -.9999999999999996 0. 0. 0. 0. 0. 0. 0. 0.))

In perfect arithmetic, this hyperbolic Haar transform is *orthonormal*, in that the sum of the squares of the elements of the image is the same as the sum of the squares of the hyperbolic Haar coefficients of the image. We can see that this is approximately true here.

We can apply the (nonhyperbolic) Haar transform to the same image as follows:

```
(let ((image
(array-copy
(make-array (make-interval '#(4 4))
(lambda (i j)
(case i
((0) 1.)
((1) -1.)
(else 0.)))))))
(display "\nInitial image:\n")
(pretty-print (list (array-domain image)
(array->list image)))
(Haar-transform image)
(display "\nArray of Haar wavelet coefficients: \n")
(pretty-print (list (array-domain image)
(array->list image)))
(Haar-inverse-transform image)
(display "\nReconstructed image: \n")
(pretty-print (list (array-domain image)
(array->list image))))
```

This yields:

Initial image: (#<##interval #12 lower-bounds: #(0 0) upper-bounds: #(4 4)> (1. 1. 1. 1. -1. -1. -1. -1. 0. 0. 0. 0. 0. 0. 0. 0.)) Array of Haar wavelet coefficients: (#<##interval #12 lower-bounds: #(0 0) upper-bounds: #(4 4)> (0. 0. 0. 0. 1.9999999999999998 0. 1.9999999999999998 0. 0. 0. 0. 0. 0. 0. 0. 0.)) Reconstructed image: (#<##interval #12 lower-bounds: #(0 0) upper-bounds: #(4 4)> (.9999999999999997 .9999999999999997 .9999999999999997 .9999999999999997 -.9999999999999997 -.9999999999999997 -.9999999999999997 -.9999999999999997 0. 0. 0. 0. 0. 0. 0. 0.))

You see in this example that this particular image has two, not one, nonzero coefficients in the two-dimensional Haar transform, which is again approximately orthonormal.

**Matrix multiplication and Gaussian elimination. **While we have avoided conflating matrices and arrays, we give here some examples of matrix operations defined using operations from this SRFI.

Given a nonsingular square matrix $A$ we can overwrite $A$ with lower-triangular matrix $L$ with ones on the diagonal and upper-triangular
matrix $U$ so that $A=LU$ as follows. (We assume "pivoting" isn't needed.) For example, if $$A=\begin{pmatrix} a_{11}&a_{12}&a_{13}\\ a_{21}&a_{22}&a_{23}\\ a_{31}&a_{32}&a_{33}\end{pmatrix}=\begin{pmatrix} 1&0&0\\ \ell_{21}&1&0\\ \ell_{31}&\ell_{32}&1\end{pmatrix}\begin{pmatrix} u_{11}&u_{12}&u_{13}\\ 0&u_{22}&u_{23}\\ 0&0&u_{33}\end{pmatrix}$$ then $A$ is overwritten with
$$
\begin{pmatrix} u_{11}&u_{12}&u_{13}\\ \ell_{21}&u_{22}&u_{23}\\ \ell_{31}&\ell_{32}&u_{33}\end{pmatrix}.
$$
The code uses `array-map`

, `array-assign!`

, `specialized-array-share`

, `array-extract`

, and `array-outer-product`

.

```
(define (LU-decomposition A)
;; Assumes the domain of A is [0,n)\times [0,n)
;; and that Gaussian elimination can be applied
;; without pivoting.
(let ((n
(interval-upper-bound (array-domain A) 0))
(A_
(array-getter A)))
(do ((i 0 (fx+ i 1)))
((= i (fx- n 1)) A)
(let* ((pivot
(A_ i i))
(column/row-domain
;; both will be one-dimensional
(make-interval (vector (+ i 1))
(vector n)))
(column
;; the column below the (i,i) entry
(specialized-array-share A
column/row-domain
(lambda (k)
(values k i))))
(row
;; the row to the right of the (i,i) entry
(specialized-array-share A
column/row-domain
(lambda (k)
(values i k))))
;; the subarray to the right and
;; below the (i,i) entry
(subarray
(array-extract
A (make-interval
(vector (fx+ i 1) (fx+ i 1))
(vector n n)))))
;; Compute multipliers.
(array-assign!
column
(array-map (lambda (x)
(/ x pivot))
column))
;; Subtract the outer product of i'th
;; row and column from the subarray.
(array-assign!
subarray
(array-map -
subarray
(array-outer-product * column row)))))))
```

We then define a $4\times 4$ Hilbert matrix:

```
(define A
(array-copy
(make-array (make-interval '#(4 4))
(lambda (i j)
(/ (+ 1 i j))))))
(define (array-display A)
(define (display-item x)
(display x) (display "\t"))
(newline)
(case (array-dimension A)
((1) (array-for-each display-item A) (newline))
((2) (array-for-each (lambda (row)
(array-for-each display-item row)
(newline))
(array-curry A 1)))
(else
(error "array-display can't handle > 2 dimensions: " A))))
(display "\nHilbert matrix:\n\n")
(array-display A)
;;; which displays:
;;; 1 1/2 1/3 1/4
;;; 1/2 1/3 1/4 1/5
;;; 1/3 1/4 1/5 1/6
;;; 1/4 1/5 1/6 1/7
(LU-decomposition A)
(display "\nLU decomposition of Hilbert matrix:\n\n")
(array-display A)
;;; which displays:
;;; 1 1/2 1/3 1/4
;;; 1/2 1/12 1/12 3/40
;;; 1/3 1 1/180 1/120
;;; 1/4 9/10 3/2 1/2800
```

We can now define matrix multiplication as follows to check our result:

```
;;; Functions to extract the lower- and upper-triangular
;;; matrices of the LU decomposition of A.
(define (L a)
(let ((a_ (array-getter a))
(d (array-domain a)))
(make-array
d
(lambda (i j)
(cond ((= i j) 1) ;; diagonal
((> i j) (a_ i j)) ;; below diagonal
(else 0)))))) ;; above diagonal
(define (U a)
(let ((a_ (array-getter a))
(d (array-domain a)))
(make-array
d
(lambda (i j)
(cond ((<= i j) (a_ i j)) ;; diagonal and above
(else 0)))))) ;; below diagonal
(display "\nLower triangular matrix of decomposition of Hilbert matrix:\n\n")
(array-display (L A))
;;; which displays:
;;; 1 0 0 0
;;; 1/2 1 0 0
;;; 1/3 1 1 0
;;; 1/4 9/10 3/2 1
(display "\nUpper triangular matrix of decomposition of Hilbert matrix:\n\n")
(array-display (U A))
;;; which displays:
;;; 1 1/2 1/3 1/4
;;; 0 1/12 1/12 3/40
;;; 0 0 1/180 1/120
;;; 0 0 0 1/2800
;;; We'll define a brief, not-very-efficient matrix multiply procedure.
(define (matrix-multiply A B)
(array-inner-product A + * B))
;;; We'll check that the product of the result of LU
;;; decomposition of A is again A.
(define product (matrix-multiply (L A) (U A)))
(display "\nProduct of lower and upper triangular matrices \n")
(display "of LU decomposition of Hilbert matrix:\n\n")
(array-display product)
;;; which displays:
;;; 1 1/2 1/3 1/4
;;; 1/2 1/3 1/4 1/5
;;; 1/3 1/4 1/5 1/6
;;; 1/4 1/5 1/6 1/7
```

**Inner products. **Our `array-inner-product`

procedure differs from that found in APL in several ways: The arguments

and `A`

must each have two or more dimensions, and the result is always an array, never a scalar.`B`

We take some examples from the APLX Language Reference:

```
;; Examples from
;; http://microapl.com/apl_help/ch_020_020_880.htm
(define TABLE1
(list->array
'(1 2
5 4
3 0)
(make-interval '#(3 2))))
(define TABLE2
(list->array
'(6 2 3 4
7 0 1 8)
(make-interval '#(2 4))))
(array-display (inner-product TABLE1 + * TABLE2))
;;; Displays
;;; 20 2 5 20
;;; 58 10 19 52
;;; 18 6 9 12
(define X ;; a "row vector"
(list->array '(1 3 5 7) (make-interval '#(1 4))))
(define Y ;; a "column vector"
(list->array '(2 3 6 7) (make-interval '#(4 1))))
(array-display (inner-product X + (lambda (x y) (if (= x y) 1 0)) Y))
;;; Displays
;;; 2
```

The SRFI author thanks Edinah K Gnang, John Cowan, Sudarshan S Chawathe, Jamison Hope, Per Bothner, and Alex Shinn for their comments and suggestions, and Arthur A. Gleckler, SRFI Editor, for his guidance and patience.

- "multi-dimensional arrays in R5RS?", by Alan Bawden.
- SRFI 4: Homogeneous Numeric Vector Datatypes, by Marc Feeley.
- SRFI 25: Multi-dimensional Array Primitives, by Jussi Piitulainen.
- SRFI 47: Array, by Aubrey Jaffer.
- SRFI 58: Array Notation, by Aubrey Jaffer.
- SRFI 63: Homogeneous and Heterogeneous Arrays, by Aubrey Jaffer.
- SRFI 164: Enhanced multi-dimensional Arrays, by Per Bothner.

© 2016, 2018, 2020, 2022 Bradley J Lucier. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Editor: Arthur A. Gleckler