empty intervals
Alex Shinn
(21 Apr 2022 11:02 UTC)
|
Re: empty intervals Bradley Lucier (21 Apr 2022 16:30 UTC)
|
Re: empty intervals
Bradley Lucier
(21 Apr 2022 16:37 UTC)
|
Re: empty intervals
Per Bothner
(21 Apr 2022 19:23 UTC)
|
Re: empty intervals
Bradley Lucier
(21 Apr 2022 21:59 UTC)
|
Re: empty intervals
Per Bothner
(21 Apr 2022 22:11 UTC)
|
Re: empty intervals
Bradley Lucier
(23 Apr 2022 17:56 UTC)
|
Re: empty intervals
Per Bothner
(23 Apr 2022 19:44 UTC)
|
Re: empty intervals
Alex Shinn
(24 Apr 2022 05:03 UTC)
|
Re: empty intervals
John Cowan
(21 Apr 2022 22:56 UTC)
|
Re: empty intervals
Alex Shinn
(22 Apr 2022 09:03 UTC)
|
On 4/21/22 7:02 AM, Alex Shinn wrote: > The notes mentions only a preference not to allow empty intervals > without a clear rationale. It seems to me these are useful in a number > of situations. I agree that "empty" arrays can be useful in some circumstances. For example, Jens Axel brought up numpy.squeeze, which eliminates axes that have length 1 (so they're in some sense irrelevant). I came up with the following code: (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))))))))) Here we have two special cases because the second argument to curry must be strictly between 0 and the number of dimensions: 1. If there are no length-one axes, then we want to return the original array, so the entries of the result array are conceptually zero-dimension arrays containing each array element. Here we need the first case of the cond. If we had a reasonable semantics for (curry a (array-dimension a)) this could be avoided. 2. If all the axes have length one, then we want to curry away all the dimensions, leaving a scalar. numpy returns a zero-dimensional array with the value. So instead of an array of zero-dimensional arrays containing single values, we have a zero-dimensional array containing the sole element of the array. To think of these two special cases in terms of functions we'd be converting (lambda (x) (+ 2 x)) to either (lambda () (lambda (x) (+ 2 x))) or (lambda (x) (lambda () (+ 2 x))) I have, in the past, tried to go over all the components of this proposal to think about how to insert consistent treatments of empty arrays. Here are some thoughts: 1. Empty lists, strings, vectors, etc., can be empty in only one way: they always have one "dimension", but there could be zero elements in that one dimension. Arrays can be "degenerate" in at least two ways: there may be no dimensions, or there may be no indices in one or more dimensions. 2. The case of zero dimensions seems easier to integrate into this proposal. A zero-dimensional array would have one value. But would a value be (uplifted to) a zero-dimensional array? When would you return a zero-dimensional array containing a value, and when the value itself? And would this definition be recursive? Would a zero-dimensional array containing a zero-dimensional array containing ... a value be the same as that value? Even numpy isn't consistent in how it handles these things. (I'm not criticizing numpy for this, I'm just saying that I'd like to have a consistent semantics for this SRFI.) 3. I find I understand arrays with no indices in certain dimensions even less. Is the array with domain (make-interval '#(3) '#(3)) different from the array with domain (make-interval '#(0) '#(0))? So, in brief, I haven't come up with a consistent semantics. > One case I encountered recently was in a procedure to replace a single > column within a 2x2 matrix. This can be done by appending the column > along with the existing left and right sides of the matrix, but since > they may be empty we need to special case: I agree that this case would be simplified with empty arrays with the right semantics. Here's a routine I came up with: (define (array-with-column array col vals) (let ((columns (array->list (array-curry (array-permute array '#(1 0)) 1)))) (apply array-stack 1 (append (take col columns) (specialized-array-reshape vals (array-domain (car columns))) (drop (+ col 1) columns))))) Here I've transferred the "empty data structure" semantics onto lists, which have a well-defined semantics. I find it hard to describe "confusion" in writing, which is what I end up with when I try to write about empty arrays, zero-dimensional arrays, etc.