Common Subarrays, R/W
Subarrays are used to extract or change parts of an array. ILNumerics subarrays are compatible with both: Matlab® arrays and numpy ndarrays. Both frameworks share a common set of subarray features which is demonstrated on this page. Thus, all examples here are applicable - regardless of where you are coming from. Other features may require an explicit choice of the feature set (see: ArrayStyles). Those special cases are described here. It is recommended to read both documents.
On this page:
- Single Element Access
- Special Placeholders: end, -1
- Ranges and Slices
- (Advanced) Index Arrays
- Performance Considerations
Two ways exist for creating and modifying parts of existing ILNumerics arrays:
- Indexers (C#) / default properties (Visual Basic), returning ILNumerics arrays. This is the recommended method in most situations and used in the examples below.
- Functional interface. For languages not supporting indexers or if you need fast access to elements based on their system type:
Array<T> A member function Example Type received/returned GetValue(d0,d1,...) double a = A.GetValue(0,1) Element type SetValue(value, d0, d1,...) A.SetValue(a, 0, 1) Element type Subarray(d0,d1,...) Array<double> B = A.Subarray(":",2) ILNumerics array SetRange(value, d0, d1,...) A.SetRange(C, "2:end", full) ILNumerics array
Which element to access is determined by specifying the 0-based index $i$ of the element for each dimension of the array. The left-most index corresponds to the first dimension. Commonly, an index $i_k$ is expected to lay in the range
$$0 \le i_k \lt l.$$ Here, $l$ corresponds to the number of elements in the dimension $k$, l = A.S[k].
Writing to individual elements of an existing array works in the same way:
Here, exactly one index is provided for each dimension in the array. Special cases arise when some dimension indices are omitted or when indices address non-existing elements. These are described here.
The position index of an element within a dimension is addressed by System.UInt32 (uint) or System.Int64 (long) integer, or by string representations of such integers ("1","4",...). A different type can be used for each dimension if needed. The compilers overload resolution will automatically determine the best function overload for the set of indices provided.
The following special placeholders are additionally supported:
- ILNumerics.Globals.end or simple arithmetic expressions thereof. 'end' will be replaced with the index of the last element in this dimension at runtime:
- Negative numbers translate into the final index by adding the length of the dimension to it. This is often used as a quicker alternative to end in addressing positions relative to the end of the dimension:
Subarrays may address a range of indices from each dimension. In numpy this is called 'slicing' or 'basic indexing'. Index ranges are specified in one of the following ways:
- The ILNumerics.Globals.r(start,end) function allows to provide the first index (inclusive, start) and the last index (inclusive, end). Start, end and all elements in between are selected. Alternatively, a positive step size can be determined using the overload r(start,step,end). All special index placeholders are supported. This function corresponds to 'start:end' and 'start:step:end' ranges, known from Matlab® and similar CAS.
- Similarly, the ILNumerics.Globals.slice(start,end) function allows to provide the first index inside (inclusive, start) and the last index outside (exclusive, end) of the selection. Note that slice excludes the end from the selection. For stepped selections slice(start,end,step) is used, where, again, step must be positive. All special index placeholders are supported. slice() offers compatibility with numpy indexing.
- the specifier Globals.full selects the full dimension, from index 0 to (and including) the last index. This corresponds to providing a single colon ':' in Matlab® or numpy.
- the specifier Globals.ellipsis expands the set of provided index specifications by substituting it with repeated 'full's in a way that each dimension of the array receives exactly one specification. This corresponds to the '...' specifier in numpy and is often used to conveniently address 'last' dimensions.
- Ranges can also be specified by string expressions in the form "start:end" or "start:step:end". Here, start, step and end must be string representations of integer values. If start is omitted the first element will be used. If end is the string "end" or is omitted the last element is assumed. Both, start and end may be strings of negative integers, addressing an index relative to the end of the dimension. Note that ranges defined by strings correspond to Matlab ranges (not numpy slices), i.e.: the end index is included into the selection!
- Write access: all subarrays, including ranges/ slices can be used on the left side of array expressions to modify parts of existing arrays. The array on the right side determines the new values for the subrange and its size must match the target size, as determined by the subrange on the left, i.e.: it must either have the same shape or a shape broadcastable to the target range.
All examples so far provide a dedicated selection for all dimensions of the array. What happens when more or fewer dimensions are addressed is configurable in ILNumerics. This and further subarray features, as: expansion, removal, and newaxis are described in detail here.
If in a subarray expression A[d0,d1,d2,...] one of the indices d0...dn is an ILNumerics array, forming a vector of numeric or boolean values, then the indexing works just the same as with ranges and slices above. Index array elements are used as indices into the corresponding dimension.
Values of numeric elements are directly taken as 0-based index. Again, negative index array elements are considered as 'counting from the end':
Some useful ways of creating integer arrays:
In order to select reversed ranges, create an integer array with the indices to be selected from the dimension in the required order.
Logical arrays are returned from binary comparison operators. Using them as index allows for filtering values based on a certain condition. A boolean index vector (Logical array) as index is (virtually) translated into element indices for the corresponding dimension by computing the 0-based position indices of its true values. It is recommended that the logical index vector has the same length as the dimension it is targeting.
Things become more interesting when multiple index arrays are involved, boolean indices are mixed with numeric index arrays, or either one spans multiple dimensions and/or is combined with basic indices. The result is as defined by numpy or ILNumericsV4(Matlab) and is controlled by the current ArrayStyle setting. Read more.
All subarray expressions in ILNumerics (as well as their functional counterparts SetRange()/ Subarray()) create a copy of the values addressed from the original array. As always, the copy is done as lazy 'copy - on - write'. Hence, the concept of 'views' known from numpy also exists in ILNumerics, with the exception that views are readonly and are copied when attempting to alter the view. This not only simplifies internal memory management (in terms of memory aliasing) but also makes sure that all subarrays behave the same, thus there are no surprises.
Some subarray features are designed for performance. Others are optimized for flexibility. Both categories are represented in the ILNumerics API: compiler resolution is used in order to choose automatically between the fast API and the flexible API - based on the index specifications provided by the user.
- The fast API is utilized when all index specifications are out of: ranges r(start,[step,]end), slices slice(start,end[,step]), numeric scalars and special index placeholders, full, end, ellipsis and newaxis. These index types resolve to subarray overloads expecting arguments of type DimSpec by implicit casting and do not require to iterate or copy any values for reading.
- The flexible API is triggered when at least one index in the list of provided index specifications is an array of numeric indices, a boolean array or a string of index specification(s). In this case the compiler will resolve the subarray definition to an overload expecting BaseArray arguments and the implementation will iterate and copy elements.
However, internally, the values are often not really copied. Optimization rules may reference the original values, or even reuse the source array directly. In this case, the object returned from indexing may be a modified version of the same array instance! One example is chained subarray expressions: A[r(2,end),full], which become really efficient this way. Another example is the replacement of whole arrays by 'ellipsis' or 'full' ranges, which is internally drastically reduced to the modification of a handful of pointers.
Such optimizations are made possible by strongly typed arrays, which carry not only types of their elements but also array lifetime type information. The user, however, just follows the general ILNumerics function rules and the optimizations happen transparently in the back.
Random Subarray Recommendations
The following guidelines serve as hints for efficient subarray use. They are a direct consequence from the last paragraphs.
1) Be explicit! Try to specify all dimensions:
2) Prefer fast indexing! Integer indices and ranges or slices are more efficient than string and array indices: