# Index Sugar and Tensor Modifiers

In Finch, expressions like `x[i + 1]`

are compiled using tensor modifiers, like `offset(x, 1)[i]`

. The user can construct tensor modifiers directly, e.g. `offset(x, 1)`

, or implicitly using the syntax `x[i + 1]`

. Recognizable index expressions are converted to tensor modifiers before dimensionalization, so that the modified tensor will participate in dimensionalization.

While tensor modifiers may change the behavior of a tensor, they reference their parent tensor as the `root`

tensor. Modified tensors are not understoond as distinct from their roots. For example, all accesses to the root tensor must obey lifecycle and dimensionalization rules. Additionally, root tensors which are themselves modifiers are unwrapped at the beginning of the program, so that modifiers are not obscured and the new root tensor is not a modifier.

The following table lists the recognized index expressions and their equivalent tensor expressions, where `i`

is an index, `a, b`

are constants, `p`

is an iteration protocol, and `x`

is an expression:

Original Expression | Transformed Expression |
---|---|

`A[i + a]` | `offset(A, 1)[i]` |

`A[i + x]` | `toeplitz(A, 1)[i, x]` |

`A[(a:b)(i)]` | `window(A, a:b)[i]` |

`A[a * i]` | `scale(A, (3,))[i]` |

`A[i * x]` | `products(A, 1)[i, j]` |

`A[~i]` | `permissive(A)[i]` |

`A[p(i)]` | `protocolize(A, p)[i]` |

Each of these tensor modifiers is described below:

`Finch.offset`

— Function`offset(tns, delta...)`

Create an `OffsetArray`

such that `offset(tns, delta...)[i...] == tns[i .+ delta...]`

. The dimensions declared by an OffsetArray are shifted, so that `size(offset(tns, delta...)) == size(tns) .+ delta`

.

`Finch.toeplitz`

— Function`toeplitz(tns, dim)`

Create a `ToeplitzArray`

such that

` Toeplitz(tns, dim)[i...] == tns[i[1:dim-1]..., i[dim] + i[dim + 1], i[dim + 2:end]...]`

The ToplitzArray can be thought of as adding a dimension that shifts another dimension of the original tensor.

`Finch.window`

— Function`window(tns, dims)`

Create a `WindowedArray`

which represents a view into another tensor

` window(tns, dims)[i...] == tns[dim[1][i], dim[2][i], ...]`

The windowed array restricts the new dimension to the dimension of valid indices of each `dim`

. The `dims`

may also be `nothing`

to represent a full view of the underlying dimension.

`Finch.scale`

— Function`scale(tns, delta...)`

Create a `ScaleArray`

such that `scale(tns, delta...)[i...] == tns[i .* delta...]`

. The dimensions declared by an OffsetArray are shifted, so that `size(scale(tns, delta...)) == size(tns) .* delta`

. This is only supported on tensors with real-valued dimensions.

`Finch.products`

— Function`products(tns, dim)`

Create a `ProductArray`

such that

` products(tns, dim)[i...] == tns[i[1:dim-1]..., i[dim] * i[dim + 1], i[dim + 2:end]...]`

This is like `toeplitz`

but with times instead of plus.

`Finch.permissive`

— Function`permissive(tns, dims...)`

Create an `PermissiveArray`

where `permissive(tns, dims...)[i...]`

is `missing`

if `i[n]`

is not in the bounds of `tns`

when `dims[n]`

is `true`

. This wrapper allows all permissive dimensions to be exempt from dimension checks, and is useful when we need to access an array out of bounds, or for padding. More formally,

```
permissive(tns, dims...)[i...] =
if any(n -> dims[n] && !(i[n] in axes(tns)[n]))
missing
else
tns[i...]
end
```

`Finch.protocolize`

— Function`protocolize(tns, protos...)`

Create a `ProtocolizedArray`

that accesses dimension `n`

with protocol `protos[n]`

, if `protos[n]`

is not nothing. See the documention for Iteration Protocols for more information. For example, to gallop along the inner dimension of a matrix `A`

, we write `A[gallop(i), j]`

, which becomes `protocolize(A, gallop, nothing)[i, j]`

.