;;;;"array.scm" Arrays for Scheme ; Copyright (C) 2001, 2003 Aubrey Jaffer ; ;Permission to copy this software, to modify it, to redistribute it, ;to distribute modified versions, and to use it for any purpose is ;granted, subject to the following restrictions and understandings. ; ;1. Any copy made of this software must include this copyright notice ;in full. ; ;2. I have made no warranty or representation that the operation of ;this software will be error-free, and I am under no obligation to ;provide any services, by way of maintenance, update, or otherwise. ; ;3. In conjunction with products arising from the use of this ;material, there shall be no use of my name in any advertising, ;promotional, or sales literature without prior written consent in ;each case. ;;@code{(require 'array)} ;;@ftindex array (require 'record) (define array:rtd (make-record-type "array" '(shape scales ;list of dimension scales offset ;exact integer store ;data ))) (define array:shape (let ((shape (record-accessor array:rtd 'shape))) (lambda (array) (cond ((vector? array) (list (list 0 (+ -1 (vector-length array))))) ((string? array) (list (list 0 (+ -1 (string-length array))))) (else (shape array)))))) (define array:scales (let ((scales (record-accessor array:rtd 'scales))) (lambda (obj) (cond ((string? obj) '(1)) ((vector? obj) '(1)) (else (scales obj)))))) (define array:store (let ((store (record-accessor array:rtd 'store))) (lambda (obj) (cond ((string? obj) obj) ((vector? obj) obj) (else (store obj)))))) (define array:offset (let ((offset (record-accessor array:rtd 'offset))) (lambda (obj) (cond ((string? obj) 0) ((vector? obj) 0) (else (offset obj)))))) (define array:construct (record-constructor array:rtd '(shape scales offset store))) ;;@args obj ;;Returns @code{#t} if the @1 is an array, and @code{#f} if not. (define array? (let ((array:array? (record-predicate array:rtd))) (lambda (obj) (or (string? obj) (vector? obj) (array:array? obj))))) ;;@noindent ;;@emph{Note:} Arrays are not disjoint from other Scheme types. Strings ;;and vectors also satisfy @code{array?}. A disjoint array predicate can ;;be written: ;; ;;@example ;;(define (strict-array? obj) ;; (and (array? obj) (not (string? obj)) (not (vector? obj)))) ;;@end example ;;@body ;;Returns @code{#t} if @1 and @2 have the same rank and shape and the ;;corresponding elements of @1 and @2 are @code{equal?}. ;; ;;@example ;;(array=? (create-array '#(foo) 3 3) ;; (create-array '#(foo) '(0 2) '(0 2))) ;; @result{} #t ;;@end example (define (array=? array1 array2) (and (equal? (array:shape array1) (array:shape array2)) (equal? (array:store array1) (array:store array2)))) (define (array:dimensions->shape dims) (map (lambda (dim) (if (list? dim) dim (list 0 (+ -1 dim)))) dims)) ;;@args prototype bound1 bound2 @dots{} ;; ;;Creates and returns an array of type @1 with dimensions @2, @3, ;;@dots{} and filled with elements from @1. @1 must be an array, ;;vector, or string. The implementation-dependent type of the returned ;;array will be the same as the type of @1; except if that would be a ;;vector or string with non-zero origin, in which case some variety of ;;array will be returned. ;; ;;If the @1 has no elements, then the initial contents of the returned ;;array are unspecified. Otherwise, the returned array will be filled ;;with the element at the origin of @1. (define (create-array prototype . dimensions) (define range2length (lambda (bnd) (- 1 (apply - bnd)))) ;;(if (not (array? prototype)) (set! prototype (vector prototype))) (let* ((shape (array:dimensions->shape dimensions)) (dims (map range2length shape)) (scales (do ((dims (reverse (cdr dims)) (cdr dims)) (scls '(1) (cons (* (car dims) (car scls)) scls))) ((null? dims) scls)))) (array:construct shape scales (- (apply + (map * (map car shape) scales))) (if (string? prototype) (case (string-length prototype) ((0) (make-string (apply * dims))) (else (make-string (apply * dims) (string-ref prototype 0)))) (let ((pshape (array:shape prototype))) (case (apply * (map range2length pshape)) ((0) (make-vector (apply * dims))) (else (make-vector (apply * dims) (apply array-ref prototype (map car pshape)))))))))) ;;@noindent ;;These functions return a prototypical uniform-array enclosing the ;;optional argument (which must be of the correct type). If the ;;uniform-array type is supported by the implementation, then it is ;;returned; defaulting to the next larger precision type; resorting ;;finally to vector. (define (make-prototype-checker name pred? creator) (lambda args (case (length args) ((1) (if (pred? (car args)) (creator (car args)) (slib:error name 'incompatible 'type (car args)))) ((0) (creator)) (else (slib:error name 'wrong 'number 'of 'args args))))) (define (integer-bytes?? n) (lambda (obj) (and (integer? obj) (exact? obj) (or (negative? n) (not (negative? obj))) (do ((num obj (quotient num 256)) (n (+ -1 (abs n)) (+ -1 n))) ((or (zero? num) (negative? n)) (zero? num)))))) ;;@args z ;;@args ;;Returns a high-precision complex uniform-array prototype. (define Ac64 (make-prototype-checker 'Ac64 complex? vector)) ;;@args z ;;@args ;;Returns a complex uniform-array prototype. (define Ac32 (make-prototype-checker 'Ac32 complex? vector)) ;;@args x ;;@args ;;Returns a high-precision real uniform-array prototype. (define Ar64 (make-prototype-checker 'Ar64 real? vector)) ;;@args x ;;@args ;;Returns a real uniform-array prototype. (define Ar32 (make-prototype-checker 'Ar32 real? vector)) ;;@args n ;;@args ;;Returns an exact signed integer uniform-array prototype with at least ;;64 bits of precision. (define As64 (make-prototype-checker 'As64 (integer-bytes?? -8) vector)) ;;@args n ;;@args ;;Returns an exact signed integer uniform-array prototype with at least ;;32 bits of precision. (define As32 (make-prototype-checker 'As32 (integer-bytes?? -4) vector)) ;;@args n ;;@args ;;Returns an exact signed integer uniform-array prototype with at least ;;16 bits of precision. (define As16 (make-prototype-checker 'As16 (integer-bytes?? -2) vector)) ;;@args n ;;@args ;;Returns an exact signed integer uniform-array prototype with at least ;;8 bits of precision. (define As8 (make-prototype-checker 'As8 (integer-bytes?? -1) vector)) ;;@args k ;;@args ;;Returns an exact non-negative integer uniform-array prototype with at ;;least 64 bits of precision. (define Au64 (make-prototype-checker 'Au64 (integer-bytes?? 8) vector)) ;;@args k ;;@args ;;Returns an exact non-negative integer uniform-array prototype with at ;;least 32 bits of precision. (define Au32 (make-prototype-checker 'Au32 (integer-bytes?? 4) vector)) ;;@args k ;;@args ;;Returns an exact non-negative integer uniform-array prototype with at ;;least 16 bits of precision. (define Au16 (make-prototype-checker 'Au16 (integer-bytes?? 2) vector)) ;;@args k ;;@args ;;Returns an exact non-negative integer uniform-array prototype with at ;;least 8 bits of precision. (define Au8 (make-prototype-checker 'Au8 (integer-bytes?? 1) vector)) ;;@args bool ;;@args ;;Returns a boolean uniform-array prototype. (define At1 (make-prototype-checker 'At1 boolean? vector)) ;;@noindent ;;When constructing an array, @var{bound} is either an inclusive range of ;;indices expressed as a two element list, or an upper bound expressed as ;;a single integer. So ;; ;;@example ;;(create-array '#(foo) 3 3) @equiv{} (create-array '#(foo) '(0 2) '(0 2)) ;;@end example ;;@args array mapper bound1 bound2 @dots{} ;;@0 can be used to create shared subarrays of other ;;arrays. The @var{mapper} is a function that translates coordinates in ;;the new array into coordinates in the old array. A @var{mapper} must be ;;linear, and its range must stay within the bounds of the old array, but ;;it can be otherwise arbitrary. A simple example: ;; ;;@example ;;(define fred (create-array '#(#f) 8 8)) ;;(define freds-diagonal ;; (make-shared-array fred (lambda (i) (list i i)) 8)) ;;(array-set! freds-diagonal 'foo 3) ;;(array-ref fred 3 3) ;; @result{} FOO ;;(define freds-center ;; (make-shared-array fred (lambda (i j) (list (+ 3 i) (+ 3 j))) ;; 2 2)) ;;(array-ref freds-center 0 0) ;; @result{} FOO ;;@end example (define (make-shared-array array mapper . dimensions) (define odl (array:scales array)) (define rank (length dimensions)) (define shape (array:dimensions->shape dimensions)) (do ((idx (+ -1 rank) (+ -1 idx)) (uvt (append (cdr (vector->list (make-vector rank 0))) '(1)) (append (cdr uvt) '(0))) (uvts '() (cons uvt uvts))) ((negative? idx) (let* ((ker0 (apply + (map * odl (apply mapper uvt)))) (scales (map (lambda (uvt) (- (apply + (map * odl (apply mapper uvt))) ker0)) uvts))) (array:construct shape scales (- (apply + (array:offset array) (map * odl (apply mapper (map car shape)))) (apply + (map * (map car shape) scales))) (array:store array)))))) ;;@body ;;Returns the number of dimensions of @1. If @1 is not an array, 0 is ;;returned. (define (array-rank obj) (if (array? obj) (length (array:shape obj)) 0)) ;;@args array ;;Returns a list of inclusive bounds. ;; ;;@example ;;(array-shape (create-array '#() 3 5)) ;; @result{} ((0 2) (0 4)) ;;@end example (define array-shape array:shape) ;;@body ;;@code{array-dimensions} is similar to @code{array-shape} but replaces ;;elements with a 0 minimum with one greater than the maximum. ;; ;;@example ;;(array-dimensions (create-array '#() 3 5)) ;; @result{} (3 5) ;;@end example (define (array-dimensions array) (map (lambda (bnd) (if (zero? (car bnd)) (+ 1 (cadr bnd)) bnd)) (array:shape array))) (define (array:in-bounds? array indices) (do ((bnds (array:shape array) (cdr bnds)) (idxs indices (cdr idxs))) ((or (null? bnds) (null? idxs) (not (integer? (car idxs))) (not (<= (caar bnds) (car idxs) (cadar bnds)))) (and (null? bnds) (null? idxs))))) ;;@args array index1 index2 @dots{} ;;Returns @code{#t} if its arguments would be acceptable to ;;@code{array-ref}. (define (array-in-bounds? array . indices) (array:in-bounds? array indices)) ;;@args array index1 index2 @dots{} ;;Returns the (@2, @3, @dots{}) element of @1. (define (array-ref array . indices) (define store (array:store array)) (or (array:in-bounds? array indices) (slib:error 'array-ref 'bad-indices indices)) ((if (string? store) string-ref vector-ref) store (apply + (array:offset array) (map * (array:scales array) indices)))) ;;@args array obj index1 index2 @dots{} ;;Stores @2 in the (@3, @4, @dots{}) element of @1. The value returned ;;by @0 is unspecified. (define (array-set! array obj . indices) (define store (array:store array)) (or (array:in-bounds? array indices) (slib:error 'array-set! 'bad-indices indices)) ((if (string? store) string-set! vector-set!) store (apply + (array:offset array) (map * (array:scales array) indices)) obj)) ;;; Legacy functions ;; ;;@args initial-value bound1 bound2 @dots{} ;; ;;Creates and returns an array with dimensions @2, ;; ;;@3, @dots{} and filled with @1. ;; (define (make-array initial-value . dimensions) ;; (apply create-array (vector initial-value) dimensions))