TensorFlux.jl

Differential geometry with mathematical notation in Julia

Tensors

Tensors are objects that transform in specific ways under coordinate changes. They are categorized by how they transform, so an (m, n)-tensor follows m contravariant and n covariant transformations. The order of a tensor is given by m + n.

The simplest tensor is a (0, 0)-tensor, a scalar. A (1, 0)-tensor is also known as a vector, and a (0, 1)-tensor is known as a covector or one-form. TensorFlux uses Julia's adjoint to specify the variance of an index.

julia

                julia> v = Tensor([1, 2]) # A vector
                (1, 0)-Tensor:
                [1, 2]
                    (:contra,)
                julia> ω = Tensor([-3, 1]') # A covector
                (0, 1)-Tensor:
                [-3, 1]
                    (:co,)
                

The Tensor type stores the variance of each index, as well as the components.

julia

                julia> v.data
                2-element Vector{Int64}:
                    1
                    2
                julia> ω.variance
                (:co,)
                

The next step up are order 2 tensors. A (1, 1)-tensor is also known as a linear map. An example of a (0, 2)- and a (2, 0)-tensor is the metric and inverse metric.

julia

                julia> L = Tensor([[4, -2]', [1, 1]']) # A linear map
                (1, 1)-Tensor:
                [4 -2; 1 1]
                    (:contra, :co)
                julia> g = Tensor([[2, 1]', [1, 2]']') # A (0, 2)-tensor
                (0, 2)-Tensor:
                [2 1; 1 2]
                    (:co, :co)
                

This pattern continues to tensors of higher order.

Indexing

Tensors need to be indexed for operations. Indexing a Tensor returns an IndexedTensor type, and either symbolic or integer indicies can be used.

Indexing follows the convention that contravariant indices are written on top (first), and covariant indices are written on the bottom (second). This distinction is arbitrary for a tensor of constant variance. Thus any (k, 0)- or (0, k)-tensor can be indexed as

julia

                julia> T[i1, i2...ik]
                

While a (k, l)-tensor is indexed as

julia

                julia> T[i1, i2...ik][j1, j2...jl]
                

Indexing with pure integers will return a scalar.

julia

                julia> v = Tensor([1, 2])
                julia> v[1] # Pure integer (1, 0)-tensor
                1
                julia> L = Tensor([[4, -2]', [1, 1]'])
                julia> L[1][2] # Pure integer (1, 1)-tensor
                -2
                

Indexing with pure symbols or a mix of integers and symbols will return an indexed tensor. In the mixed case, the data will be spliced.

julia

                julia> v[:i] # Pure symbolic (1, 0)-tensor
                (1, 0)-Tensor:
                [1, 2]
                    (:contra,)
                    (:i,), ()
                julia> L[:i][:j] # Pure symbolic (1, 1)-tensor
                (1, 1)-Tensor:
                [4 -2; 1 1]
                    (:contra, :co)
                    (:i,), (:j,)
                julia> L[2][:k] # Mixed (1, 1)-tensor
                (0, 1)-Tensor:
                [1, 1]
                    (:co,)
                    (), (:k,)
                

The same convention of indexing applies to other objects with slight modifications.

Operations

A fundamental operation with tensors is the tensor product. It takes an (m, n)-tensor and a (p, q) tensor and returns an (m + p, n + q)-tensor.

julia

                julia> v = Tensor([2, -1])
                julia> w = Tensor([3, 4])
                julia> α = Tensor([2, 3]')
                julia> L = v ⊗ w # Tensor product of two vectors
                (2, 0)-Tensor:
                [6 8; -3 -4]
                    (:contra, :contra)
                julia> L ⊗ α ⊗ v  # Tensor product of a (2, 0)-tensor, a covector, and a vector
                (3, 1)-Tensor:
                [24 32; -12 -16;;; 36 48; -18 -24;;;; -12 -16; 6 8;;; -18 -24; 9 12]
                    (:contra, :contra, :co, :contra)
                

Another fundamental operation is contraction. Contraction takes a linear combination along the repeated indices, and is the extension of matrix-vector multiplication. Contraction requires that, in each pair, one index is contravariant and the other is covariant.

julia

                julia> α[:i] * v[:i] # A covector contracted with a vector
                1
                julia> A = v ⊗ α # A linear map
                julia> A[:i][:j] * v[:j] # A linear map contracted with a vector
                (1, 0)-Tensor:
                [2, -1]
                    (:contra,)
                    (:i,), ()
                julia> L[:i, :j] * A[:k][:j] # A (2, 0)-tensor contracted with a linear map
                (2, 0)-Tensor:
                [72 -36; -36 18]
                    (:contra, :contra)
                    (:i, :k), ()
                

Contraction also allows you to take the trace of a tensor across a pair of indices.

julia

                julia> A[:i][:i]
                1
                

Scaling is applied across all elements.

julia

                julia> 2 * L[:i, :j] # Scaling
                (2, 0)-Tensor:
                [12 16; -6 -8]
                    (:contra, :contra)
                    (:i, :j), ()
                

Addition and subtraction are element-wise.

julia

                julia> M = Tensor([[4, 2], [-1, 1]])
                julia> L[:i, :j] + M[:i, :j] # Addition
                (2, 0)-Tensor:
                [10 10; -4 -3]
                    (:contra, :contra)
                    (:i, :j), ()
                julia> L[:i, :j] - M[:i, :j] # Subtraction
                (2, 0)-Tensor:
                [2 6; -2 -5]
                    (:contra, :contra)
                    (:i, :j), ()