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)
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
LinearCombinations.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, 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
Manipulating tensors
Core.Tuple
— MethodTuple(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)
LinearCombinations.cat
— Functioncat(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)
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)
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
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) -> 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)
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)
(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)
Calling tensors
LinearCombinations.AbstractTensor
— Method(tf::AbstractTensor)(tx::AbstractTensor...) -> Tensor
Evaluating an AbstractTensor
on other AbstractTensor
s (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
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")
(ab⊗c, x⊗yz)
julia> s*t
-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")
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)
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")
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