|
| 1 | +.. _indexing: |
| 2 | + |
1 | 3 | # Indexing
|
| 4 | + |
| 5 | +> Array API specification for indexing arrays. |
| 6 | +
|
| 7 | +A conforming implementation of the array API standard must adhere to the following conventions. |
| 8 | + |
| 9 | +## Single-axis Indexing |
| 10 | + |
| 11 | +To index a single array axis, an array must support standard Python indexing rules. Let `n` be the axis (dimension) size. |
| 12 | + |
| 13 | +- An integer index must be an object satisfying [`operator.index`](https://www.python.org/dev/peps/pep-0357/) (e.g., `int`). |
| 14 | + |
| 15 | +- Nonnegative indices must start at `0` (i.e., zero-based indexing). |
| 16 | + |
| 17 | +- **Valid** nonnegative indices must reside on the half-open interval `[0, n)`. |
| 18 | + |
| 19 | + .. note:: |
| 20 | + |
| 21 | + This specification does not require bounds checking. The behavior for out-of-bounds integer indices is left unspecified. |
| 22 | + |
| 23 | +- Negative indices must count backward from the last array index, starting from `-1` (i.e., negative-one-based indexing, where `-1` refers to the last array index). |
| 24 | + |
| 25 | + .. note:: |
| 26 | + |
| 27 | + A negative index `j` is equivalent to `n-j`; the former is syntactic sugar for the latter, providing a shorthand for indexing elements that would otherwise need to be specified in terms of the axis (dimension) size. |
| 28 | + |
| 29 | +- **Valid** negative indices must reside on the closed interval `[-n, -1]`. |
| 30 | + |
| 31 | + .. note:: |
| 32 | + |
| 33 | + This specification does not require bounds checking. The behavior for out-of-bounds integer indices is left unspecified. |
| 34 | + |
| 35 | +- A negative index `j` is related to a zero-based nonnegative index `i` via `i = n+j`. |
| 36 | + |
| 37 | +- Colons `:` must be used for [slices](https://docs.python.org/3/library/functions.html#slice): `start:stop:step`, where `start` is inclusive and `stop` is exclusive. |
| 38 | + |
| 39 | +### Slice Syntax |
| 40 | + |
| 41 | +The basic slice syntax is `i:j:k` where `i` is the starting index, `j` is the stopping index, and `k` is the step (`k != 0`). A slice may contain either one or two colons, with either an integer value or nothing on either side of each colon. The following are valid slices. |
| 42 | + |
| 43 | +```text |
| 44 | +A[:] |
| 45 | +A[i:] |
| 46 | +A[:j] |
| 47 | +A[i:k] |
| 48 | +A[::] |
| 49 | +A[i::] |
| 50 | +A[:j:] |
| 51 | +A[::k] |
| 52 | +A[i:j:] |
| 53 | +A[i::k] |
| 54 | +A[:j:k] |
| 55 | +A[i::k] |
| 56 | +A[i:j:k] |
| 57 | +``` |
| 58 | + |
| 59 | +.. note:: |
| 60 | + |
| 61 | + Slice syntax can be equivalently achieved using the Python built-in [`slice()`](https://docs.python.org/3/library/functions.html#slice) API. From the perspective from `A`, the behavior of `A[i:j:k]` and `A[slice(i, j, k)]` is indistinguishable (i.e., both retrieve the same set of items from `__getitem__`). |
| 62 | + |
| 63 | +Using a slice to index a single array axis must select `m` elements with index values |
| 64 | + |
| 65 | +```text |
| 66 | +i, i+k, i+2k, i+3k, ..., i+(m-1)k |
| 67 | +``` |
| 68 | + |
| 69 | +where |
| 70 | + |
| 71 | +```text |
| 72 | +m = q + r |
| 73 | +``` |
| 74 | + |
| 75 | +and `q` and `r` (`r != 0`) are the quotient and remainder obtained by dividing `j-i` by `k` |
| 76 | + |
| 77 | +```text |
| 78 | +j - i = qk + r |
| 79 | +``` |
| 80 | + |
| 81 | +such that |
| 82 | + |
| 83 | +```text |
| 84 | +j > i + (m-1)k |
| 85 | +``` |
| 86 | + |
| 87 | +.. note:: |
| 88 | + |
| 89 | + For `i` on the interval `[0, n)` (where `n` is the axis size), `j` on the interval `(0, n]`, `i` less than `j`, and positive step `k`, a starting index `i` is **always** included, while the stopping index `j` is **always** excluded. This preserves `x[:i]+x[i:]` always being equal to `x`. |
| 90 | + |
| 91 | +.. note:: |
| 92 | + |
| 93 | + Using a slice to index into a single array axis should select the same elements as using a slice to index a Python list of the same size. |
| 94 | + |
| 95 | +Slice syntax must have the following defaults. Let `n` be the axis (dimension) size. |
| 96 | + |
| 97 | +- If `k` is not provided (e.g., `0:10`), `k` must equal `1`. |
| 98 | +- If `k` is greater than `0` and `i` is not provided (e.g., `:10:2`), `i` must equal `0`. |
| 99 | +- If `k` is greater than `0` and `j` is not provided (e.g., `0::2`), `j` must equal `n`. |
| 100 | +- If `k` is less than `0` and `i` is not provided (e.g., `:10:-2`), `i` must equal `n-1`. |
| 101 | +- If `k` is less than `0` and `j` is not provided (e.g., `0::-2`), `j` must equal `-n-1`. |
| 102 | + |
| 103 | +Using a slice to index a single array axis must adhere to the following rules. Let `n` be the axis (dimension) size. |
| 104 | + |
| 105 | +- If `i` equals `j`, a slice must return an empty array, whose axis (dimension) size along the indexed axis is `0`. |
| 106 | + |
| 107 | +- Indexing via `:` and `::` must be equivalent and have defaults derived from the rules above. Both `:` and `::` indicate to select all elements along a single axis (dimension). |
| 108 | + |
| 109 | +.. note:: |
| 110 | + |
| 111 | + This specification does not require "clipping" out-of-bounds indices (i.e., requiring the starting and stopping indices `i` and `j` be bound by `0` and `n`, respectively). |
| 112 | + |
| 113 | + _Rationale: this is consistent with bounds checking for integer indexing; the behavior of out-of-bounds indices is left unspecified. Implementations may choose to clip, raise an exception, return junk values, or some other behavior depending on device requirements and performance considerations._ |
| 114 | + |
| 115 | +.. note:: |
| 116 | + |
| 117 | + This specification leaves unspecified the behavior of indexing a single array axis with an out-of-bounds slice (i.e., a slice which does not select any array axis elements). |
| 118 | + |
| 119 | + _Rationale: this is consistent with bounds checking for integer indexing; the behavior of out-of-bounds indices is left unspecified. Implementations may choose to return an empty array (whose axis (dimension) size along the indexed axis is `0`), raise an exception, or some other behavior depending on device requirements and performance considerations._ |
| 120 | + |
| 121 | +## Multi-axis Indexing |
| 122 | + |
| 123 | +Multi-dimensional arrays must extend the concept of single-axis indexing to multiple axes by applying single-axis indexing rules along each axis (dimension) and supporting the following additional rules. Let `N` be the number of dimensions ("rank") of a multi-dimensional array `A`. |
| 124 | + |
| 125 | +- Each axis may be independently indexed via single-axis indexing by providing a comma-separated sequence ("selection tuple") of single-axis indexing expressions (e.g., `A[:, 2:10, :, 5]`). |
| 126 | + |
| 127 | + .. note:: |
| 128 | + |
| 129 | + In Python, `x[(exp1, exp2, ..., expN)]` is equivalent to `x[exp1, exp2, ..., expN]`; the latter is syntactic sugar for the former. |
| 130 | + |
| 131 | +- Providing a single nonnegative integer `i` as a single-axis index must index the same elements as the slice `i:i+1`. |
| 132 | + |
| 133 | +- Providing a single negative integer `i` as a single-axis index must index the same elements as the slice `n+i:n`, where `n` is the axis (dimension) size. |
| 134 | + |
| 135 | +- Providing a single integer as a single-axis index must reduce the number of array dimensions by `1` (i.e., the array rank should decrease by one; if `A` has rank `2`, `rank(A)-1 == rank(A[0, :])`). In particular, a selection tuple with the `m`th element an integer (and all other entries `:`) indexes a sub-array with rank `N-1`. |
| 136 | + |
| 137 | +- Providing a slice must retain array dimensions (i.e., the array rank must remain the same; `rank(A) == rank(A[:])`). |
| 138 | + |
| 139 | +- If the number of provided single-axis indexing expressions is less than `N`, then `:` must be assumed for the remaining dimensions (e.g., if `A` has rank `2`, `A[2:10] == A[2:10, :]`). |
| 140 | + |
| 141 | +- An `IndexError` exception must be raised if the number of provided single-axis indexing expressions is greater than `N`. |
| 142 | + |
| 143 | +- Providing [ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis) must apply `:` to each dimension necessary to index all dimensions (e.g., if `A` has rank `4`, `A[1:, ..., 2:5] == A[1:, :, :, 2:5]`). Only a single ellipsis must be allowed. An `IndexError` exception must be raised if more than one ellipsis is provided. |
| 144 | + |
| 145 | +- The result of multi-axis indexing must be an array of the same data type as the indexed array. |
| 146 | + |
| 147 | +.. note:: |
| 148 | + |
| 149 | + This specification leaves unspecified the behavior of providing a slice which attempts to select elements along a particular axis, but whose starting index is out-of-bounds. |
| 150 | + |
| 151 | + _Rationale: this is consistent with bounds-checking for single-axis indexing. An implementation may choose to set the axis (dimension) size of the result array to `0`, raise an exception, return junk values, or some other behavior depending on device requirements and performance considerations._ |
| 152 | + |
| 153 | +## Boolean Array Indexing |
| 154 | + |
| 155 | +An array must support indexing via a **single** `M`-dimensional boolean array `B` with shape `S1 = (s1, ..., sM)` according to the following rules. Let `A` be an `N`-dimensional array with shape `S2 = (s1, ..., sM, ..., sN)`. |
| 156 | + |
| 157 | +- If `N >= M`, then `A[B]` must replace the first `M` dimensions of `A` with a single dimension having a size equal to the number of `True` elements in `B`. The values in the resulting array must be in row-major (C-style order); this is equivalent to `A[nonzero(B)]`. |
| 158 | + |
| 159 | + .. note:: |
| 160 | + |
| 161 | + For example, if `N == M == 2`, indexing `A` via a boolean array `B` will return a one-dimensional array whose size is equal to the number of `True` elements in `B`. |
| 162 | + |
| 163 | +- If `N < M`, then an `IndexError` exception must be raised. |
| 164 | + |
| 165 | +- The size of each dimension in `B` must equal the size of the corresponding dimension in `A` or be `0`, beginning with the first dimension in `A`. If a dimension size does not equal the size of the corresponding dimension in `A` and is not `0`, then an `IndexError` exception must be raised. |
| 166 | + |
| 167 | +- The elements of a boolean index array must be iterated in row-major, C-style order, with the exception of zero-dimensional boolean arrays. |
| 168 | + |
| 169 | +- A zero-dimensional boolean index array (equivalent to `True` or `False`) must follow the same axis replacement rules stated above. Namely, a zero-dimensional boolean index array removes zero dimensions and adds a single dimension of length `1` if the index array's value is `True` and of length `0` if the index array's value is `False`. Accordingly, for a zero-dimensional boolean index array `B`, the result of `A[B]` has shape `S = (1, s1, ..., sN)` if the index array's value is `True` and has shape `S = (0, s1, ..., sN)` if the index array's value is `False`. |
| 170 | + |
| 171 | +- The result of indexing into an array via a boolean index array must be an array of the same data type as the indexed array. |
0 commit comments