Industrial Data Science
in C# and .NET:
Simple. Fast. Reliable.

ILNumerics - Technical Computing

Modern High Performance Tools for Technical

Computing and Visualization in Industry and Science

# Extended indexing Styles

ILNumerics.Array<T> fully supports the n-d array features of both: Matlab (ILNumerics vers.4 / Octave / Julia) and numpy.ndarray. Many common indexing cases create the same result for both feature sets. Functions in ILNumerics are prepared to handle any array - regardless which style the array was created in. Some array features, however, differ slightly between numpy and Matlab style. Here, the intended behavior is selected by the setting ILNumerics.Settings.ArrayStyle. This setting controls the indexing style when creating or modifying subarrays as described below.

Make sure to reference the module ILNumerics.numpy in order to enable numpy semantics.

## Indexing feature sets

Indexing involves many aspects and can become quite flexible. If you are familiar with one or both supported indexing feature sets (numpy and Matlab® indexing) you will find most of the aspects described here to be 100% compatible to its origin.

### Handling missing dimensions

If in a subarray expression $A[d_0,d_1,\ldots, d_i]$ $i$ is smaller than the number of dimensions of $A$, trailing unspecified dimensions are ...

ArrayStyle = ArrayStyles.ILNumericsV4       ArrayStyle = ArrayStyles.numpy

... replaced with 0. Hence, only the first element is selected from unspecified dimensions. The last specifier $d_i$ may reach over the range of dimension $i$, in which case $d_i$ is considered a sequential index into the merged trailing dimensions (and assuming column major storage).

... replaced with ':'  or 'full'. Note, that $d_0$ corresponds to the first dimension of $A$ and unspecified trailing dimension are 'full'y selected from A (same as in numpy).

### Virtual Dimensions

ILNumerics arrays have a dedicated number of explicitly specified dimensions: A.S.NumberOfDimensions. Additionally, any number of virtual dimensions can be addressed in indexing. Virtual dimensions are singleton dimensions, with a length of 1 and are ... right: virtual. They can be addressed just like regular dimensions by aiming at the only element (index #0) in index arguments. Virtual dimensions are convenient when the dimensionality of A is not known in advance and for indexing into numpy scalars. This works regardless of the current array style:

ArrayStyle = ArrayStyles.ILNumericsV4       ArrayStyle = ArrayStyles.numpy

Array<uint> A = 1;

// A is 2dim [1,1] now!

// A[0, full] gives the same element as A[full, 0, 0, 0, 0]

Array<uint> A = 1;

// A is a 0-dim scalar array!

B = A[0]; // virtual dimension indexing of scalar A

### Newaxis Specifier

When reading a subarray the ILNumerics.Globals.newaxis specifier inserts a singleton dimension into the indexing result at the position the specifier was defined at. This functionality corresponds to the newaxis keyword in numpy. In ILNumerics it is only available in numpy array style. (In difference to the ILNumerics.Globals.ellipsis specifier which works in both array styles.) When combining newaxis with ellipsis existing newaxis specifiers do not contribute to the number of 'full' dimensions to be inserted in place of the ellipis specifier. Thus, newaxis does not count as a regular dimension index specifier to this regard.

### Subarray Size

The number of dimensions D of a subarray returned from indexers $A[d_0,d_1,\ldots, d_i]$ depends on the indexing arguments $d_0,d_1,\ldots, d_i$:

ILNumericsV4       numpy
$D$ equals $i+1$, respecting a lower limit of 2. (In Matlab, any array is a matrix or higher dimensional.)

$D$ is computed by adding the dimensionalities of the indices $d_0,...d_i$. For example: a scalar $d$ (0-dims) contributes 0 dimensions, ranges & slices bring 1 dimension and numeric index arrays contribute all their non-virtual dimensions to the result. Index specifiers addressing a virtual dimension of $A$ create a virtual dimension in $D$, too. Thus, adressing a 0-dim scalar $s$ with: $s[full]$ creates a 0-dim scalar.

See below for advanced indexing rules.

### Reading Index Out Of Range

For read access, all indices may address existing elements. An IndexOutOfRangeException is thrown otherwise. In general, if $d_i$ (the last index specifier provided) reaches over the limits of the corresponding dimension...

ILNumericsV4       numpy
... $d_i$ is considered a sequential index into the merged trailing dimensions (counting in column major order). Only if $d_i$ also exceeds the limits of the merged dimensions, now addressing a non-existing element, an exception is thrown. Note that addressing existing elements inside such merged dimensions may silently change the storage layout of $A$ to ColumnMajor order. See example below. ... an exception is thrown.

### Writing Index Out Of Range / Expansion

If in an indexing expression $A[d_0,d_1,\ldots, d_i] = B$ an index $d_0,...d_{i}$ exceeds the limits of the corresponding dimension of $A$ then ...

ILNumericsV4       numpy
... $A$ is expanded just enough to store the addressed elements, and fills the rest of the new elements with 0 or default(T). ... an IndexOutOfRangeException is thrown. In numpy, arrays cannot change their size.

### Removing Subarrays / Shrinking

Indexing expressions $A[d_0,d_1,\ldots, d_i] = B$, where at most one index specification does not address a full dimension and $B$ is an empty array or null, cause ...

ArrayStyle = ArrayStyles.ILNumericsV4       ArrayStyle = ArrayStyles.numpy
... the subarray to be removed from A. A's shape is decreased accordingly.  ... an ArgumentException to be thrown. In numpy, arrays cannot change their size.

### Array Assignments shapes

In an assigning index expression $A[d_0,d_1,\ldots, d_i] = B$, the array $B$ on the rigth side must have the same element type than $A$. Its size must be 'suitable' for the indexing size $S$ formed by $d_0,...d_{i}$ on the left side. An array $B$ of the same shape than $S$ is always suitable. Other options include:

ArrayStyle = ArrayStyles.ILNumericsV4       ArrayStyle = ArrayStyles.numpy
The shapes of $B$ and $S$ can differ if B.S.NumberOfElements equals the number of elements addressed by $S$: elements are copied from $B$ in column major order. Or $B$ is broadcastable to $S$. Note, that broadcasting in ArrayStyles.ILNumericsV4 starts with the first dimension! Ex.: [3,2] is broadcastable to [3,2,5]. [2,5] is not broadcastable to [3,2,5]. $B$ is broadcastable to $S$. Note, that broadcasting in ArrayStyles.numpy starts with the last dimension! Ex.: [2,5] is broadcastable to [3,2,5]. [3,2] is not broadcastable to [3,2,5].

The set of indices $d_0,d_1,\ldots, d_i$ may include any number of numeric arrays $N_0,\ldots,N_k$, where $N_l$ has $n$ number of dimensions and a shape of $S_l$. For $k = 0$ and $n = 1$ the behavior is straight forward: Elements of the only vector shaped index array $N_0$ simply select the indices from the corresponding dimension of $A$. The resulting dimension is found in the subarray at the position corresponding to the input dimension. Otherwise, ...

ILNumericsV4 numpy
... the indices $N_0,\ldots,N_k$ are considered as flattened version (reshaped to a vector in column major order). Their shapes and or number of elements may differ. Each $N$ contributes to the dimension of the output according to the position in the set of indices $d_0,d_1,\ldots, d_i$. ...all indices $N_0,\ldots,N_k$ must be broadcastable to the single advanced indexing space. This space may has multiple dimensions which are inserted into the subarray according to the numpy advanced indexing rules.

### Boolean Indices

Any index specifier $d_0,d_1,\ldots, d_i$ can be a Logical array $L_0,\ldots,L_m$. If $m = 0$ and the only logical is a vector matching the lengths of the corresponding dimension of $A$, only such elements are selected from $A$ matching the positions of true elements in $L_0$. Otherwise, and for $m > 0$ ...

ILNumericsV4 numpy
... the logical indices $L_0,\ldots,L_k$ are converted*) into integer indices by applying the Math.find() function first. The resulting index is a vector of sequential indices of the true elements and is used to index into the corresponding dimension, just like regular numeric indices. ...the logical indices $L_0,\ldots,L_k$ are converted*) into integer indices as if numpy.nonzero() were applied first. Each logical index potentially spans and selects elements from multiple dimensions, according to its own rank.  numpy boolean indexing rules apply.

*) No actual conversion is performed in the implementation.

## Other ArrayStyle features

The current setting of ILNumerics.Settings.ArrayStyle controls even more array features:

### Storage order

Elements of ILNumerics arrays can be stored in memory in arbitrary order. Most users don't have to deal with this order at all. ILNumerics will automatically decide the best storage order for most operations. Nevertheless, many functions accept an optional argument determining the storage order. For all cases, where no 'best' storage order exists the storage order for new arrays is determined as follows:

ILNumericsV4       numpy
New arrays will have column major storage order, if no other storage order is implied by the operation, by the user or by performance rules.

New arrays will be created in row major storage order, if no other storage order is implied by the operation, by the user or by performance rules.

### Minimum/Maximum Number of Dimensions

ILNumerics arrays are capable of handling up to $n$ dimensions, where $n$ corresponds to the value of ILNumerics.Size.MaxNumberDimensions. This number is 32 in version 5.1.

ILNumericsV4       numpy
All arrays have 2 or more dimensions. If necessary, singleton dimensions are padded at the end of an array in order to meet the lower limit of 2 dimensions. Arrays may have 0 or more dimensions.