diff --git a/generic-arrays.scm b/generic-arrays.scm index 73f9ad6..dd89b3f 100644 --- a/generic-arrays.scm +++ b/generic-arrays.scm @@ -99,18 +99,16 @@ (declare (not inline)) (define (make-interval lower-bounds upper-bounds) - (cond ((not (vector? lower-bounds)) - (error "make-interval: lower-bounds must be a vector: " lower-bounds)) - ((not (vector? upper-bounds)) - (error "make-interval: upper-bounds must be a vector: " upper-bounds)) + (cond ((not (and (vector? lower-bounds) + (< 0 (vector-length lower-bounds)) + (##vector-every ##exact-integer? lower-bounds))) + (error "make-interval: The first argument is not a nonempty vector of exact integers: " lower-bounds upper-bounds)) + ((not (and (vector? upper-bounds) + (< 0 (vector-length upper-bounds)) + (##vector-every ##exact-integer? upper-bounds))) + (error "make-interval: The second argument is not a nonempty vector of exact integers: " lower-bounds upper-bounds)) ((not (= (vector-length lower-bounds) (vector-length upper-bounds))) - (error "make-interval: lower-bounds and upper-bounds must be the same length: " lower-bounds upper-bounds)) - ((not (< 0 (vector-length lower-bounds))) - (error "make-interval: lower-bounds and upper-bounds must be nonempty vectors: " lower-bounds upper-bounds)) - ((not (##vector-every ##exact-integer? lower-bounds)) - (error "make-interval: All lower-bounds must be exact integers: " lower-bounds)) - ((not (##vector-every ##exact-integer? upper-bounds)) - (error "make-interval: All upper-bounds must be exact integers: " upper-bounds)) + (error "make-interval: The first and second arguments are not the same length: " lower-bounds upper-bounds)) ((not (##vector-every (lambda (x y) (< x y)) lower-bounds upper-bounds)) (error "make-interval: Each lower-bound must be less than the associated upper-bound: " lower-bounds upper-bounds)) (else @@ -299,6 +297,28 @@ (make-##interval (##vector-map + (interval-lower-bounds->vector Interval) translation) (##vector-map + (interval-upper-bounds->vector Interval) translation))) +(define (##interval-scale interval scales) + (let* ((uppers (##interval-upper-bounds->vector interval)) + (lowers (##interval-lower-bounds->vector interval)) + (new-uppers (##vector-map (lambda (u s) + (quotient (+ u s -1) s)) + uppers scales))) + (make-##interval lowers new-uppers))) + +(define (interval-scale interval scales) + (cond ((not (and (interval? interval) + (##vector-every zero? (interval-lower-bounds->vector interval)))) + (error "interval-scale: The first argument is not an interval with all lower bounds zero: " interval scales)) + ((not (and (vector? scales) + (##vector-every ##exact-integer? scales) + (##vector-every positive? scales))) + (error "interval-scale: The second argument is not a vector of positive, exact, integers: " interval scales)) + ((not (= (vector-length scales) (interval-dimension interval))) + (error "interval-scale: The dimension of the first argument (an interval) is not equal to the length of the second (a vector): " + interval scales)) + (else + (##interval-scale interval scales)))) + (define (interval-dilate interval lower-diffs upper-diffs) (cond ((not (interval? interval)) (error "interval-dilate: first argument is not an interval: " interval lower-diffs upper-diffs)) @@ -545,24 +565,14 @@ ;;; The order of application of f and operator is not specified. -(define (interval-reduce f operator identity interval) - (cond ((not (interval? interval)) - (error "interval-reduce: Argument is not a interval: " interval)) - ((not (procedure? f)) - (error "interval-reduce: Argument is not a procedure: " f)) - ((not (procedure? operator)) - (error "interval-reduce: Operator is not a procedure: " operator)) - (else - (##interval-reduce f operator identity interval)))) - -(define (##interval-reduce f operator identity interval) +(define (##interval-fold f operator identity interval) (case (##interval-dimension interval) ((1) (let ((lower-i (##interval-lower-bound interval 0)) (upper-i (##interval-upper-bound interval 0))) (let i-loop ((i lower-i) (result identity)) (if (= i upper-i) result - (i-loop (+ i 1) (operator result (f i))))))) + (i-loop (+ i 1) (operator (f i) result)))))) ((2) (let ((lower-i (##interval-lower-bound interval 0)) (lower-j (##interval-lower-bound interval 1)) (upper-i (##interval-upper-bound interval 0)) @@ -573,7 +583,7 @@ (let j-loop ((j lower-j) (result result)) (if (= j upper-j) (i-loop (+ i 1) result) - (j-loop (+ j 1) (operator result (f i j))))))))) + (j-loop (+ j 1) (operator (f i j) result)))))))) ((3) (let ((lower-i (##interval-lower-bound interval 0)) (lower-j (##interval-lower-bound interval 1)) (lower-k (##interval-lower-bound interval 2)) @@ -589,7 +599,7 @@ (let k-loop ((k lower-k) (result result)) (if (= k upper-k) (j-loop (+ j 1) result) - (k-loop (+ k 1) (operator result (f i j k))))))))))) + (k-loop (+ k 1) (operator (f i j k) result)))))))))) ((4) (let ((lower-i (##interval-lower-bound interval 0)) (lower-j (##interval-lower-bound interval 1)) (lower-k (##interval-lower-bound interval 2)) @@ -610,7 +620,7 @@ (let l-loop ((l lower-l) (result result)) (if (= l upper-l) (k-loop (+ k 1) result) - (l-loop (+ l 1) (operator result (f i j k l))))))))))))) + (l-loop (+ l 1) (operator (f i j k l) result)))))))))))) (else (let* ((lower-bounds (##interval-lower-bounds->list interval)) (upper-bounds (##interval-upper-bounds->list interval)) @@ -636,7 +646,7 @@ (begin (set-car! arg-tail i) (loop (+ i 1) - (operator result (apply f arg)))))) + (operator (apply f arg) result))))) (let loop ((i lower-bound) (result result)) (if (= i upper-bound) @@ -693,11 +703,11 @@ ((procedure? setter) setter) (else - (error "make-array: setter is not a procedure: " domain getter setter))))) + (error "make-array: The third argument is not a procedure: " domain getter setter))))) (cond ((not (interval? domain)) - (error "make-array: domain is not an interval: " domain getter setter)) + (error "make-array: The first argument is not an interval: " domain getter setter)) ((not (procedure? getter)) - (error "make-array: getter is not a procedure: " domain getter setter)) + (error "make-array: The second argument is not a procedure: " domain getter setter)) (else (make-##array-base domain getter @@ -961,7 +971,7 @@ ;;; an example of an array that can return multiple values. ;;; ;;; Rather than trying to formalize this idea, and trying to get it to work with array-map, -;;; array-reduce, ..., we'll just manipulate the getter functions of these conceptual arrays. +;;; array-fold, ..., we'll just manipulate the getter functions of these conceptual arrays. ;;; ;;; Indexers are 1-1 affine maps from one interval to another. ;;; @@ -1523,41 +1533,58 @@ (result (specialized-array domain result-storage-class safe?)) - (result-setter (array-setter result)) - (getter (array-getter array)) - (checker (storage-class-checker result-storage-class))) - (##interval-for-each (case (##interval-dimension domain) - ((1) (lambda (i) - (let ((item (getter i))) - (if (checker item) - (result-setter item i) - (error "array->specialized-array: not all elements of the array can be manipulated by the storage class: " - array result-storage-class safe?))))) - ((2) (lambda (i j) - (let ((item (getter i j))) - (if (checker item) - (result-setter item i j) - (error "array->specialized-array: not all elements of the array can be manipulated by the storage class: " - array result-storage-class safe?))))) - ((3) (lambda (i j k) - (let ((item (getter i j k))) - (if (checker item) - (result-setter item i j k) - (error "array->specialized-array: not all elements of the array can be manipulated by the storage class: " - array result-storage-class safe?))))) - ((4) (lambda (i j k l) - (let ((item (getter i j k l))) - (if (checker item) - (result-setter item i j k l) - (error "array->specialized-array: not all elements of the array can be manipulated by the storage class: " - array result-storage-class safe?))))) - (else (lambda multi-index - (let ((item (apply getter multi-index))) - (if (checker item) - (apply result-setter item multi-index) - (error "array->specialized-array: not all elements of the array can be manipulated by the storage class: " - array result-storage-class safe?)))))) - domain) + (getter (array-getter array))) + (if (eq? result-storage-class generic-storage-class) ;; checker always returns #t + (let ((body (array-body result)) + (indexer (array-indexer result))) + (##interval-for-each + (case (##interval-dimension domain) + ((1) (lambda (i) + (vector-set! body (indexer i) (getter i)))) + ((2) (lambda (i j) + (vector-set! body (indexer i j) (getter i j)))) + ((3) (lambda (i j k) + (vector-set! body (indexer i j k) (getter i j k)))) + ((4) (lambda (i j k l) + (vector-set! body (indexer i j k l) (getter i j k l)))) + (else (lambda multi-index + (vector-set! body (apply indexer multi-index) (apply getter multi-index))))) + domain)) + (let ((checker (storage-class-checker result-storage-class)) + (result-setter (array-setter result))) + (##interval-for-each + (case (##interval-dimension domain) + ((1) (lambda (i) + (let ((item (getter i))) + (if (checker item) + (result-setter item i) + (error "array->specialized-array: not all elements of the array can be manipulated by the storage class: " + array result-storage-class safe?))))) + ((2) (lambda (i j) + (let ((item (getter i j))) + (if (checker item) + (result-setter item i j) + (error "array->specialized-array: not all elements of the array can be manipulated by the storage class: " + array result-storage-class safe?))))) + ((3) (lambda (i j k) + (let ((item (getter i j k))) + (if (checker item) + (result-setter item i j k) + (error "array->specialized-array: not all elements of the array can be manipulated by the storage class: " + array result-storage-class safe?))))) + ((4) (lambda (i j k l) + (let ((item (getter i j k l))) + (if (checker item) + (result-setter item i j k l) + (error "array->specialized-array: not all elements of the array can be manipulated by the storage class: " + array result-storage-class safe?))))) + (else (lambda multi-index + (let ((item (apply getter multi-index))) + (if (checker item) + (apply result-setter item multi-index) + (error "array->specialized-array: not all elements of the array can be manipulated by the storage class: " + array result-storage-class safe?)))))) + domain))) result)))) ;;; @@ -1936,6 +1963,264 @@ (else (##immutable-array-permute Array permutation)))) +(define-macro (setup-reversed-getters-and-setters) + + (define (make-symbol . args) + (string->symbol + (apply string-append + (map (lambda (x) + (cond ((string? x) x) + ((symbol? x) (symbol->string x)) + ((number? x) (number->string x)))) + args)))) + + (define (truth-table n) ;; generate all combinations of n #t and #f + (if (zero? n) + '(()) + (let ((subtable (truth-table (- n 1)))) + (apply append (map (lambda (value) + (map (lambda (t) + (cons value t)) + subtable)) + '(#t #f)))))) + + (define (iota n) + ;; generates list of (- n 1) ... 0 + (if (zero? n) + '() + (cons (- n 1) (iota (- n 1))))) + + + (define (generate-code-for-fixed-n name transformer n) + (let ((zero-to-n-1 + (reverse (iota n))) + (table + (truth-table n))) + `((,n) (let (,@(map (lambda (k) + `(,(make-symbol 'adjust_ k) (+ (##interval-upper-bound interval ,k) + (##interval-lower-bound interval ,k) + -1))) + zero-to-n-1)) + (cond ,@(map (lambda (table-entry) + `((equal? flip? ',(list->vector table-entry)) + (lambda ,(transformer (map (lambda (k) + (make-symbol 'i_ k)) + zero-to-n-1)) + (,name ,@(transformer (map (lambda (flip? k) + (if flip? + `(- ,(make-symbol 'adjust_ k) + ,(make-symbol 'i_ k)) + `,(make-symbol 'i_ k))) + table-entry zero-to-n-1)))))) + table)))))) + + (define (reverser name transform-arguments) + `(define (,(make-symbol name '-reverse) ,name flip? interval) + (case (vector-length flip?) + ,@(map (lambda (n) + (generate-code-for-fixed-n name transform-arguments n)) + '(1 2 3 4)) + (else + (let ((n + (vector-length flip?)) + (flip? + (vector->list flip?)) + (adjust + (map (lambda (u_k l_k) + (+ u_k l_k -1)) + (vector->list (##interval-upper-bounds interval)) + (vector->list (##interval-lower-bounds interval))))) + (lambda ,(transform-arguments 'indices) + (if (not (= (length indices) n)) + (error "number of indices does not equal array dimension: " indices) + (apply ,name ,@(transform-arguments '((map (lambda (i adjust flip?) + (if flip? + (- adjust i) + i)) + indices adjust flip?))))))))))) + (let ((result + `(begin + ,(reverser '##getter values) + ,(reverser '##setter (lambda (args) (cons 'v args)))))) + #;(pp result) + result)) + +(setup-reversed-getters-and-setters) + +(define (##immutable-array-reverse Array flip?) + (make-array (array-domain Array) + (##getter-reverse (array-getter Array) flip? (array-domain Array)))) + +(define (##mutable-array-reverse Array flip?) + (make-array (array-domain Array) + (##getter-reverse (array-getter Array) flip? (array-domain Array)) + (##setter-reverse (array-setter Array) flip? (array-domain Array)))) + +(define (##specialized-array-reverse Array flip?) + (specialized-array-share Array + (array-domain Array) + (##getter-reverse values flip? (array-domain Array)))) + +(define (array-reverse Array flip?) + (cond ((not (array? Array)) + (error "array-reverse: the first argument is not an array: " Array flip?)) + ((not (and (vector? flip?) + (##vector-every boolean? flip?))) + (error "array-reverse: the second argument is not a vector of booleans: " Array flip?)) + ((not (fx= (array-dimension Array) + (vector-length flip?))) + (error "array-reverse: the dimension of the first argument (an array) does not equal the dimension of the second argument (a vector of booleans): " Array flip?)) + ((specialized-array? Array) + (##specialized-array-reverse Array flip?)) + ((mutable-array? Array) + (##mutable-array-reverse Array flip?)) + (else + (##immutable-array-reverse Array flip?)))) + + + +(define-macro (macro-generate-sample) + + (define (make-symbol . args) + (string->symbol + (apply string-append + (map (lambda (x) + (cond ((string? x) x) + ((symbol? x) (symbol->string x)) + ((number? x) (number->string x)))) + args)))) + + (define (take l n) + (if (zero? n) + '() + (cons (car l) (take (cdr l) (- n 1))))) + + (define (remove l n) + (if (zero? n) + l + (remove (cdr l) (- n 1)))) + + (define (first-half l) + (take l (quotient (length l) 2))) + + (define (second-half l) + (remove l (quotient (length l) 2))) + + (define (iota n) + ;; generates list of (- n 1) ... 0 + (if (zero? n) + '() + (cons (- n 1) (iota (- n 1))))) + + (define (arg-lists ks) + (if (null? ks) + '(()) + (let* ((k (car ks)) + (i_k (make-symbol 'i_ k)) + (s_k (make-symbol 's_ k)) + (sublists + (arg-lists (cdr ks))) + (plains + (map (lambda (l) + (cons i_k l)) + sublists)) + (scales + (map (lambda (l) + (cons `(* ,i_k ,s_k) l)) + sublists))) + (append plains + scales)))) + + (define (transformer args) args) + (define name 'getter) + + (define (code-for-one-n name transformer n) + (let* ((zero-to-n-1 + (reverse (iota n))) + (arg-list + (map (lambda (k) + (make-symbol 'i_ k)) + zero-to-n-1)) + (args + (arg-lists zero-to-n-1))) + (define (build-code args ks) + (if (null? (cdr args)) + `(lambda ,(transformer arg-list) + (,name ,@(transformer (car args)))) + (let* ((k (car ks)) + (s_k (make-symbol 's_ k)) + (plains (first-half args)) + (scales (second-half args))) + `(if (= 1 ,s_k) + ,(build-code plains (cdr ks)) + ,(build-code scales (cdr ks)))))) + `((,n) + (let (,@(map (lambda (k) + `(,(make-symbol 's_ k) (vector-ref scales ,k))) + zero-to-n-1)) + ,(build-code args zero-to-n-1))))) + + (define (sampler name transformer) + `(define (,(make-symbol name '-sample) ,name scales interval) + (case (vector-length scales) + ,@(map (lambda (n) + (code-for-one-n name transformer n)) + '(1 2 3 4)) + (else + (let ((n + (vector-length scales)) + (scales + (vector->list scales))) + (lambda ,(transformer 'indices) + (if (not (= (length indices) n)) + (error "number of indices does not equal array dimension: " indices) + (apply ,name ,@(transformer '((map (lambda (i s) + (* s i)) + indices scales))))))))))) + + + + (let ((result + `(begin + ,(sampler '##getter values) + ,(sampler '##setter (lambda (args) (cons 'v args)))))) + #;(pp result) + result)) + +(macro-generate-sample) + + +(define (##immutable-array-sample array scales) + (make-array (##interval-scale (array-domain array) scales) + (##getter-sample (array-getter array) scales (array-domain array)))) + +(define (##mutable-array-sample array scales) + (make-array (##interval-scale (array-domain array) scales) + (##getter-sample (array-getter array) scales (array-domain array)) + (##setter-sample (array-setter array) scales (array-domain array)))) + +(define (##specialized-array-sample array scales) + (specialized-array-share array + (##interval-scale (array-domain array) scales) + (##getter-sample values scales (array-domain array)))) + +(define (array-sample array scales) + (cond ((not (and (array? array) + (##vector-every zero? (interval-lower-bounds->vector (array-domain array))))) + (error "array-sample: The first argument is an array whose domain has nonzero lower bounds: " array scales)) + ((not (and (vector? scales) + (##vector-every ##exact-integer? scales) + (##vector-every positive? scales))) + (error "array-sample: The second argument is not a vector of positive, exact, integers: " array scales)) + ((not (= (vector-length scales) (array-dimension array))) + (error "array-sample: The dimension of the first argument (an array) is not equal to the length of the second (a vector): " + array scales)) + ((specialized-array? array) + (##specialized-array-sample array scales)) + ((mutable-array? array) + (##mutable-array-sample array scales)) + (else + (##immutable-array-sample array scales)))) (define (##immutable-array-curry Array right-dimension) (call-with-values @@ -2090,7 +2375,31 @@ (getter-2 i j k l)))) (else (lambda multi-index (f (apply getter-0 multi-index) (apply getter-1 multi-index) - (apply getter-2 multi-index))))))) + (apply getter-2 multi-index))))))) + ((3) (let ((getter-1 (array-getter (car arrays))) + (getter-2 (array-getter (cadr arrays))) + (getter-3 (array-getter (caddr arrays)))) + (case (##interval-dimension domain) + ((1) (lambda (i) (f (getter-0 i) + (getter-1 i) + (getter-2 i) + (getter-3 i)))) + ((2) (lambda (i j) (f (getter-0 i j) + (getter-1 i j) + (getter-2 i j) + (getter-3 i j)))) + ((3) (lambda (i j k) (f (getter-0 i j k) + (getter-1 i j k) + (getter-2 i j k) + (getter-3 i j k)))) + ((4) (lambda (i j k l) (f (getter-0 i j k l) + (getter-1 i j k l) + (getter-2 i j k l) + (getter-3 i j k l)))) + (else (lambda multi-index (f (apply getter-0 multi-index) + (apply getter-1 multi-index) + (apply getter-2 multi-index) + (apply getter-3 multi-index))))))) (else (let ((getters (cons getter-0 (map array-getter arrays)))) (case (##interval-dimension domain) @@ -2131,14 +2440,24 @@ ;;; This version assumes, and may use, that (array-getter a) is thread-safe and that operator is associative. ;;; The order of application of (array-getter) and operator is not specified. -(define (array-reduce op id a) +(define (##array-fold op id a) + (##interval-fold (array-getter a) op id (array-domain a))) + +(define (array-fold op id a) (cond ((not (procedure? op)) - (error "array-reduce: operator is not a procedure: " op)) + (error "array-fold: The first argument is not a procedure: " op id a)) ((not (array? a)) - (error "array-reduce: argument is not an array: " a)) + (error "array-fold: The third argument is not an array: " op id a)) (else - (##interval-reduce (array-getter a) op id (array-domain a))))) + (##array-fold op id a)))) +(define (array-fold-right op id a) + (cond ((not (procedure? op)) + (error "array-fold-right: The first argument is not a procedure: " op id a)) + ((not (array? a)) + (error "array-fold-right: The third argument is not an array: " op id a)) + (else + (##array-fold op id (array-reverse a (make-vector (array-dimension a) #t)))))) (define (array-every? proc a) @@ -2147,22 +2466,16 @@ ((not (array? a)) (error "array-every?: The second argument is not an array: " proc a)) (else - (##interval-reduce (array-getter a) - (lambda (result x) - (and result (proc x))) - #t - (array-domain a))))) - - + (##array-fold (lambda (x result) + (and x result)) + #t + (array-map proc a))))) (define (array->list array) (cond ((not (array? array)) (error "array->list: object is not an array: " array)) (else - (reverse (array-reduce (lambda (result a_i) - (cons a_i result)) - '() - array))))) + (array-fold-right cons '() array)))) (define (list->specialized-array l interval #!optional (result-storage-class generic-storage-class) (safe? (specialized-array-default-safe?))) (cond ((not (list? l)) diff --git a/srfi-122.html b/srfi-122.html index 7023e45..76e08bf 100644 --- a/srfi-122.html +++ b/srfi-122.html @@ -29,7 +29,8 @@
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 a rectangular interval $[l_0,u_0)\times[l_1,u_1)\times\cdots\times[l_{d-1},u_{d-1})$, a subset of $\mathbb Z^d$, $d$-tuples of integers. Thus, we introduce a data type called intervals, which encapsulate the cross product of nonempty intervals of exact integers. Specialized variants of arrays are specified to provide portable programs with efficient representations for common use cases.
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:
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. array-translate
to provide this operation.array-permute
for this operation. The only nonidentity permutation of a two-dimensional spreadsheet turns rows into columns and vice versa.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.We make several remarks. First, all these operations could have been computed by specifying the particular mapping $T_{BA}$ explicitly, so that these routines, where one specifies the translation $\vec d$ or the permutation $\pi$ or the outer dimension $r$ of $D_A$ (in the currying example) are simply "convenience" procedures. Second, because the composition of any number of affine mappings are 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 tranforms to multi-dimensional separable transforms, or quickly generating two-dimensional slices of three-dimensional image data. Examples are given below.
+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. array-translate
to provide this operation.array-permute
for this operation. The only nonidentity permutation of a two-dimensional spreadsheet turns rows into columns and vice versa.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.#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).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 routines are simply "convenience" procedures. Second, because the composition of any number of affine mappings are 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 tranforms 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. @@ -67,21 +70,10 @@
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 routine 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 A)
by A-getter
, then if B is the result of array-extract
applied to A, then (array-getter B)
is simply A-getter
. Similarly, if A is a two-dimensional array, and B is derived from A by applying the permutation $\pi((i,j))=(j,i)$, then (array-getter B)
is (lambda (i j) (A-getter j i))
. Translation and currying also lead to transformed arrays whose getters are relatively efficiently derived from A-getter
, at least for arrays of small dimension.
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 functions array-extract
, array-translate
, array-permute
, and array-curry
, and we provide relatively efficient implementations of these functions for arrays of dimension no greater than four.
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 functions array-extract
, array-translate
, array-permute
, array-curry
, array-reverse
, and array-sample
, and we provide relatively efficient implementations of these functions for arrays of dimension no greater than four.
Daniel Friedman and David Wise wrote a famous paper CONS should not Evaluate its Arguments. In the spirit of that paper, our procedure array-map
does not immediately produce a specialized array, but a simple immutable array, whose elements are recomputed from the arguments of array-map
each time they are accessed. This immutable array can be passed on to further applications of array-map
for further processing, without generating the storage bodies for intermediate arrays.
We provide the procedure array->specialized-array
to transform a generalized array (like that returned by array-map
) to a specialized, Bawden-style array, for which accessing each element again takes $O(1)$ operations.
-(define (vector-field-sequence-ell-infty-ell-1-ell-2-norm p) - (array-max (array-map (lambda (p^k) - (array-average (array-map Point-length-R^4 - (array-extract p^k - (array-domain zero-image))))) - p)))(with suitable definitions for
array-max
, array-average
, and Point-length-R^4
) computes the maximum over a number of two-dimensional arrays of the average length of four-vectors in those arrays restricted to the domain of zero-image
, without storing all of the elements of any of the intermediate arrays together.interval-curry
),
which don't seem terribly natural for arrays.
(make-array ...)
, array-map
, and array->specialized-array
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 functions, as to cover nearly all needs.This document refers to translations and permutations. - A translation is a vector of exact integers. A permutation of length $n$ + 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 occuring once, in any order.
Procedure: translation? object
(vector-ref lower-bounds i)
and interval-upper-bound
returns
(vector-ref upper-bounds i)
. It is an error to call interval-lower-bound
or interval-upper-bound
if interval
and i
do not satisfy these conditions.
+ Procedure: interval-lower-bounds->list interval
Procedure: interval-upper-bounds->list interval
If interval
is an interval built with
(make-interval lower-bounds upper-bounds)
+
+ then interval-lower-bounds->list
returns (vector->list lower-bounds)
and interval-upper-bounds->list
returns (vector->list upper-bounds)
. It is an error to call
+ interval-lower-bounds->list
or interval-upper-bounds->list
if interval
does not satisfy these conditions.;;;
Procedure: interval-lower-bounds->vector interval
Procedure: interval-upper-bounds->vector interval
If interval
is an interval built with
(make-interval lower-bounds upper-bounds)
then, assuming the existence of vector-map
, interval-volume
returns
(apply * (vector->list (vector-map - (interval-upper-bounds->vector interval) (interval-lower-bounds->vector interval)))
+(apply * (vector->list (vector-map - upper-bounds lower-bounds)))
It is an error to call
interval-volume
ifinterval
does not satisfy this condition.Procedure:
@@ -258,8 +262,8 @@interval= interval1 interval2
Procedures
(interval-upper-bound interval1 j)
${}\leq{}$(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 ...
If
interval
is an interval with dimension $d$ andindex-0
, ..., is a multi-index of length $d$, +Procedure:
+interval-contains-multi-index? interval index-0 index-1 ...
If
interval
is an interval with dimension $d$ andindex-0
,index-1
, ..., is a multi-index of length $d$, theninterval-contains-multi-index?
returns#t
if@@ -275,35 +279,25 @@(interval-lower-bound interval j)
$\leq$index-j
$<$(interval-upper-bound interval j)
Procedures
$[l_{d-\text{right-dimension}},u_{d-\text{right-dimension}})\times\cdots\times[l_{d-1},u_{d-1})$This function, the inverse of Cartesian products or cross products of intervals, is used to keep track of the domains of curried arrays.
-More precisely, if
-interval
is an interval andleft-dimension
is an exact integer that satisfies-0 < left-dimension < (interval-dimension interval)
-then
-interval-curry
returns two intervals:-(values (make-interval (vector (interval-lower-bound interval 0) +More precisely, if
+interval
is an interval andright-dimension
is an exact integer that satisfies0 < right-dimension < d
theninterval-curry
returns two intervals:+ (interval-upper-bound interval (- d 1)))))+(values (make-interval (vector (interval-lower-bound interval 0) ... - (interval-lower-bound interval (- d right-dimension 1))) - (vector (interval-upper-bound interval 0) + (interval-lower-bound interval (- d right-dimension 1))) + (vector (interval-upper-bound interval 0) ... - (interval-upper-bound interval (- d right-dimension 1)))) - (make-interval (vector (interval-lower-bound interval (- d right-dimension)) + (interval-upper-bound interval (- d right-dimension 1)))) + (make-interval (vector (interval-lower-bound interval (- d right-dimension)) ... - (interval-lower-bound interval (- (interval-dimension interval) 1))) - (vector (interval-upper-bound interval (- d right-dimension)) + (interval-lower-bound interval (- d 1))) + (vector (interval-upper-bound interval (- d right-dimension)) ... - (interval-upper-bound interval (- (interval-dimension interval) 1)))))
It is an error to call
interval-curry
if its arguments do not satisfy these conditions.Procedure:
interval-for-each f interval
This routine assumes that
-interval
is an interval andf
is a routine whose domain includes elements ofinterval
. It is an error to callinterval-for-each
ifinterval
andf
do not satisfy these conditions.-
interval-for-each
callsf
on each element ofinterval
in lexicographical order.Procedure:
-interval-reduce f operator identity interval
If
-interval
is an interval,f
is a routine whose domain includes elements ofinterval
, then -interval-reduce
returns-(... (operator (operator (operator identity (f multi-index1)) (f multi-index2)) (f multi-index3)) ...)
-where
-multi-index1
,multi-index2
, ... are the multi-indices ininterval
in lexicographical order.It is an error to call
+interval-reduce
ifinterval
andf
do not satisfy these conditions.
interval-for-each
callsf
with each multi-index ofinterval
as arguments, all in lexicographical order.Procedure:
interval-dilate interval lower-diffs upper-diffs
If
interval
is an interval with lower bounds l0, ..., ld-1 and @@ -312,8 +306,7 @@Procedures
upper bounds u0+U0, ..., ud-1+Ud-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 '#(0 0) '#(100 100)) '#(1 1) '#(1 1)) (make-interval '#(1 1) '#(101 101))) => #t (interval= (interval-dilate (make-interval '#(0 0) '#(100 100)) '#(-1 -1) '#(1 1)) @@ -321,8 +314,7 @@
Procedures
(interval= (interval-dilate (make-interval '#(0 0) '#(100 100)) '#(0 0) '#(-50 -50)) (make-interval '#(0 0) '#(50 50))) => #t (interval-dilate (make-interval '#(0 0) '#(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, the
@@ -347,6 +339,9 @@interval-intersect?
returns that intersection; otherwise it returns#f
Procedures
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-dilate interval translation)
will be the representation of $[0,16)\times [0,4)\times[0,8)\times[0,21)$.Procedure:
+interval-scale interval scales
If
+interval
is a $d$-dimensional interval $[0,u_1)\times\cdots\times[0,u_{d-1})$ with all lower bounds zero, andscales
is a length-$d$ vector of positive exact integers, which we'll denote by $\vec s$, theninterval-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
interval
andscales
do not satisfy this condition.Storage classes
Conceptually, a storage-class is a set of functions to manage the backing store of a specialized-array. The functions 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 homogenous) vector.
@@ -397,7 +392,7 @@Global Variables
2X-1 inclusive),fX-storage-class
is defined forX
= 32 and 64 (which have default value 0.0 and manipulate 32- and 64-bit floating-point numbers), andcX-storage-class
is defined forX
= 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). Each of these - could be defined simply asgeneric-storage-class
, but it is assumed that implementations with homogeneous arrays will give definitions + could be defined simply asgeneric-storage-class
, but it is assumed that implementations with homogeneous vectors will give definitions that either save space, avoid boxing, etc., for the specialized arrays.Arrays
Arrays are a data type distinct from other Scheme data types.
@@ -405,9 +400,9 @@Procedures
Procedure:
make-array interval getter [ setter ]
Assume first that the optional argument
setter
is not given.If
-interval
is an interval andgetter
is a function from -interval
to Scheme objects, thenarray
returns an array with domaininterval
+interval
to Scheme objects, thenmake-array
returns an array with domaininterval
and gettergetter
.It is an error to call
array
ifinterval
andgetter
+It is an error to call
make-array
ifinterval
andgetter
do not satisfy these conditions.If now
setter
is specified, assume that it is a procedure such that getter and setter satisfy: If(i1,...,in)
$\neq$(j1,...,jn)
@@ -424,38 +419,38 @@Procedures
and
-(getter i1,...,in) => v
Then
+array
builds a mutable array with domaininterval
, gettergetter
, and - settersetter
. It is an error to callarray
if its arguments do not satisfy these conditions.Then
make-array
builds a mutable array with domaininterval
, gettergetter
, and + settersetter
. It is an error to callmake-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))))++ (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 sparse-array - (let ((domain (make-interval '#(0 0) '#(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))))))))) -((array-getter sparse-array) 12345 6789) => 0. -((array-getter sparse-array) 0 0) => 0. -((array-setter sparse-array) 1.0 0 0) => undefined -((array-getter sparse-array) 12345 6789) => 0. -((array-getter sparse-array) 0 0) => 1.++ (define sparse-array + (let ((domain (make-interval '#(0 0) '#(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))))))))) + ((array-getter sparse-array) 12345 6789) => 0. + ((array-getter sparse-array) 0 0) => 0. + ((array-setter sparse-array) 1.0 0 0) => undefined + ((array-getter sparse-array) 12345 6789) => 0. + ((array-getter sparse-array) 0 0) => 1.
Procedure:
array? obj
Returns
#t
if and only ifobj
is an array.Procedure:
@@ -466,15 +461,15 @@array-domain array
Procedures
then
array-domain
returnsinterval
andarray-getter
returnsgetter
. It is an error to callarray-domain
orarray-getter
ifarray
is not an array.Example:
--(define a (make-array (make-interval '#(1 1) '#(11 11)) - (lambda (i j) - (if (= i j) - 1 - 0)))) -((array-getter a) 3 3) => 1 -((array-getter a) 2 3) => 0 -((array-getter a) 11 0) => is an error, which may be signaled++ (define a (make-array (make-interval '#(1 1) '#(11 11)) + (lambda (i j) + (if (= i j) + 1 + 0)))) + ((array-getter a) 3 3) => 1 + ((array-getter a) 2 3) => 0 + ((array-getter a) 11 0) => is an error, which may be signaled
Procedure:
array-dimension array
Shorthand for
(interval-dimension (array-domain array))
. It is an error to call this function ifarray
is not an arrayProcedure:
@@ -485,139 +480,213 @@mutable-array? obj
Procedures
then
-array-setter
returnssetter
. It is an error to callarray-setter
ifarray
is not a mutable array.Procedure:
-array-map f array . arrays
If
-array
,(car arrays)
, ... all have the same domain andf
is a function, thenarray-map
- returns a new 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 an error to call
+array-map
if its arguments do not satisfy these conditions.Procedure:
+specialized-array-default-safe? [ bool ]
With no argument, Returns
+#t
if newly-constructed specialized arrays check the arguments of setters and getters by default, and#f
otherwise.If
+bool
is#t
then the next call tospecialized-array-default-safe?
will return#t
; + ifbool
is#f
then the next call tospecialized-array-default-safe?
will return#f
; + otherwise it is an error.Procedure:
+specialized-array interval [ storage-class generic-storage-class ] [ safe? (specialized-array-default-safe?) ]
Constructs a specialized-array from its arguments.
++
interval
must be given as a nonempty interval. If given,storage-class
must be a storage class; if it is not given it defaults togeneric-storage-class
. If given,safe?
must be a boolean; if it is not given it defaults to the current value of(specialized-array-default-safe?)
.The body of the result is constructed as
+++ ((storage-class-maker storage-class) + (interval-volume interval) + (storage-class-default storage-class)) +
The indexer of the resulting array is constructed as the lexicographical mapping of
+interval
onto the interval[0,(interval-volume interval)
.If
+safe
is#t
, then the arguments of the getter and setter (including the value to be stored) of the resulting array are checked for correctness. If not, then(array-getter array)
is defined simply as++ (lambda multi-index + ((storage-class-getter storage-class) + (array-body array) + (apply (array-indexer array) multi-index))) +
and
+(array-setter array)
is defined as++ (lambda (val . multi-index) + ((storage-class-getter storage-class) + (array-body array) + (apply (array-indexer array) multi-index) + val)) +
It is an error if the arguments of
+specialized-array
do not satisfy these conditions.Examples. A simple array that can hold any type of element can be defined with
+(specialized-array (make-interval '#(0 0) '#(3 3)))
. If you find that you're using a lot of unsafe arrays of unsigned 16-bit integers, one could define++ (define (u16-array interval) + (specialized-array interval u16-storage-class #f)) +
and then simply call, e.g.,
+(u16-array (make-interval '#(0 0) '#(3 3)))
.Procedure:
+specialized-array? obj
Returns
+#t
ifobj
is a specialized-array, and#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 ofarray
.array-safe?
is true if and only if the arguments of(array-getter array)
and(array-setter array)
(including the value to be stored in the array) are checked for correctness.+
(array-indexer array)
is asssumed to be a one-to-one, but not necessarily onto, affine mapping from(array-domain array)
into(array-body array)
.It is an error to call any of these routines if
+array
is not a specialized-array.Procedure:
+specialized-array-share array new-domain new-domain->old-domain
Constructs a new specialized-array that shares the body of the specialized-array
+array
. + Returns an object that is behaviorally equivalent to a specialized array with the following fields:++ 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)) + (specialized-array-indexer array)))
+
new-domain->old-domain
must be an affine one-to-one mapping fromnew-domain
to +(array-domain array)
.Note: It is assumed that affine structure of the composition of
+new-domain->old-domain
and(specialized-array-indexer array
will be used to simplify:++ (lambda multi-index + (call-with-values + (lambda () + (apply new-domain->old-domain multi-index)) + (specialized-array-indexer array)))
It is an error if
+array
is not a specialized array, or ifnew-domain
is not an interval, or ifnew-domain->old-domain
is not a one-to-one affine mapping with the appropriate domain and range.Procedure:
+array->specialized-array array [ result-storage-class generic-storage-class ] [ safe? (specialized-array-default-safe?) ]
If
+array
is an array whose elements can be manipulated by the storage-class +result-storage-class
, then the specialized-array returned byarray->specialized-array
can be defined by:++ (let ((result (specialized-array (array-domain array) + result-storage-class + safe?)) + (interval-for-each (lambda multi-index + (apply (array-setter result) (apply (array-getter array) multi-index) multi-index)) + (array-domain array)) + result)
It is guaranteed that
+(array-getter array)
is called precisely once for each multi-index in(array-domain array)
in lexicographical order.It is an error if
result-storage-class
does not safisfy these conditions, or ifsafe?
is not a boolean.Procedure:
array-curry array inner-dimension
If
array
is an array whose domain is an interval $[l_0,u_0)\times\cdots\times[l_{d-1},u_{d-1})$, andinner-dimension
is an exact integer strictly between $0$ and $d$, thenarray-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
andB
are defined by-(define interval (make-interval '#(0 0 0 0) - '#(10 10 10 10))) -(define A (make-array interval list)) -(define B (array-curry A 3)) -++ (define interval (make-interval '#(0 0 0 0) + '#(10 10 10 10))) + (define A (make-array interval list)) + (define B (array-curry A 1)) +
so
--((array-getter A) i j k l) => (list i j k l)++ ((array-getter A) i j k l) => (list i j k l)
then
-B
is an immutable array with domain(make-interval '#(0 0 0) '#(10 10 10))
, each of whose elements is itself an (immutable) array and-(equal? ((array-getter A) i j k l) - (array-getter ((array-getter B) i j k) l)) => #t -++ (equal? ((array-getter A) i j k l) + ((array-getter ((array-getter B) i j k)) l)) => #t +
for all multi-indices
-i j k l
ininterval
.The type of the subarrays is the same as the type of the input array.
+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-curry (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))))))))++ (call-with-values + (lambda () (interval-curry (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-curry (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))))))))++ (call-with-values + (lambda () (interval-curry (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-curry (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))))))))++ (call-with-values + (lambda () (interval-curry (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.Example:
--(define a (make-array (make-interval '#(0 0) '#(10 10)) - list)) -((array-getter a) 3 4) => (3 4) -(define curried-a (array-curry a 1)) -((array-getter ((array-getter curried-a) 3)) 4) => (3 4)++ (define a (make-array (make-interval '#(0 0) '#(10 10)) + list)) + ((array-getter a) 3 4) => (3 4) + (define curried-a (array-curry a 1)) + ((array-getter ((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
-array
is an array andnew-domain
is an interval that is a sub-interval of(array-domain array)
. Ifarray
is a specialized array, then returns- (specialized-array-share array - new-domain - values) -++ (specialized-array-share array + new-domain + values) +
Otherwise, if
-array
is a mutable array, thenarray-extract
returns- (make-array new-domain - (array-getter array) - (array-setter array)) +++ (make-array new-domain + (array-getter array) + (array-setter array)) -
Finally, if
-array
is an immutable array, thenarray-extract
returns- (make-array new-domain - (array-getter array)) -++ (make-array new-domain + (array-getter array)) +
It is an error if the arguments of
array-extract
do not satisfy these conditions.Procedure:
array-translate array translation
Assumes that
array
is a valid array,translation
is a valid translation, and that the dimensions of the array and the translation are the same. The resulting array will have domain(interval-translate (array-domain Array) translation)
.If
-array
is a specialized array, returns a new specialized 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
+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
array
.If
-array
is not a specialized array but is a mutable array, returns a new mutable 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))))) -++ (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
-array
is not a mutable array, returns a new array- (make-array (interval-translate (array-domain Array) translation) - (lambda multi-index - (apply (array-getter array) (map - multi-index (vector->list translation))))) -++ (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
array
is a valid array,permutation
is a valid permutation, and that the dimensions of the array and the permutation are the same. The resulting array will have domain(interval-permute (array-domain Array) permutation)
.We begin with an example. Assume that the domain of
array
is represented by the interval $[0,4)\times[0,8)\times[0,21)\times [0,16)$, as in the example forinterval-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 ofarray-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 defineold-getter
as(array-getter array)
, the definition of the new array must be in fact- (make-array (interval-permute (array-domain array) '#(3 0 1 2)) - (lambda (l i j k) - (old-getter i j k l))) -++ (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
old-getter
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, thelambda
, 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 old-getter (
$\pi^{-1}$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
array
is a specialized-array and we denote the permutation by $\pi$, thenarray-permute
returns the new specialized array
- (specialized-array-share array + (specialized-array-share array (interval-permute (array-domain array)
$\pi$)(lambda multi-index (apply values (
$\pi^{-1}$multi-index))))
The result array shares
@@ -633,110 +702,148 @@(array-body array)
with the argument.Procedures
(make-array (interval-permute (array-domain array) $\pi$)(lambda multi-index (apply (array-getter array) (
$\pi^{-1}$multi-index))))
It is an error to call
+array-permute
if its arguments do not satisfy these conditions.Procedure:
+array-reverse array flip?
We assume that
+array
is an array andflip?
is a vector of booleans whose length is the same as the dimension ofarray
.+
array-reverse
returns a new array that is specialized, mutable, or immutable according to whetherarray
is specialized, mutable, or immutable, respectively. Informally, if(vector-ref flip? k)
is true, then the ordering of multi-indices in the k'th coordinate direction is reversed, and is left undisturbed otherwise.More formally, if
+array
is specialized, thenarray-reverse
returns++ (specialized-array-share +
array
+ (array-domainarray
) + (lambda multi-index + (apply values + (vector->list + (vector-map + (lambda (i_k flip?_k l_k u_k) + (if flip? + (- (+ l_k u_k -1) i_k) + i_k)) + (list->vector multi-index) + flip? + (interval-lower-bounds->vector (array-domainarray
)) + (interval-upper-bounds->vector (array-domainarray
)))))))Otherwise, if
+array
is mutable, thenarray-reverse
returns++ (make-array (array-domain
array
) + (lambda multi-index + (apply (array-getterarray
) + (vector->list + (vector-map + (lambda (i_k flip?_k l_k u_k) + (if flip? + (- (+ l_k u_k -1) i_k) + i_k)) + (list->vector multi-index) + flip? + (interval-lower-bounds->vector (array-domainarray
)) + (interval-upper-bounds->vector (array-domainarray
)))))) + (lambda (v . multi-index) + (apply (array-setterarray
) + v + (vector->list + (vector-map + (lambda (i_k flip?_k l_k u_k) + (if flip? + (- (+ l_k u_k -1) i_k) + i_k)) + (list->vector multi-index) + flip? + (interval-lower-bounds->vector (array-domainarray
)) + (interval-upper-bounds->vector (array-domainarray
)))))))Finally, if
+array
is immutable, thenarray-reverse
returns++ (make-array (array-domain
array
) + (lambda multi-index + (apply (array-getterarray
) + (vector->list + (vector-map + (lambda (i_k flip?_k l_k u_k) + (if flip? + (- (+ l_k u_k -1) i_k) + i_k)) + (list->vector multi-index) + flip? + (interval-lower-bounds->vector (array-domainarray
)) + (interval-upper-bounds->vector (array-domainarray
)))))))It is an error if
+array
andflip?
don't satisfy these requirements.Procedure:
+array-sample array scales
We assume that
+array
is an array all of whose lower bounds are zero, andscales
is a vector of positive exact integers whose length is the same as the dimension ofarray
.+
array-sample
returns a new array that is specialized, mutable, or immutable according to whetherarray
is specialized, mutable, or immutable, respectively. Informally, if we construct a new matrix $S$ with the entries ofscales
on the main diagonal, then the $\vec i$th element of(array-sample array scales)
is the $S\vec i$th element ofarray
.More formally, if
+array
is specialized, thenarray-scale
returns++ (specialized-array-share +
array
+ (interval-scale (array-domainarray
)scales
) + (lambda multi-index + (apply values (map * multi-index (vector->listscales
)))))Otherwise, if
+array
is mutable, thenarray-scale
returns++ (make-array (interval-scale (array-domain
array
)scales
) + (lambda multi-index + (apply (array-getterarray
) + (map * multi-index (vector->listscales
)))) + (lambda (v . multi-index) + (apply (array-setterarray
) + v + (map * multi-index (vector->listscales
)))))Finally, if
+array
is immutable, thenarray-scale
returns++ (make-array (interval-scale (array-domain
array
)scales
) + (lambda multi-index + (apply (array-getterarray
) + (map * multi-index (vector->listscales
)))))It is an error if
+array
andscales
don't satisfy these requirements.Procedure:
+array-map f array . arrays
If
+array
,(car arrays)
, ... all have the same domain andf
is a function, thenarray-map
+ returns a new 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
+f
is appropriately defined to be evaluated in this context.It is an error to call
array-map
if its arguments do not satisfy these conditions.Procedure:
array-for-each f array . arrays
If
-array
,(car arrays)
, ... all have the same domain andf
is an appropriate function, thenarray-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))++ (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
andarray-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)))))++ (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-reduce operator identity array
If
-array
is an array thenarray-reduce
returns(interval-reduce (array-getter array) operator identity (array-domain array))
.It is an error if
+array
is not a valid array, or ifoperator
is not a procedure.Procedure:
+array-fold kons knil array
If we use the defining relations for fold over lists from SRFI-1:
+++ (fold kons knil lis) = (fold kons (kons (car lis) knil) (cdr lis)) + (fold kons knil '()) = knil +
then
+(array-fold kons knil array)
returns++ (fold kons knil (array->list array))
It is an error if
+array
is not an array, or ifkons
is not a procedure.Procedure:
+array-fold-right kons knil array
If we use the defining relations for fold-right over lists from SRFI-1:
+++ (fold-right kons knil lis) = (kons (car lis) (fold-right kons knil (cdr lis))) + (fold-right kons knil '()) = knil +
then
+(array-fold-right kons knil array)
returns++ (fold-right kons knil (array->list array))
It is an error if
array
is not an array, or ifkons
is not a procedure.Procedure:
-array-every? proc array
Returns
-#f
ifproc
is not true of every element of array, and another, nonfalse, value otherwise.It is an error if
-array
is not an array or ifproc
is not a procedure.Procedure:
-specialized-array-default-safe? [ bool ]
With no argument, Returns
-#t
if newly-constructed specialized arrays check the arguments of setters and getters by default, and#f
otherwise.If
-bool
is#t
then the next call tospecialized-array-default-safe?
will return#t
; - ifbool
is#f
then the next call tospecialized-array-default-safe?
will return#f
; - otherwise it is an error.Procedure:
-specialized-array interval [ storage-class generic-storage-class ] [ safe? (specialized-array-default-safe?) ]
Constructs a specialized-array from its arguments.
--
interval
must be given as a nonempty interval. If given,storage-class
must be a storage class; if it is not given it defaults togeneric-storage-class
. If given,safe?
must be a boolean; if it is not given it defaults to the current value of(specialized-array-default-safe?)
.The body of the result is constructed as
-- ((storage-class-maker storage-class) - (interval-volume interval) - (storage-class-default storage-class)) --The indexer of the resulting array is constructed as the lexicographical mapping of
-interval
onto the interval[0,(interval-volume interval)
.If
-safe
is#t
, then the arguments of the getter and setter (including the value to be stored) of the resulting array are checked for correctness. If not, then(array-getter array)
is defined simply as- (lambda multi-index - ((storage-class-getter storage-class) - (array-body array) - (apply (array-indexer array) multi-index))) --and
-(array-setter array)
is defined as- (lambda (val . multi-index) - ((storage-class-getter storage-class) - (array-body array) - (apply (array-indexer array) multi-index) - val)) --It is an error if the arguments of
-specialized-array
do not satisfy these conditions.Examples. A simple array that can hold any type of element can be defined with
-(specialized-array (make-interval '#(0 0) '#(3 3)))
. If you find that you're using a lot of unsafe arrays of unsigned 16-bit integers, one could define- (define (u16-array interval) - (specialized-array interval u16-storage-class #f)) --and then simply call, e.g.,
-(u16-array (make-interval '#(0 0) '#(3 3)))
.Procedure:
-specialized-array? obj
Returns
-#t
ifobj
is a specialized-array, and#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 ofarray
.array-safe?
is true if and only if the arguments of(array-getter array)
and(array-setter array)
(including the value to be stored in the array) are checked for correctness.-
(array-indexer array)
is asssumed to be a one-to-one, but not necessarily onto, affine mapping from(array-domain array)
into(array-body array)
.It is an error to call any of these routines if
-array
is not a specialized-array.Procedure:
-specialized-array-share array new-domain new-domain->old-domain
Constructs a new specialized-array that shares the body of the specialized-array
-array
. - Returns an object that is behaviorally equivalent to a specialized array with the following fields:- 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)) - (specialized-array-indexer array)))--
new-domain->old-domain
must be an affine one-to-one mapping fromnew-domain
to -(array-domain array)
.Note: It is assumed that affine structure of the composition of
-new-domain->old-domain
and(specialized-array-indexer array
will be used to simplify:-(lambda multi-index - (call-with-values - (lambda () - (apply new-domain->old-domain multi-index)) - (specialized-array-indexer array)))-It is an error if
-array
is not a specialized array, or ifnew-domain
is not an interval, or ifnew-domain->old-domain
is not a one-to-one affine mapping with the appropriate domain and range.Procedure:
-array->specialized-array array [ result-storage-class generic-storage-class ] [ safe? (specialized-array-default-safe?) ]
If
-array
is an array whose elements can be manipulated by the storage-class -result-storage-class
, then the specialized-array returned byarray->specialized-array
can be defined by:-(let ((result (specialized-array (array-domain array) - result-storage-class - safe?))) - (interval-for-each (lambda multi-index - (apply (array-setter result) (apply (array-getter array) multi-index) multi-index)) - (array-domain array)) - result)-It is guaranteed that
-(array-getter array)
is called precisely once for each multi-index in(array-domain array)
in lexicographical order.It is an error if
+result-storage-class
does not safisfy these conditions, or ifsafe?
is not a boolean.If
+array
is an array andproc
is a procedure that can be applied to elements ofarray
, thenarray-every?
returns#t
ifproc
returns a non-false value for all elements ofarray
, and#f
otherwise.It is an error if
array
andproc
don't satisfy these conditions.Procedure:
array->list array
Stores the elements of
array
into a newly-allocated list in lexicographical order. It is an error ifarray
is not an array.Procedure:
-list->specialized-array l interval [ result-storage-class generic-storage-class ] [ safe? (specialized-array-default-safe?) ]
Returns a specialized-array with domain
+interval
whose elements are the elements of the listl
stored in lexicographical order. It is an error ifl
is not a list, ifinterval
is not an interval, if the length ofl
is not the same as the volume ofinterval
, ifresult-storage-class
(when given) is not a storage class, ifsafe?
(when given) is not a boolean, or if any element ofl
cannot be stored in the body ofresult-storage-class
.Returns a specialized-array with domain
interval
whose elements are the elements of the listl
stored in lexicographical order. It is an error ifl
is not a list, ifinterval
is not an interval, if the length ofl
is not the same as the volume ofinterval
, ifresult-storage-class
(when given) is not a storage class, ifsafe?
(when given) is not a boolean, or if any element ofl
cannot be stored in the body ofresult-storage-class
, and this last error shall be detected and raised ifsafe
is#t
.Implementation
We provide an implementation in Gambit-C; the nonstandard techniques used in the implementation are: DSSSL-style optional and keyword arguments; a @@ -775,122 +882,122 @@
Other examples
Reading an image file in PGM format. On a system with eight-bit chars, one can write a function to read greyscale images in the PGM format of the netpbm package as follows. The lexicographical order in array->specialized-array 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))) - (read-char port) ; to skip the newline or next whitespace - (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. ++ (define (read-pgm file) + + (define (read-pgm-object port) + (skip-white-space port) + (let ((o (read port))) + (read-char port) ; to skip the newline or next whitespace + (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->specialized-array + (make-array + (make-interval '#(0 0) + (vector rows columns)) + (cond ((or (eq? header 'p5) ;; pgm binary + (eq? header 'P5)) + (if (< greys 256) + (lambda (i j) ;; one byte/pixel + (char->integer (read-char port))) + (lambda (i j) ;; two bytes/pixel, little-endian + (let* ((first-byte (char->integer (read-char port))) + (second-byte (char->integer (read-char port)))) + (+ (* second-byte 256) first-byte))))) + ((or (eq? header 'p2) ;; pgm ascii + (eq? header 'P2)) + (lambda (i j) + (read port))) + (else + (error "read-pgm: not a pgm file"))))))))))+ (define make-pgm cons) + (define pgm-greys car) + (define pgm-pixels cdr) - (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->specialized-array - (array - (make-interval '#(0 0) - (vector rows columns)) - (cond ((or (eq? header 'p5) ;; pgm binary - (eq? header 'P5)) - (if (< greys 256) - (lambda (i j) ;; one byte/pixel - (char->integer (read-char port))) - (lambda (i j) ;; two bytes/pixel, little-endian - (let* ((first-byte (char->integer (read-char port))) - (second-byte (char->integer (read-char port)))) - (+ (* second-byte 256) first-byte))))) - ((or (eq? header 'p2) ;; pgm ascii - (eq? header 'P2)) - (lambda (i j) - (read port))) - (else - (error "read-pgm: not a pgm file"))))))))))
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
body
, then one could get 1024 axial views, each $512\times512$, of this 3D body by(array-curry body 2)
; or 512 median views, each $1024\times512$, by(array-curry (array-permute body '#(1 0 2)) 2)
; or finally 512 frontal views, each again $1024\times512$ pixels, by(array-curry (array-permute body '#(2 0 1)) 2)
; see Anatomical plane.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->specialized-array - (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))))))) -++ (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->specialized-array + (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->specialized-array (make-array (make-interval '#(0 0) '#(8 8)) (lambda (i j) (exact->inexact (+ (* i i) (* j j))))))) @@ -908,7 +1015,7 @@
Other examples
(expose (all-second-differences image '#(1 1))) (display "\nSecond-difference images in the direction $k\times (1,-1)$, $k=1,2,...$, wherever they're defined:\n") (expose (all-second-differences image '#(1 -1)))) -On Gambit 4.8.5, this yields (after some hand editing):
Second-difference images in the direction $k\times (1,0)$, $k=1,2,...$, wherever they're defined: @@ -939,7 +1046,7 @@Other examples
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 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 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 compute the multidimensional transform as follows:
-++(define (make-separable-transform 1D-transform) (lambda (array) ;; Works on arrays of any dimension. @@ -970,72 +1077,65 @@
Other examples
;; return the permutation to the identity (vector-set! permutation d d) (vector-set! permutation (fx- n 1) (fx- n 1)))))) -We can test this by turning a one-dimensional Haar wavelet transform into a multi-dimensional Haar transform:
-- (define (1D-Haar-loop a) - (let ((getter (array-getter a)) - (setter (array-setter a)) - (n (interval-upper-bound (array-domain a) 0))) - (do ((i 0 (fx+ i 2))) - ((fx= i n)) - (let* ((a_i (getter i)) - (a_i+1 (getter (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)))) - (setter scaled-sum i) - (setter scaled-difference (fx+ i 1)))))) - - (define (1D-Haar-transform a) - ;; works only on specialized arrays with domains $[0, 2^k)$ for some $k$ - (let ((n (interval-upper-bound (array-domain a) 0))) - (if (fx< 1 n) - (begin - ;; calculate the scaled sums and differences - (1D-Haar-loop a) - ;; Apply the transform to the sub-array of scaled sums - (1D-Haar-transform (specialized-array-share a - (make-interval '#(0) (vector (quotient n 2))) - (lambda (i) - (fx* 2 i)))))))) - - (define (1D-Haar-inverse-transform a) - ;; works only on specialized arrays with domains $[0, 2^k)$ for some $k$ - (let* ((n (interval-upper-bound (array-domain a) 0))) - (if (fx< 1 n) - (begin - ;; Apply the inverse transform to get the array of scaled sums - (1D-Haar-inverse-transform (specialized-array-share a - (make-interval '#(0) (vector (quotient n 2))) - (lambda (i) - (fx* 2 i)))) - ;; reconstruct the array values from the scaled sums and differences - (1D-Haar-loop a))))) - - (define Haar-transform - (make-separable-transform 1D-Haar-transform)) - - (define Haar-inverse-transform - (make-separable-transform 1D-Haar-inverse-transform)) - -++ (define (1D-Haar-loop a) + (let ((getter (array-getter a)) + (setter (array-setter a)) + (n (interval-upper-bound (array-domain a) 0))) + (do ((i 0 (fx+ i 2))) + ((fx= i n)) + (let* ((a_i (getter i)) + (a_i+1 (getter (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)))) + (setter scaled-sum i) + (setter scaled-difference (fx+ i 1)))))) + + (define (1D-Haar-transform a) + ;; works only on mutable arrays with domains $[0, 2^k)$ for some $k$ + (let ((n (interval-upper-bound (array-domain a) 0))) + (if (fx< 1 n) + (begin + ;; calculate the scaled sums and differences + (1D-Haar-loop a) + ;; Apply the transform to the sub-array of scaled sums + (1D-Haar-transform (array-sample a '#(2))))))) + + (define (1D-Haar-inverse-transform a) + ;; works only on mutable arrays with domains $[0, 2^k)$ for some $k$ + (let* ((n (interval-upper-bound (array-domain a) 0))) + (if (fx< 1 n) + (begin + ;; Apply the inverse transform to get the array of scaled sums + (1D-Haar-inverse-transform (array-sample a '#(2))) + ;; reconstruct the array values from the scaled sums and differences + (1D-Haar-loop a))))) + + (define Haar-transform + (make-separable-transform 1D-Haar-transform)) + + (define Haar-inverse-transform + (make-separable-transform 1D-Haar-inverse-transform)) +
We then define an image that is a multiple of a single, two-dimensional Haar wavelet, compute its transform (which should be nonzero for only a single Haar coefficient), and then the inverse transform:
-- (let ((image (array->specialized-array (make-array (make-interval '#(0 0) '#(4 4)) - (lambda (i j) - (if (fx< i 2) 1. -1.)))))) - (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 "\nArray reconstructed from Haar wavelet coefficients: \n") - (pretty-print (list (array-domain image) - (array->list image)))) -++ (let ((image (array->specialized-array (make-array (make-interval '#(0 0) '#(4 4)) + (lambda (i j) + (if (fx< i 2) 1. -1.)))))) + (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 "\nArray reconstructed from Haar wavelet coefficients: \n") + (pretty-print (list (array-domain image) + (array->list image)))) +
This yields:
Initial image: diff --git a/srfi-122.scm b/srfi-122.scm index 8e06e9f..94b52f7 100644 --- a/srfi-122.scm +++ b/srfi-122.scm @@ -66,6 +66,7 @@ MathJax.Hub.Config({ ("Draft #10 published: 2016/8/30") ( "Draft #11 published: 2016/9/7") ( "Draft #12 published: 2016/9/16") + ( "Draft #13 published: 2016/11/18") ) ( "Abstract") @@ -120,25 +121,37 @@ MathJax.Hub.Config({ "can be expressed in this way. We give several examples below: ") (
(
- ( "Restricting the domain of an array: ") - " If the domain of $B$ is a subset of the domain of $A$ ( $D_B\\subseteq D_A$), then $T_{BA}(\\vec i)=\\vec i$ is a one-to-one affine mapping. We define " + " 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. ") (
- ( "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\\}$ into $D_A$. " + "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$ "( href: "https://en.wikipedia.org/wiki/Permutation" '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 again a one-to-one affine map from $D_B=\\{\\pi^{-1}(\\vec i)\\mid \\vec i\\in D_A\\}$ to $D_A$. We provide "(
'array-permute)" for this operation. " + "$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. " "The only nonidentity permutation of a two-dimensional spreadsheet turns rows into columns and vice versa.") - (
- ( "\"Currying\" an array: ") + (
- ( "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, once again, 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 " + "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.")) - (
"We make several remarks. First, all these operations could have been computed by specifying the particular mapping $T_{BA}$ explicitly, so that these routines, " - "where one specifies the translation $\\vec d$ or the permutation $\\pi$ or the outer dimension $r$ of $D_A$ (in the currying example) are simply " + "Currying a two-dimensional array would be like organizing a spreadsheet into a one-dimensional array of rows of the spreadsheet.") + (
- ( "Traversing the order of 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 routines are simply " "\"convenience\" procedures. Second, because the composition of any number of affine mappings are 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 " @@ -166,8 +179,8 @@ they may have hash tables or databases behind an implementation, one may read th (
"Certain ways of sharing generalized arrays, however, are relatively easy to code and not that expensive. If we denote "(
"(array-getter A)")" by "(
'A-getter)", then if B is the result of "(
'array-extract)" applied to A, then " (
"(array-getter B)")" is simply "(
'A-getter)". Similarly, if A is a two-dimensional array, and B is derived from A by applying the permutation $\\pi((i,j))=(j,i)$, then "(
"(array-getter B)")" is " (
"(lambda (i j) (A-getter j i))")". Translation and currying also lead to transformed arrays whose getters are relatively efficiently derived from "(
'A-getter)", at least for arrays of small dimension.") - (
"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 functions "(
'array-extract)", "(
'array-translate)", "(
'array-permute)", and " - (
'array-curry)", and we provide relatively efficient implementations of these functions for arrays of dimension no greater than four.") + (
"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 functions "(
'array-extract)", "(
'array-translate)", "(
'array-permute)", " + (
'array-curry)", "(
'array-reverse)", and "(
'array-sample)", and we provide relatively efficient implementations of these functions for arrays of dimension no greater than four.") (
"Array-map does not produce a specialized array") (
"Daniel Friedman and David Wise wrote a famous paper "( href: "http://www.cs.indiana.edu/cgi-bin/techreports/TRNNN.cgi?trnum=TR44" "CONS should not Evaluate its Arguments")". " "In the spirit of that paper, our procedure "(
'array-map)" does not immediately produce a specialized array, but a simple immutable array, whose elements are recomputed from the arguments of "(
'array-map) @@ -178,139 +191,125 @@ they may have hash tables or databases behind an implementation, one may read th - (
"Examples of application areas") - (
- - (
- "Many applications have multi-dimensional data that behave differently in different coordinate directions. For example, one might have a time series of maps, which can be stored in a single three-dimensional array. Or one might have one-dimensional spectral data assigned to each pixel on a map. The data cube as a whole is considered three-dimensional "( 'hyperspectral)" data, but for processing the spectra separately one would apply a function to the spectrum at each pixel. This corresponds to "( 'currying)" arguments in programming languages, so we include such procedures here.") - (
- "By default, an array computes each array element each time it is needed. So the following code" - (
- " -(define (vector-field-sequence-ell-infty-ell-1-ell-2-norm p) - (array-max (array-map (lambda (p^k) - (array-average (array-map Point-length-R^4 - (array-extract p^k - (array-domain zero-image))))) - p)))" -) - "(with suitable definitions for "('array-max)", "(
'array-average)", and "(
'Point-length-R^4)") computes the maximum over a number of two-dimensional arrays of the average length of four-vectors in those arrays restricted to the domain of "(
"zero-image")", without storing all of the elements of any of the intermediate arrays together.") - ) - -(
"Issues and Notes") -(
- (
- ( "Relationship to "( href: "http://docs.racket-lang.org/math/array_nonstrict.html#%28tech._nonstrict%29" "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 "( href: "http://research.microsoft.com/en-us/um/people/simonpj/papers/ndp/RArrays.pdf" "array proposal for Haskell")". Our \"specialized\" arrays are related to Racket's \"strict\" arrays.") - (
- ( "Indexers. ")"The argument new-domain->old-domain to "(
'specialized-array-share)" is, conceptually, a multi-valued array.") - (
- ( "Source of function names. ")"The function "(
'array-curry)" gets its name from the " #\newline - ( href: "http://en.wikipedia.org/wiki/Currying" "curry operator") - " in programming---we are currying the getter of the array and keeping careful track of the domains. " #\newline - "interval-curry is simply given a parallel name (although it can be thought of as currying the " #\newline - "characteristic function of the interval, encapsulated here as "(
'interval-contains-multi-index?)").") - (
- ( "Choice of functions on intervals. ")"The choice of functions for both arrays and intervals was motivated almost solely by what I needed for arrays. There are " #\newline - "natural operations on intervals, like " - (
("(interval-cross-product interval1 interval2 ...)")) - "(the inverse of "(
'interval-curry)"), + (
"Issues and Notes") + (
+ (
- ( "Relationship to "( href: "http://docs.racket-lang.org/math/array_nonstrict.html#%28tech._nonstrict%29" "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 "( href: "http://research.microsoft.com/en-us/um/people/simonpj/papers/ndp/RArrays.pdf" "array proposal for Haskell")". Our \"specialized\" arrays are related to Racket's \"strict\" arrays.") + (
- ( "Indexers. ")"The argument new-domain->old-domain to "(
'specialized-array-share)" is, conceptually, a multi-valued array.") + (
- ( "Source of function names. ")"The function "(
'array-curry)" gets its name from the " #\newline + ( href: "http://en.wikipedia.org/wiki/Currying" "curry operator") + " in programming---we are currying the getter of the array and keeping careful track of the domains. " #\newline + "interval-curry is simply given a parallel name (although it can be thought of as currying the " #\newline + "characteristic function of the interval, encapsulated here as "(
'interval-contains-multi-index?)").") + (
- ( "Choice of functions on intervals. ")"The choice of functions for both arrays and intervals was motivated almost solely by what I needed for arrays. There are " #\newline + "natural operations on intervals, like " + (
("(interval-cross-product interval1 interval2 ...)")) + "(the inverse of "(
'interval-curry)"), which don't seem terribly natural 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 arrays is 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->specialized-array)" 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 functions, as to cover nearly all needs.") - - ) -(
"Specification") -(let ((END ",\n")) - (
"Names defined in this SRFI:") - (
- (
- "Miscellaneous Functions") - (
- ( href: "#translation?" "translation?") END - ( href: "#permutation?" "permutation?") - ".") - (
- "Intervals") - (
- ( href: "#make-interval" "make-interval")END - ( href: "#interval?" "interval?")END - ( href: "#interval-dimension" "interval-dimension")END - ( href: "#interval-lower-bound" "interval-lower-bound")END - ( href: "#interval-upper-bound" "interval-upper-bound")END - ;;( href: "#interval-lower-bounds->list" "interval-lower-bounds->list")END - ;;( href: "#interval-upper-bounds->list" "interval-upper-bounds->list")END - ( href: "#interval-lower-bounds->vector" "interval-lower-bounds->vector")END - ( href: "#interval-upper-bounds->vector" "interval-upper-bounds->vector")END - ( href: "#interval=" "interval=")END - ( href: "#interval-volume" "interval-volume")END - ( href: "#interval-subset?" "interval-subset?")END - ( href: "#interval-contains-multi-index?" "interval-contains-multi-index?")END - ( href: "#interval-curry" "interval-curry")END - ( href: "#interval-for-each" "interval-for-each")END - ( href: "#interval-reduce" "interval-reduce")END - ( href: "#interval-dilate" "interval-dilate")END - ( href: "#interval-intersect?" "interval-intersect?")END - ( href: "#interval-translate" "interval-translate")END - ( href: "#interval-permute" "interval-permute") - ".") - (
- "Storage Classes") - (
- ( href: "#make-storage-class" "make-storage-class") END - ( href: "#storage-class?" "storage-class?") END - ( href: "#storage-class-getter" "storage-class-getter") END - ( href: "#storage-class-setter" "storage-class-setter") END - ( href: "#storage-class-checker" "storage-class-checker") END - ( href: "#storage-class-maker" "storage-class-maker") END - ( href: "#storage-class-length" "storage-class-length") END - ( href: "#storage-class-default" "storage-class-default") END - ( href: "#generic-storage-class" "generic-storage-class") END - ( href: "#s8-storage-class" "s8-storage-class") END - ( href: "#s16-storage-class" "s16-storage-class") END - ( href: "#s32-storage-class" "s32-storage-class") END - ( href: "#s64-storage-class" "s64-storage-class") END - ( href: "#u1-storage-class" "u1-storage-class") END - ( href: "#u8-storage-class" "u8-storage-class") END - ( href: "#u16-storage-class" "u16-storage-class") END - ( href: "#u32-storage-class" "u32-storage-class") END - ( href: "#u64-storage-class" "u64-storage-class") END - ( href: "#f32-storage-class" "f32-storage-class") END - ( href: "#f64-storage-class" "f64-storage-class") END - ( href: "#c64-storage-class" "c64-storage-class") END - ( href: "#c128-storage-class" "c128-storage-class") - ".") - (
- "Arrays") - (
- ( href: "#make-array" "make-array")END - ( href: "#array?" "array?")END - ( href: "#array-domain" "array-domain")END - ( href: "#array-dimension" "array-dimension")END - ( href: "#array-getter" "array-getter")END - ( href: "#mutable-array?" "mutable-array?")END - ( href: "#array-setter" "array-setter")END - ( href: "#specialized-array" "specialized-array")END - ( href: "#specialized-array-share" "specialized-array-share")END - ( href: "#specialized-array?" "specialized-array?")END - ( href: "#array-safe?" "array-safe?") END - ( href: "#array-body" "array-body")END - ( href: "#array-indexer" "array-indexer")END - ( href: "#array-storage-class" "array-storage-class")END - ( href: "#array-map" "array-map")END - ( href: "#array-curry" "array-curry")END - ( href: "#array-for-each" "array-for-each")END - ( href: "#array-reduce" "array-reduce")END - ( href: "#array-every?" "array-every?")END - ( href: "#array-extract" "array-extract") END - ( href: "#specialized-array-default-safe?" "specialized-array-default-safe?") END - ( href: "#array->specialized-array" "array->specialized-array")END - ( href: "#array-translate" "array-translate")END - ( href: "#array-permute" "array-permute")END - ( href: "#array->list" "array->list") END - ( href: "#list->specialized-array" "list->specialized-array") END - "." - ))) -(
"Miscellaneous Functions") -(
"This document refers to "( 'translations)" and "( 'permutations)". - A translation is a vector of exact integers. A permutation of length $n$ + (
- ( "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 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->specialized-array)" 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 functions, as to cover nearly all needs.") + + ) + (
"Specification") + (let ((END ",\n")) + (
"Names defined in this SRFI:") + (
+ (
- "Miscellaneous Functions") + (
- ( href: "#translation?" "translation?") END + ( href: "#permutation?" "permutation?") + ".") + (
- "Intervals") + (
- ( href: "#make-interval" "make-interval")END + ( href: "#interval?" "interval?")END + ( href: "#interval-dimension" "interval-dimension")END + ( href: "#interval-lower-bound" "interval-lower-bound")END + ( href: "#interval-upper-bound" "interval-upper-bound")END + ( href: "#interval-lower-bounds->list" "interval-lower-bounds->list")END + ( href: "#interval-upper-bounds->list" "interval-upper-bounds->list")END + ( href: "#interval-lower-bounds->vector" "interval-lower-bounds->vector")END + ( href: "#interval-upper-bounds->vector" "interval-upper-bounds->vector")END + ( href: "#interval=" "interval=")END + ( href: "#interval-volume" "interval-volume")END + ( href: "#interval-subset?" "interval-subset?")END + ( href: "#interval-contains-multi-index?" "interval-contains-multi-index?")END + ( href: "#interval-curry" "interval-curry")END + ( href: "#interval-for-each" "interval-for-each")END + ( href: "#interval-dilate" "interval-dilate")END + ( href: "#interval-intersect?" "interval-intersect?")END + ( href: "#interval-translate" "interval-translate")END + ( href: "#interval-permute" "interval-permute") END + ( href: "#interval-scale" "interval-scale") + ".") + (
- "Storage Classes") + (
- ( href: "#make-storage-class" "make-storage-class") END + ( href: "#storage-class?" "storage-class?") END + ( href: "#storage-class-getter" "storage-class-getter") END + ( href: "#storage-class-setter" "storage-class-setter") END + ( href: "#storage-class-checker" "storage-class-checker") END + ( href: "#storage-class-maker" "storage-class-maker") END + ( href: "#storage-class-length" "storage-class-length") END + ( href: "#storage-class-default" "storage-class-default") END + ( href: "#generic-storage-class" "generic-storage-class") END + ( href: "#s8-storage-class" "s8-storage-class") END + ( href: "#s16-storage-class" "s16-storage-class") END + ( href: "#s32-storage-class" "s32-storage-class") END + ( href: "#s64-storage-class" "s64-storage-class") END + ( href: "#u1-storage-class" "u1-storage-class") END + ( href: "#u8-storage-class" "u8-storage-class") END + ( href: "#u16-storage-class" "u16-storage-class") END + ( href: "#u32-storage-class" "u32-storage-class") END + ( href: "#u64-storage-class" "u64-storage-class") END + ( href: "#f32-storage-class" "f32-storage-class") END + ( href: "#f64-storage-class" "f64-storage-class") END + ( href: "#c64-storage-class" "c64-storage-class") END + ( href: "#c128-storage-class" "c128-storage-class") + ".") + (
- "Arrays") + (
- ( href: "#make-array" "make-array")END + ( href: "#array?" "array?")END + ( href: "#array-domain" "array-domain")END + ( href: "#array-getter" "array-getter")END + ( href: "#array-dimension" "array-dimension")END + ( href: "#mutable-array?" "mutable-array?")END + ( href: "#array-setter" "array-setter")END + ( href: "#specialized-array" "specialized-array")END + ( href: "#specialized-array-share" "specialized-array-share")END + ( href: "#specialized-array?" "specialized-array?")END + ( href: "#array-safe?" "array-safe?") END + ( href: "#array-body" "array-body")END + ( href: "#array-indexer" "array-indexer")END + ( href: "#array-storage-class" "array-storage-class")END + ( href: "#array-map" "array-map")END + ( href: "#array-curry" "array-curry")END + ( href: "#array-for-each" "array-for-each")END + ( href: "#array-fold" "array-fold")END + ( href: "#array-fold-right" "array-fold-right")END + ( href: "#array-extract" "array-extract") END + ( href: "#specialized-array-default-safe?" "specialized-array-default-safe?") END + ( href: "#array->specialized-array" "array->specialized-array")END + ( href: "#array-translate" "array-translate")END + ( href: "#array-permute" "array-permute")END + ( href: "#array-reverse" "array-reverse")END + ( href: "#array-sample" "array-sample")END + ( href: "#array-every?" "array-every?")END + ( href: "#array->list" "array->list") END + ( href: "#list->specialized-array" "list->specialized-array") END + "." + ))) + (
"Miscellaneous Functions") + (
"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 occuring once, in any order.") -(
"Procedures") -(format-lambda-list '(translation? object)) -(
"Returns "(
'#t)" if "(
('object))" is a translation, and "(
'#f)" otherwise.") -(format-lambda-list '(permutation? object)) -(
"Returns "(
'#t)" if "(
('object))" is a permutation, and "(
'#f)" otherwise.") -(
"Intervals") -(
"An interval represents the set of all multi-indices of exact integers + (
"Procedures") + (format-lambda-list '(translation? object)) + (
"Returns "(
'#t)" if "(
('object))" is a translation, and "(
'#f)" otherwise.") + (format-lambda-list '(permutation? object)) + (
"Returns "(
'#t)" if "(
('object))" is a permutation, and "(
'#f)" otherwise.") + (
"Intervals") + (
"An interval represents the set of all multi-indices of exact integers $i_0,\\ldots,i_{d-1}$ satisfying $l_0\\leq i_0
"dimension")" of the interval. It is required that $l_0 "Intervals are a data type distinct from other Scheme data types.") + ( "Intervals are a data type distinct from other Scheme data types.") -(
"Procedures") -(format-lambda-list '(make-interval lower-bounds upper-bounds)) -(
"Create a new interval; "(
("lower-bounds"))" and "(
("upper-bounds"))" + (
"Procedures") + (format-lambda-list '(make-interval lower-bounds upper-bounds)) + (
"Create a new interval; "(
("lower-bounds"))" and "(
("upper-bounds"))" are nonempty vectors (of the same length) of exact integers that satisfy") -(
- (" (< (vector-ref "("lower-bounds")" i) (vector-ref "("upper-bounds")" i))")) -(
" for + (
+ (" (< (vector-ref "("lower-bounds")" i) (vector-ref "("upper-bounds")" i))")) + (
" for $0\\leq i<{}$"(
"(vector-length "("lower-bounds")")")". It is an error if "(
("lower-bounds"))" and "(
("upper-bounds"))" do not satisfy these conditions.") -(format-lambda-list '(interval? obj)) -(
"Returns "(
"#t")" if "(
("obj"))" is an interval, and "(
"#f")" otherwise.") + (format-lambda-list '(interval? obj)) + (
"Returns "(
"#t")" if "(
("obj"))" is an interval, and "(
"#f")" otherwise.") -(format-lambda-list '(interval-dimension interval)) -(
"If "(
("interval"))" is an interval built with ") -(
- ("(make-interval "("lower-bounds")" "("upper-bounds")")")) -(
"then "(
'interval-dimension)" returns "(
"(vector-length "("lower-bounds")")")". It is an error to call "(
'interval-dimension)" + (format-lambda-list '(interval-dimension interval)) + (
"If "(
("interval"))" is an interval built with ") + (
+ ("(make-interval "("lower-bounds")" "("upper-bounds")")")) + (
"then "(
'interval-dimension)" returns "(
"(vector-length "("lower-bounds")")")". It is an error to call "(
'interval-dimension)" if "(
("interval"))" is not an interval.") -(format-lambda-list '(interval-lower-bound interval i)) -(format-lambda-list '(interval-upper-bound interval i)) -(
"If "(
("interval"))" is an interval built with ") -(
- ("(make-interval "("lower-bounds")" "("upper-bounds")")")) -(
"and "(
("i"))" is an exact integer that satisfies") -(
- "$0 \\leq i<$ "("(vector-length "("lower-bounds")")")",") -(
" then "(
'interval-lower-bound)" returns + (format-lambda-list '(interval-lower-bound interval i)) + (format-lambda-list '(interval-upper-bound interval i)) + (
"If "(
("interval"))" is an interval built with ") + (
+ ("(make-interval "("lower-bounds")" "("upper-bounds")")")) + (
"and "(
("i"))" is an exact integer that satisfies") + (
+ "$0 \\leq i<$ "("(vector-length "(