123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- guile-ploy is an array library for Guile. Guile's array functions are low-level
- and barebones. If you need basic stuff such as a reshape function or a usable
- subscript operation, you'll find them here.
- guile-ploy doesn't use macros (other than some assert macros that you don't need
- to use), doesn't use GOOPS, and it's all Guile Scheme [1]. This is not by design,
- but perhaps a sign of immaturity.
- [1] guile-ploy requires a version of Guile that provides the functions
- array-cell-ref, array-slice, array-cell-set! and array-slice-for-each. These
- were added to Guile in 2.2 (2.1.6).
- A (slower) replacement for these functions is commented out in ploy/basic.scm,
- in case you want to try the library without rebuiliding Guile.
- What follows is a description of the library itself. If you aren't interested in
- internals you can look up examples of use in test/test-*.scm.
- 0. (ploy basic)
- (ploy basic) Some definitions to bootstrap the library.
- 1. array-map/frame! in (ploy ploy)
- This is a version of array-map! that is able to iterate over array slices
- (cells), not just over array elements. Now that (array-slice-for-each) is
- available, this has become a thin wrapper that handles rank-0 arrays and
- return values, since (array-slice-for-each), just as the other Guile array
- functions, operates exclusively by side effect.
- 2. make-verb in (ploy ploy)
- Define a new 'verb', a type of function that applies over arrays and knows the
- rank of its arguments. The name is from the J language.
- 3. nested-op-frames in (ploy ploy)
- array-map/frame! needs the frames of all its arguments to match
- exactly. nested-op-frames is a rank extension / rank matching mechanism, modeled
- after J. This is so one can do obvious things like
- (+verb 1 #(1 2 3 4)) -> #(2 3 4 5)
- (+verb #(1 2) #(2 4)) -> #(3 6)
- This is similar to 'broadcasting' in other languages/libraries, like
- Numpy. There's a good explanation in
- http://www.jsoftware.com/help/jforc/loopless_code_i_verbs_have_r.htm#_Toc191734341
- nested-op-frames also supports the 'rank conjunction' (w/rank) which can be used
- to write the equivalent of nested loops, as explained in that link.
- This matching of ranks is done using shared arrays, with no copies.
- 4. ply, ply/type in (ploy ploy)
- This is an interface to the functionality above that takes also regular Scheme
- functions, asigning rank 0 to all of their arguments. So you can do
- (ply + 1 #(1 2 3 4)) -> #(2 3 4 5)
- The result will have the same array type as the first argument. ply/type allows
- you to give an explicit output type.
- The verb type includes an oshape field that is used to compute the shape of the
- output cell. If this is available, it's used to preallocate the whole
- destination array. If the oshape is #f, the result is first computed as an
- array-of-arrays and then 'collapsed'.
- A difference with J is that the output cells must all have the same shape; no
- padding is done. Therefore, it should be possible in principle to compute the
- first cell, then use its shape for the whole output array. If the output rank is
- 0, collapse is not needed, since the array-of-whatever is already the result we
- seek.
- 5. (from A . i) and (amend! A val . i) in (ploy ploy)
- This is a rectangular selection/assignment operator. The semantics are after
- APL. Each element of i is an array of indices into the corresponding axis of A,
- and the shape of the result is the concatenation of the shapes of all the
- i. This is rather like Octave's or Numpy's subscripting, except that the
- arguments can be of any rank.
- It is a regular Scheme function, not a verb as defined above.
- There are optimizations for 'J-vectors', (like a:b:c in Octave or Numpy slices),
- just plain integers, or for taking a full axis at once. These are used to avoid
- copying the result when possible. Array indices always cause a copy to be made.
- In Numpy or J, negative indices are interpreted as indices from the end of the
- axis. This cannot work with general Guile arrays, where the lower bounds can
- (unfortunately, since I don't think it's useful) be different from 0. The Octave
- feature of using 'end' for the last index of the axis could be replicated for
- (from) through the use of macros.
- There are examples of use in test-ploy.scm.
- 6. ravel, reshape in (ploy ploy)
- This is the reshape function of every array language. The semantics are a mix of
- J and Octave. It clips or repeats, like J but unlike Octave, but it unravels its
- arguments, like Octave but unlike J. Unraveling is in C order.
- It does some effort to avoid copying, but I might not have covered every case.
- Like (from A . i), these are regular Scheme functions, not verbs.
- 7. cat, icat in (ploy cat)
- To concatenate arrays over arbitrary axes. Like Octave's cat. (cat) concatenates
- over a given axis, while (icat) (= item-cat) concatenates items of the given
- rank.
- 8. folda, foldb in (ploy reduce)
- These are very simple reductions based on J over (/). They operate over the
- items of the array:
- (folda + 0 #2((1 3 2) (10 20 30))) => #2((11 23 32)) .
- folda and foldb are semantically identical. The difference between them is that
- folda performs the fold 'above' the frame of its array op, while foldb does it
- 'below'. In the example above, the fold is along rows, and the array op is along
- columns. In folda, the outer loop is over rows and the inner loop over columns;
- in foldb it's the opposite. See also 5.2 of the Repa paper.
- They perform very differently depending on the shapes of the arguments, although
- a lot of this difference is just an artifact of how crude the implementation is
- (e.g. the apply-map-from burden).
- I'd prefer the order to be left unspecified, so that fold would decide by itself
- whether to fold above, below or even in the middle, if the frame allows. This is
- similar to how ply is supposed to work: it resolves to an array-map! call, and
- in principle traversal order is decided there.
- This functions are a bit of an experiment, I'm not sure they should work as they
- do.
- 9. as-array in (ploy as-array)
- A function to convert array-like objects into arrays. It's a generalization of
- Guile's list->array or list->typed-array, similar to Numpy's asarray. Array-like
- objects are nested vectors, nested lists, lists of vectors, arrays of lists, and
- so on.
- This function is needed in two typical cases. One is when interfacing with other
- Scheme code that has been written for those array-like types. (Look in
- test/test-as-array for ways to do the converse operation.) The other is when
- interfacing with other languages/libraries that expect arrays to use a specific
- memory layout (e.g. C) and do not accept general strides.
- 10. (ploy slices)
- This module contains a disorganized list of other array functions: out explode
- raze reverse. tile sort. every. any. index are some that I use
- often. Unfortunately most of these don't work as verbs but as regular Scheme
- functions, since the ply/verb as implemented here mechanism is either too slow,
- too inconvenient, or not well-thought enough to handle these functions that
- generally apply over full arrays (would have rank -1 in J). This is a sad
- situation and I hope to improve on it in the future.
- That's it! Thanks for reading.
- - lloda
|