Interface

The @tensor macro rewrites tensor operations in terms of basic building blocks, such that any tensor type that implements the following interface can be supported. In these methods, C will indicate an output tensor which is changed in-place, while A and B denote input tensors that are left unaltered. pC, pA and pB denote an Index2Tuple, a tuple of two tuples that represents a permutation and partition of the original tensor indices. Finally, conjA and conjB are boolean values that are used to indicate if the input tensor should be conjugated (true) or used as-is (false).

Operations

The three primitive tensor operations have already been described in the previous section, and correspond to

All other functions described in the previous section are implemented in terms of those. Hence, those are the only methods that need to be overloaded to support e.g. a new tensor type of to implement a new backend and/or allocator. For new types of tensors, it is possible to implement the methods without the final two arguments backend and allocator, if you know that you will never need them. They will not be inserted by the @tensor macro if they are not explicitly specified as keyword arguments. However, it is possible to add those arguments with the default values and ignore them in case they are not needed.

Alternatively, if some new tensor type is backed by an AbstractArray instance, and the tensor operations are also implemented by applying the same operations to the underlying array, it is possible to forward the value of the backend and allocator arguments in order to still support the full flexibility of the @tensor macro.

There is one more necessary tensor operation, which is to convert back from a rank zero tensor to a scalar quantity.

TensorOperations.tensorscalarFunction
tensorscalar(C)

Return the single element of a tensor-like object with zero indices or dimensions as a value of the underlying scalar type.

source

As there is typically a simple and unique way of doing so, this method does not have any backend or allocator arguments.

Allocations

For some networks, it will be necessary to allocate output and/or intermediate tensors. This process is split into the following hierarchy of functions, where custom tensor types can opt in at different levels.

By default, the process is split into three steps.

First, the scalar type TC of the resulting tensor is determined. This is done by leveraging VectorInterface.jl's scalartype, and promoting the results along with the types of any scalars that are present.

Then, the type and structure of the resulting tensor is determined. The former represents all the information that is contained within the type, while the latter adds the required runtime information (e.g. array sizes, ...).

TensorOperations.tensorstructureFunction
tensorstructure(A, iA, conjA)

Obtain the information associated to indices iA of tensor op(A), where op acts as identity if conjA equals false and as conj if conjA equals true.

source
TensorOperations.tensoradd_typeFunction
tensoradd_type(TC, A, pA, conjA)

Obtain the type information of C, where C would be the output of tensoradd!(C, A, pA, conjA) with scalartype TC.

source
TensorOperations.tensorcontract_typeFunction
tensorcontract_type(TC, A, pA, conjA, B, pB, conjB, pAB)

Obtain the type information of C, where C would be the output of tensorcontract!(C, A, pA, conjA, B, pB, conjB, pAB) with scalar type TC.

source
TensorOperations.tensorcontract_structureFunction
tensorcontract_structure(A, pA, conjA, B, pB, conjB, pAB)

Obtain the structure information of C, where C would be the output of tensorcontract!(C, A, pA, conjA, B, pB, conjB, pAB).

source

Finally, the tensor is allocated, where a flag indicates if this is a temporary object, or one that will persist outside of the scope of the macro. If the resulting tensor is a temporary object and its memory will not be freed by Julia's garbage collector, it can be explicitly freed by implementing tensorfree!, which by default does nothing.

TensorOperations.tensorallocFunction
tensoralloc(ttype, structure, [istemp=false, allocator])

Allocate memory for a tensor of type ttype and structure structure. The optional third argument can be used to indicate that the result is used as a temporary tensor, for which in some cases and with some allocators (the optional fourth argument) a different allocation strategy might be used.

See also tensoralloc_add, tensoralloc_contract and tensorfree!.

source

These functions also depend on the optional allocator argument that can be used to control different allocation strategies, and for example to differentiate between allocations of temporary tensors as opposed to tensors that are part of the output.

The @tensor macro will however only insert the calls to the following functions, which have a default implementation in terms of the functions above.

TensorOperations.tensoralloc_addFunction
tensoralloc_add(TC, A, pA, conjA, [istemp=Val(false), allocator])

Allocate a tensor C of scalar type TC that would be the result of

`tensoradd!(C, A, pA, conjA)`

The istemp argument is used to indicate that a tensor wlil not be used after the @tensor block, and thus will be followed by an explicit call to tensorfree!. The allocator can be used to implement different allocation strategies.

See also tensoralloc and tensorfree!.

source
TensorOperations.tensoralloc_contractFunction
tensoralloc_contract(TC, A, pA, conjA, B, pB, conjB, pAB, [istemp=Val(false), allocator])

Allocate a tensor C of scalar type TC that would be the result of

`tensorcontract!(C, A, pA, conjA, B, pB, conjB, pAB)`

The istemp argument is used to indicate that a tensor wlil not be used after the @tensor block, and thus will be followed by an explicit call to tensorfree!. The allocator can be used to implement different allocation strategies.

See also tensoralloc and tensorfree!.

source

Utility

Some of the optional keywords for @tensor can be accessed only after implementing the following utility functions:

TensorOperations.tensorcostFunction
tensorcost(A, i)

Compute the contraction cost associated with the ith index of a tensor, such that the total cost of a pairwise contraction is found as the product of the costs of all contracted indices and all uncontracted indices.

source
TensorOperations.checkcontractibleFunction
checkcontractible(A, iA, conjA, B, iB, conjB, label)

Verify whether two tensors opA(A) and opB(B) are compatible for having their respective index iA and iB contracted, and throws an error if not. The operation opA acts as identity if conjA equals false and as conj if conjA equals true; the operation opB is determined by conjB analogously.

source