Contraction

Einsum operations are performed automatically by unary_einsum and binary_einsum.

Unlike other tensor libraries, the einsum pattern is not explicitly stated by the user but implicitly inferred from the Tensor objects; i.e. repeated indices will be contracted while unique indices will remain. However, the user might require some flexibility on the output and contracted indices. That's why unary_einsum and binary_einsum have two extra keyword arguments: dims, which lists the indices to be contracted, and out, which lists the resulting indices after the contraction. Keep in mind that you're not forced to define them: dims defaults to the repeated indices and out defaults to the unique indices, but it's not recommended to define both.

For example, let's imagine that we want to perform the following operation: A sum over one dimension of a tensor.

\[X_j = \sum_i A_{ij}\]

unary_einsum can act on just one tensor (unary contraction) and the user can write the following operation in two different ways:

julia> Muscle.unary_einsum(a; dims=[Index(:i)])ERROR: ArgumentError: `unary_einsum!` not implemented or not loaded for backend Tensor{Float64, 2, Matrix{Float64}}
julia> Muscle.unary_einsum(a; out=[Index(:j)])ERROR: ArgumentError: `unary_einsum!` not implemented or not loaded for backend Tensor{Float64, 2, Matrix{Float64}}

For the case of binary contraction, imagine the following matrix multiplication:

\[Y_j = \sum_i A_{ij} B_{ji}\]

Then the default would be enough, although you can still define dims or out.

julia> Muscle.binary_einsum(a, b)0-dimensional Tensor{Float64, 0, Array{Float64, 0}}:
119.0
julia> Muscle.binary_einsum(a, b; dims=[Index(:i)])ERROR: ArgumentError: `BackendBase` can't deal with hyperindices. Load OMEinsum and use `BackendOMEinsum` instead. isdisjoint(inds_c, inds_contract) must hold. Got inds_c => Index[index<j>] inds_contract => Index[index<i>, index<j>]
julia> Muscle.binary_einsum(a, b; out=[Index(:j)])ERROR: ArgumentError: `BackendBase` can't deal with hyperindices. Load OMEinsum and use `BackendOMEinsum` instead. isdisjoint(inds_c, inds_contract) must hold. Got inds_c => Index{Symbol}[index<j>] inds_contract => Index[index<i>, index<j>]

But what if instead of contracting index :i, we want to perform a Hadamard product (element-wise multiplication)? Then that's a case where implicit inference of the einsum rule is not enough and you need to specify dims or out.

julia> # Muscle.binary_einsum(a, b; dims=Index[])
       Muscle.binary_einsum(a, b; out=[Index(:i), Index(:j)])ERROR: ArgumentError: `BackendBase` can't deal with hyperindices. Load OMEinsum and use `BackendOMEinsum` instead.
isdisjoint(inds_c, inds_contract) must hold. Got
inds_c => Index{Symbol}[index<i>, index<j>]
inds_contract => Index[index<i>, index<j>]