Tensors
LinearCombinations.AbstractTensor — TypeAbstractTensor{T<:Tuple}The supertype of all tensor types. Currently the only subtype is Tensor.
See Tensor, tensor, Tuple(t::AbstractTensor).
Constructors
LinearCombinations.Tensor — TypeTensor{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)
Linear{Char, Int64} with 2 terms:
'x'+2*'y'
julia> b = Linear(Tensor('x', 'z') => 1, Tensor('y', 'z') => 2)
Linear{Tensor{Tuple{Char, Char}}, Int64} with 2 terms:
2*'y'⊗'z'+'x'⊗'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()]
1LinearCombinations.tensor — Functiontensor(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 = Linear('x' => 1, 'y' => 2)
Linear{Char, Int64} with 2 terms:
'x'+2*'y'
julia> b = Linear("w" => 3, "z" => -1)
Linear{String, Int64} with 2 terms:
3*"w"-"z"
julia> tensor(a, "w")
Linear{Tensor{Tuple{Char, String}}, Int64} with 2 terms:
'x'⊗"w"+2*'y'⊗"w"
julia> tensor(a, b)
Linear{Tensor{Tuple{Char, String}}, Int64} with 4 terms:
3*'x'⊗"w"-'x'⊗"z"-2*'y'⊗"z"+6*'y'⊗"w"
julia> tensor('x', b, a; coefftype = Float64)
Linear{Tensor{Tuple{Char, String, Char}}, Float64} with 4 terms:
6.0*'x'⊗"w"⊗'y'+3.0*'x'⊗"w"⊗'x'-2.0*'x'⊗"z"⊗'y'-'x'⊗"z"⊗'x'
julia> a = tensor(); a[Tensor()]
1Manipulating tensors
Core.Tuple — MethodTuple(t::AbstractTensor{T}) -> T <: TupleReturn 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)LinearCombinations.cat — Functioncat(t::AbstractTensor...) -> TensorConcatenate 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')LinearCombinations.flatten — Functionflatten(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'LinearCombinations.swap — Constantswap(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)
Linear1{Tensor{Tuple{String, String}}, Int64} with 1 term:
"z"⊗"x"
julia> a = Linear("x" => 1, "yy" => 1) ⊗ Linear("z" => 1, "ww" => 1)
Linear{Tensor{Tuple{String, String}}, Int64} with 4 terms:
"x"⊗"ww"+"yy"⊗"ww"+"yy"⊗"z"+"x"⊗"z"
julia> swap(a)
Linear{Tensor{Tuple{String, String}}, Int64} with 4 terms:
"ww"⊗"x"+"z"⊗"yy"+"z"⊗"x"+"ww"⊗"yy"
julia> swap(a; coeff = 2)
Linear{Tensor{Tuple{String, String}}, Int64} with 4 terms:
2*"ww"⊗"x"+2*"z"⊗"yy"+2*"z"⊗"x"+2*"ww"⊗"yy"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
Linear1{Tensor{Tuple{String, String}}, Int64} with 1 term:
-"z"⊗"x"
julia> swap(a) # same a as before
Linear{Tensor{Tuple{String, String}}, Int64} with 4 terms:
"ww"⊗"x"+"z"⊗"yy"-"z"⊗"x"+"ww"⊗"yy"LinearCombinations.Regroup — TypeLinearCombinations.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.
LinearCombinations.regroup — Functionregroup(a, b) -> RegroupReturn 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)
Linear1{Tensor{Tuple{Tensor{Tuple{String, String}}, Tensor{Tuple{String, String}}}}, Int64} with 1 term:
("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
Linear1{Tensor{Tuple{Tensor{Tuple{String, String}}, Tensor{Tuple{String, String}}}}, Int64} with 1 term:
-("z"⊗"x")⊗("w"⊗"y")LinearCombinations.regroup_inv — Functionregroup_inv(a, b) -> Tuple{Regroup,Regroup}Return the tuple (regroup(a, b), regroup(b, a)).
See also regroup.
Base.transpose — Functiontranspose(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)
Linear1{Tensor{Tuple{Tensor{Tuple{String, String}}, Tensor{Tuple{String, String}}, Tensor{Tuple{String, String}}}}, Int64} with 1 term:
("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
Linear1{Tensor{Tuple{Tensor{Tuple{String, String}}, Tensor{Tuple{String, String}}, Tensor{Tuple{String, String}}}}, Int64} with 1 term:
-("a"⊗"x")⊗("b"⊗"y")⊗("c"⊗"z")Calling tensors
LinearCombinations.AbstractTensor — Method(tf::AbstractTensor)(tx::AbstractTensor...) -> TensorEvaluating 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 = Linear('x' => 1, 'y' => 2)
Linear{Char, Int64} with 2 terms:
'x'+2*'y'
julia> b = Linear('Z' => -1, 'W' => 3)
Linear{Char, Int64} with 2 terms:
-'Z'+3*'W'
julia> h(Tensor('x', 'Z'))
Linear{Tensor{Tuple{Char, Char}}, Int64} with 1 term:
'X'⊗'z'
julia> h(tensor(a, b))
Linear{Tensor{Tuple{Char, Char}}, Int64} with 4 terms:
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"))
Linear{Tensor{Tuple{String, String}}, Int64} with 1 term:
-"xpp"⊗"yyqqq"
julia> a = Linear("x" => 1, "yy" => 2)
Linear{String, Int64} with 2 terms:
"x"+2*"yy"
julia> b = tensor(a, a)
Linear{Tensor{Tuple{String, String}}, Int64} with 4 terms:
2*"yy"⊗"x"+"x"⊗"x"+4*"yy"⊗"yy"+2*"x"⊗"yy"
julia> j(b)
Linear{Tensor{Tuple{String, String}}, Int64} with 4 terms:
4*"yypp"⊗"yyqqq"+2*"yypp"⊗"xqqq"-2*"xpp"⊗"yyqqq"-"xpp"⊗"xqqq"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'))
Linear{Tensor{Tuple{String, String}}, Int64} with 1 term:
"a#p#x"⊗"b@q@y"Other functions accepting tensors
LinearCombinations.deg — Methoddeg(t::AbstractTensor)Return the degree of a tensor, which is the sum of the degrees of its components.
See also deg.
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")
(Tensor("ab", "c"), Tensor("x", "yz"))
julia> s*t
Linear{Tensor{Tuple{String, String}}, Int64} with 1 term:
-"abx"⊗"cyz"
LinearCombinations.coprod — Methodcoprod(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")
Linear{Tensor{Tuple{String, String}}, Int64} with 2 terms:
"a"⊗"bc"+"ab"⊗"c"
julia> t = Tensor("abc", "xyz")
"abc"⊗"xyz"
julia> coprod(t)
Linear{Tensor{Tuple{Tensor{Tuple{String, String}}, Tensor{Tuple{String, String}}}}, Int64} with 4 terms:
("a"⊗"xy")⊗("bc"⊗"z")-("ab"⊗"x")⊗("c"⊗"yz")+("a"⊗"x")⊗("bc"⊗"yz")+("ab"⊗"xy")⊗("c"⊗"z")LinearCombinations.diff — Methoddiff(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")
Linear1{String, Int64} with 1 term:
"Dx"
julia> diff(dx)
Linear1{String, Int64} with 0 terms:
0
julia> t = Tensor("a", "bb", "ccc")
"a"⊗"bb"⊗"ccc"
julia> diff(t)
Linear{Tensor{Tuple{String, String, String}}, Int64} with 3 terms:
-"a"⊗"bb"⊗"Dccc"-"a"⊗"Dbb"⊗"ccc"+"Da"⊗"bb"⊗"ccc"