Email list hosting service & mailing list manager


Numpy's array manipulation routines compared to this SRFI. Bradley Lucier 25 Feb 2022 20:17 UTC

Summary:

Most array manipulation routines in NumPy have associated methods in
this SRFI.  Maybe this SRFI should have array-block, an inverse to
array-tile.  (A use case for array-tile is segmenting a large array to
pass to GPU memory blocks, so array-block could copy the contents of
those blocks back to a single array.) array-squeeze combines existing
things in a simple way.

I invite anyone interested in having NumPy's array manipulation power in
Scheme to review this list.

Also, the array-block example uses a lot of list*->arrays, it might
inform opinions about the proper argument order.

Brad

Numpy array manipulation routines, from

https://numpy.org/doc/stable/reference/routines.array-manipulation.html

We note approximate correspondences with this SRFI.  Numpy operations
allow the user to consider either C or Fortran ordering; in this SRFI
one can do something like that, search for "fortran" below.

numpy.copyto: Like array-assign!

numpy.shape: Like array-domain

numpy.reshape: Like specialized-array-reshape

numpy.ravel: Like specialized-array-reshape with a copy if necessary.

ndarray.flat: Seems like what a SRFI 42 :array iterator would be like.

ndarray.flatten: (specialized-array-reshape (array-copy ...))

numpy.moveaxis: array-permute

numpy.rollaxis: says to use numpy.moveaxis

numpy.swapaxes: array-permute

numpy.ndarray.T: array-permute

numpy.transpose: array-permute (Documentation says "A view is returned
whenever possible."  When wouldn't it be possible?)

numpy.atleast_1d: ???

numpy.atleast_2d: ???

numpy.atleast_3d: ???

numpy.broadcast: ???

numpy.broadcast_to: ???

numpy.broadcast_arrays: ???

numpy.expand_dims: ???

numpy.squeeze: easy to write with array-permute and array-curry, but
because this SRFI does not have zero-dimensional arrays has different
semantics in case all axes have length one.  (By the way, numpy is not
consistent in its handling of zero-dimensional arrays, there are quite a
number of exceptional cases.)

(define (array-squeeze a)
   (let* ((domain
           (array-domain a))
          (dim
           (interval-dimension domain))
          (all-axes
           (iota dim))
          (ks
           ;; indices of all the length-one axes
           (filter (lambda (k)
                     (= (- (interval-upper-bound domain k)
                           (interval-lower-bound domain k))
                        1))
                   all-axes))
          (length-ks
           (length ks))
          (permutation
           ;; put length-one axes first, leave the rest in order.
           (list->vector (append ks (remove (lambda (k) (memv k ks))
all-axes)))))
     (cond ((zero? length-ks)
            a)
           ((= dim length-ks)
            ;; a has one element, Numpy returns a zero-dimensional array
containing the element.
            (car (array->list a)))
           (else
            (car (array->list
                  (array-curry   ;; this array has only one element.
                   (array-permute a permutation)
                   (- dim (length ks)))))))))

numpy.asarray: I don't understand it completely, but it appears that
list->array, list*->array, vector->array, and vector*->array, as well
as, e.g.

(array-copy (array-map inexact a) f64-storage-class)

covers most of the bases.

numpy.asanyarray: Similar to numpy.asarray, I think.

numpy.asmatrix: It seems that matrices and arrays are separate

numpy.asfarray: Use (array-copy (array-map inexact a) f64-storage-class)

numpy.asfortranarray: If you want to take the same elements and go
through them in Fortran order you could (assuming the elements of a are
contiguous and in order in its body)

(define (array->fortran a)
   (let* ((domain
           (array-domain a))
          (dim
           (interval-dimension domain))
          (reverse-permutation
           (list->vector (reverse (iota dim))))
          (reverse-domain
           (interval-permute domain reverse-permutation)))
     (array-permute (specialized-array-reshape a reverse-domain)
reverse-permutation)))

Then

 > (define b (specialized-array-reshape
(make-specialized-array-from-data (list->vector (iota 10)))
(make-interval '#(2 5))))
 > (array->vector* b)
#(#(0 1 2 3 4) #(5 6 7 8 9))
 > (array->vector* (array->fortran b))
#(#(0 2 4 6 8) #(1 3 5 7 9))
 > (array-body b)
#(0 1 2 3 4 5 6 7 8 9)
 > (array-body (array->fortran b))
#(0 1 2 3 4 5 6 7 8 9)

numpy.ascontiguousarray: I don't quite understand what this does, but
array-map, array-copy, and specialized-array-reshape seems to do it.

numpy.asarray_chkfinite: Like numpy.asarray, but throws an error if any
element is inexact and not finite.

numpy.asscalar: Use the above array-squeeze with all dimensions length 1.

numpy.require: Seems like combining numpy.asarray, numpy.asfarray, etc.

numpy.concatenate: array-append

numpy.stack: array-stack

numpy.block: Interesting, inverse to array-tile in this SRFI, in a
similar way that array-stack is a partial inverse to array-curry after
array-permute.  Something like

 > (define (array-block list*)
   (define (helper axis list-or-array)
     (if (list? list-or-array)
         (apply array-append
                axis
                (map (lambda (l-or-a)
                       (helper (+ axis 1) l-or-a))
                     list-or-array))
         list-or-array))
   (helper 0 list*))
 > (define a
   (array-block
    (list (list (list*->array '((0 1)
                                (2 3))
                              2)
                (list*->array '((4 5)
                                (6 7))
                              2))
          (list (list*->array '((8 9)) 2)
                (list*->array '((10 11)) 2)))))
 > (array->vector* a)
#(#(0 1 4 5)
   #(2 3 6 7)
   #(8 9 10 11))

Maybe this SRFI should have something like this.  Only instead of nested
lists/nested vectors/... the argument should be an array of arrays, like
the output of array-tile.

numpy.vstack, numpy.hstack, numpy.dstack, numpy.column_stack,
numpy.row_stack: array-stack

numpy.split, numpy.array_split, numpy.dsplit, numpy.hsplit,
numpy.vsplit: array-tile

numpy.tile: nada

numpy.repeat: nada

numpy.delete: Like the example in the SRFI document after array-stack.

numpy.insert: Combine array-permute, array-curry, array->list, array-stack.

numpy.append: array-append.

numpy.resize: shouldn't exist.

numpy.trim_zeros: Nada

numpy.uniq: combine array->list, sort, uniq, list->array.

numpy.flip, numpy.fliplr, numpy.flipud: array-reverse

numpy.reshaps: specialized-array-reshape

numpy.roll: combine array-curry, array->list, take and drop from SRFI 1,
append, array-stack; see the example in the SRFI document after array-stack.

numpy.rot90: combine array-permute and array-reverse (I think).