Tensors

Constructors

LinearCombinations.TensorType
Tensor{T<:Tuple}

Tensor{T}(xs...) where T
Tensor(xs...)

The type Tensor represents pure tensors.

A general tensor is a linear combination of pure tensors and can conveniently be created using tensor. LinearCombinations takes pure tensors as basis elements.

A Tensor can be created out of a Tuple or out of the individual components. The second form is not available if the tensor has a tuple as its only component.

Tensor implements the iteration and indexing interfaces. This makes for example splatting available for tensors, and the i-th component of t::Tensor can be accessed as t[i].

Tensors can be nested. Different bracketings lead to different tensors. The functions cat, flatten, swap and regroup are provided to make rearranging tensors more easily.

Note that the type parameter of Tensor is always a Tuple. For instance, the type of a Tensor with two components of types T1 and T2 is Tensor{Tuple{T1,T2}}, not Tensor{T1,T2}.

See also tensor, cat, flatten, regroup, swap.

Examples

julia> t = Tensor('x', 'y', "z")
x⊗y⊗z

julia> typeof(t)
Tensor{Tuple{Char, Char, String}}

julia> Tuple(t)
('x', 'y', "z")

julia> length(t), t[2], t[end]
(3, 'y', "z")

julia> a = Linear('x' => 1, 'y' => 2)
x+2*y

julia> b = Linear(Tensor('x', 'z') => 1, Tensor('y', 'z') => 2)
x⊗z+2*y⊗z

julia> b == tensor(a, 'z')
true

julia> [uppercase(x) for x in t]
3-element Vector{Any}:
 'X': ASCII/Unicode U+0058 (category Lu: Letter, uppercase)
 'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
 "Z"

julia> f((x1, xs...)::Tensor) = x1
f (generic function with 1 method)

julia> f(t)
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

julia> t == Tensor(Tensor('x', 'y'), "z")
false

julia> a = tensor(); a[Tensor()]
1
source
LinearCombinations.tensorFunction
tensor(xs...) -> Linear{Tensor}
x1 ⊗ x2 ⊗ ... -> Linear{Tensor}

tensor is the multilinear extension of Tensor. is a synomym for tensor. Note that tensor always returns a linear combination.

See also Tensor, @multilinear

Examples

julia> a, b = Linear('x' => 1, 'y' => 2), Linear("w" => 3, "z" => -1)
(x+2*y, 3*w-z)

julia> tensor(a, "w")
x⊗w+2*y⊗w

julia> tensor(a, b)
-2*y⊗z+3*x⊗w-x⊗z+6*y⊗w

julia> tensor('x', b, a; coefftype = Float64)
-2.0*x⊗z⊗y-x⊗z⊗x+6.0*x⊗w⊗y+3.0*x⊗w⊗x

julia> a = tensor(); a[Tensor()]
1
source

Manipulating tensors

Core.TupleMethod
Tuple(t::AbstractTensor{T}) -> T <: Tuple

Return the tuple of components of t.

Although any AbstractTensor has to supports the iteration interface, it is often more efficient to deal with the underlying Tuple of components. For instance, functions like map or reduce map return a Tuple in this case instead of a Vector.

Example

julia> t = Tensor('A','b','c')
A⊗b⊗c

julia> Tuple(t)
('A', 'b', 'c')

julia> map(isuppercase, t)
3-element Vector{Bool}:
 1
 0
 0

julia> map(isuppercase, Tuple(t))
(true, false, false)
source
LinearCombinations.catFunction
cat(t::AbstractTensor...) -> Tensor

Concatenate the tensors given as arguments. This function is multilinear.

See also flatten.

Example

julia> LinearCombinations.cat(Tensor('x'), Tensor('y', Tensor('z', 'w')))
x⊗y⊗(z⊗w)
source
LinearCombinations.flattenFunction
flatten(t::AbstractTensor) -> Tensor
flatten(a::AbstractLinear{<:AbstractTensor}) -> AbstractLinear{Tensor}

Recursively take all tensor components and concatenate the result. This function is linear.

See also cat.

Example

julia> t = Tensor('x', Tensor('y', Tensor('z', 'w')))
x⊗(y⊗(z⊗w))

julia> flatten(t)
x⊗y⊗z⊗w
source
LinearCombinations.swapConstant
swap(t::AbstractTensor{Tuple{T1,T2}}) where {T1,T2} -> AbstractLinear{Tensor{Tuple{T2,T1}}}
swap(a::AbstractLinear{AbstractTensor{Tuple{T1,T2}})}) where {T1,T2}
    -> AbstractLinear{Tensor{Tuple{T1,T2}})}

This linear function swaps the components of two-component tensors. If the two components of a tensor t have non-zero degrees, then the usual sign (-1)^(deg(t[1])*deg(t[2])) is introduced. By default, all terms have zero degree.

Note that swap is a special case of regroup: it is simply defined as regroup(:((1, 2)), :((2, 1))).

See also Tensor, deg, regroup, LinearCombinations.DefaultCoefftype.

Examples

Examples without degrees

julia> t = Tensor("x", "z")
x⊗z

julia> swap(t)
z⊗x

julia> a = Linear("x" => 1, "yy" => 1) ⊗ Linear("z" => 1, "ww" => 1)
yy⊗ww+x⊗ww+x⊗z+yy⊗z

julia> swap(a)
ww⊗yy+z⊗x+z⊗yy+ww⊗x

julia> swap(a; coeff = 2)
2*ww⊗yy+2*z⊗x+2*z⊗yy+2*ww⊗x

Examples with degrees

For simplicity, we define the degree of a String to be its length.

julia> LinearCombinations.deg(x::String) = length(x)

julia> swap(t)   # same t as before
-z⊗x

julia> swap(a)   # same a as before
ww⊗yy-z⊗x+z⊗yy+ww⊗x
source
LinearCombinations.RegroupType
LinearCombinations.Regroup{A, B}

Applying a Regroup object to a Tensor or a linear combinations of tensors rearranges the components of the tensor. Use regroup to create a Regroup object. It is possible to define additional methods to apply Regroup objects to other arguments besides tensors.

See also regroup.

source
LinearCombinations.regroupFunction
regroup(a, b) -> Regroup

Return a Regroup object that can be used to rearrange the components of tensors and possibly other structures.

The actual rearrangement is specified by the two parameters a and b. Both are expression trees consisting of nested tuples of integers. These trees encode the structure of nested tensors, and the integers specify a mapping from the components of the nested source tensor to the nested target tensor. The labels for a and b can in fact be of any isbits type instead of Int, but they must be the same for a and b.

The return value rg = regroup(a, b) is a callable object. An argument t for rg must be a nested tensor of the same shape as the a tree, and the return value is a Tensor of the same shape as b. The components of the nested tensor t are permuted according to the labels.

If the components of t have non-zero degrees, then rg(t) additionally has a sign according to the usual sign rule: whenever two ojects x and y are swapped, then this incurs the sign (-1)^(deg(x)*(deg(y))).

Moreover, rg is linear and can be called with linear combinations of tensors.

Note that for each Regroup element rg, Julia generates separate, efficient code for computing rg(t).

See also swap, regroup_inv, Regroup, LinearCombinations.DefaultCoefftype.

Examples

Example without degrees

julia> rg = regroup(:( (1, (2, 3), 4) ), :( ((3, 1), (4, 2)) ))
Regroup{(1, (2, 3), 4),((3, 1), (4, 2))}

julia> t = Tensor("x", Tensor("y", "z"), "w")
x⊗(y⊗z)⊗w

julia> rg(t)
(z⊗x)⊗(w⊗y)

Example with degrees

For simplicity, we define the degree of a String to be its length.

julia> LinearCombinations.deg(x::String) = length(x)

julia> rg(t)   # same rg and t as before
-(z⊗x)⊗(w⊗y)
source
Base.transposeFunction
transpose(t::AbstractTensor{T}) where T <: Tuple{Vararg{AbstractTensor}}

Return the transpose of a tensor t whose components are tensors of the same length. In other words, the component transpose(t)[i][j] is t[j][i]. If the components t[i][j] have non-zero degrees, a sign is added according to the usual sign rule. The tensor t must have at least one component. If all component tensors are empty, then the empty tensor Tensor() is returned.

This function is linear.

Examples

Example without signs

julia> t = Tensor(Tensor("a", "b", "c"), Tensor("x", "y", "z"))
(a⊗b⊗c)⊗(x⊗y⊗z)

julia> transpose(t)
(a⊗x)⊗(b⊗y)⊗(c⊗z)

Example with signs

As usual, the degree of a String is its length.

julia> LinearCombinations.deg(x::String) = length(x)

julia> transpose(t)   # same t as before
-(a⊗x)⊗(b⊗y)⊗(c⊗z)
source

Calling tensors

LinearCombinations.AbstractTensorMethod
(tf::AbstractTensor)(tx::AbstractTensor...) -> Tensor

Evaluating an AbstractTensor on other AbstractTensors (with the same number of components) is done componentwise. If the degrees of the components and the maps are not all zero, then the usual sign is introduced: whenever a map f is moved past a component x, then this changes the sign by (-1)^(deg(f)*deg(x)).

Examples

Examples without degrees

julia> @linear f; f(x) = uppercase(x)
f (generic function with 2 methods)

julia> @linear g; g(x) = lowercase(x)
g (generic function with 2 methods)

julia> const h = Tensor(f, g)
f⊗g

julia> a, b = Linear('x' => 1, 'y' => 2), Linear('Z' => -1, 'W' => 3)
(x+2*y, -Z+3*W)

julia> h(Tensor('x', 'Z'))
X⊗z

julia> h(tensor(a, b))
6*Y⊗w-2*Y⊗z+3*X⊗w-X⊗z

Examples with degrees

We again take the length of a String as its degree.

julia> import LinearCombinations: deg

julia> deg(x::String) = length(x);

julia> struct P{T} y::T end; deg(p::P) = deg(p.y);

julia> @linear p::P; (p::P)(x) = x * p.y

julia> p = P("pp"); q = P("qqq")
P{String}("qqq")

julia> j = Tensor(p, q)
P{String}("pp")⊗P{String}("qqq")

julia> j(Tensor("x", "yy"))
-xpp⊗yyqqq

julia> a = Linear("x" => 1, "yy" => 2)
x+2*yy

julia> b = tensor(a, a)
2*x⊗yy+4*yy⊗yy+x⊗x+2*yy⊗x

julia> j(b)
-xpp⊗xqqq-2*xpp⊗yyqqq+2*yypp⊗xqqq+4*yypp⊗yyqqq

A multilinear example

julia> @multilinear f; f(x::Char...) = join(x, '#');

julia> @multilinear g; g(x::Char...) = join(x, '@');

julia> f('a', 'p', 'x')
"a#p#x"

julia> Tensor(f, g)(Tensor('a', 'b'), Tensor('p', 'q'), Tensor('x', 'y'))
a#p#x⊗b@q@y
source

Other functions accepting tensors

LinearCombinations.degMethod
deg(t::AbstractTensor)

Return the degree of a tensor, which is the sum of the degrees of its components.

See also deg.

source
Base.:*Method
*(t1::AbstractTensor , t2::AbstractTensor, ...)

Return the product of the tensors, computed from the products of its components. Signs are introduced according to the usual sign rule. If all degrees are integers, then the coefficient type is DefaultCoefftype.

This function is linear.

See also: LinearCombinations.DefaultCoefftype.

Example

julia> import LinearCombinations: deg

julia> deg(x::String) = length(x);

julia> (s, t) = Tensor("ab", "c"), Tensor("x", "yz")
(ab⊗c, x⊗yz)

julia> s*t
-abx⊗cyz
source
LinearCombinations.coprodMethod
coprod(t::T) where T <: AbstractTensor -> Linear{Tensor{Tuple{T,T}}}

Return the coproduct of a tensor, computed from the coproducts of its components. Signs are introduced according to the usual sign rule. If all degrees are integers, then the coefficient type is DefaultCoefftype.

This function is linear.

See also: coprod, LinearCombinations.DefaultCoefftype.

Example

julia> import LinearCombinations: deg, coprod

julia> deg(x::String) = length(x);

julia> coprod(x::String) = Linear(Tensor(x[1:k], x[k+1:end]) => 1 for k in 1:length(x)-1);

julia> coprod("abc")
a⊗bc+ab⊗c

julia> t = Tensor("abc", "xyz")
abc⊗xyz

julia> coprod(t)
-(ab⊗x)⊗(c⊗yz)+(a⊗xy)⊗(bc⊗z)+(ab⊗xy)⊗(c⊗z)+(a⊗x)⊗(bc⊗yz)
source
LinearCombinations.diffMethod
diff(t::T) where T <: AbstractTensor -> Linear{T}

Return the differential of the tensor t by differentiating each tensor factor at a time and adding signs according to the degrees of the components. The coefficient type is usually DefaultCoefftype. However, if the degrees of the tensor components are not integers, then the coefficient type is chosen such that it can accommodate the signs.

See also diff, LinearCombinations.DefaultCoefftype.

Example

As usual, the degree of a string is its length.

julia> import LinearCombinations: deg, diff

julia> deg(x::String) = length(x);

julia> function diff(x::String)
           if isempty(x) || x[1] == 'D'
               zero(Linear1{String,Int})
           else
               Linear1('D'*x => 1)end
       end;

julia> dx = diff("x")
Dx

julia> diff(dx)
0

julia> t = Tensor("a", "bb", "ccc")
a⊗bb⊗ccc

julia> diff(t)
Da⊗bb⊗ccc-a⊗Dbb⊗ccc-a⊗bb⊗Dccc
source