Skip to content

Commit 34d74c2

Browse files
committed
update docs structure
1 parent a2d61c8 commit 34d74c2

7 files changed

Lines changed: 247 additions & 242 deletions

File tree

docs/make.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,14 @@ pages = [
2626
"man/intro.md", "man/tutorial.md",
2727
"man/spaces.md", "man/symmetries.md",
2828
"man/sectors.md", "man/gradedspaces.md",
29-
"man/fusiontrees.md", "man/tensors.md",
30-
"man/indexmanipulations.md", "man/tensormanipulations.md",
29+
"man/fusiontrees.md",
30+
"Tensors" => [
31+
"man/tensors.md",
32+
"man/linearalgebra.md",
33+
"man/indexmanipulations.md",
34+
"man/factorizations.md",
35+
"man/contractions.md",
36+
],
3137
],
3238
"Library" => [
3339
"lib/sectors.md", "lib/fusiontrees.md",

docs/src/man/contractions.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# [Tensor contractions and tensor networks](@id ss_tensor_contraction)
2+
3+
One of the most important operation with tensor maps is to compose them, more generally known as contracting them.
4+
As mentioned in the section on [category theory](@ref s_categories), a typical composition of maps in a ribbon category can graphically be represented as a planar arrangement of the morphisms (i.e. tensor maps, boxes with lines eminating from top and bottom, corresponding to source and target, i.e. domain and codomain), where the lines connecting the source and targets of the different morphisms should be thought of as ribbons, that can braid over or underneath each other, and that can twist.
5+
Technically, we can embed this diagram in ``ℝ × [0,1]`` and attach all the unconnected line endings corresponding objects in the source at some position ``(x,0)`` for ``x∈ℝ``, and all line endings corresponding to objects in the target at some position ``(x,1)``.
6+
The resulting morphism is then invariant under what is known as *framed three-dimensional isotopy*, i.e. three-dimensional rearrangements of the morphism that respect the rules of boxes connected by ribbons whose open endings are kept fixed.
7+
Such a two-dimensional diagram cannot easily be encoded in a single line of code.
8+
9+
However, things simplify when the braiding is symmetric (such that over- and under- crossings become equivalent, i.e. just crossings), and when twists, i.e. self-crossings in this case, are trivial.
10+
This amounts to `BraidingStyle(I) == Bosonic()` in the language of TensorKit.jl, and is true for any subcategory of ``\mathbf{Vect}``, i.e. ordinary tensors, possibly with some symmetry constraint.
11+
The case of ``\mathbf{SVect}`` and its subcategories, and more general categories, are discussed below.
12+
13+
In the case of trivial twists, we can deform the diagram such that we first combine every morphism with a number of coevaluations ``η`` so as to represent it as a tensor, i.e. with a trivial domain.
14+
We can then rearrange the morphism to be all ligned up horizontally, where the original morphism compositions are now being performed by evaluations ``ϵ``.
15+
This process will generate a number of crossings and twists, where the latter can be omitted because they act trivially.
16+
Similarly, double crossings can also be omitted.
17+
As a consequence, the diagram, or the morphism it represents, is completely specified by the tensors it is composed of, and which indices between the different tensors are connect, via the evaluation ``ϵ``, and which indices make up the source and target of the resulting morphism.
18+
If we also compose the resulting morphisms with coevaluations so that it has a trivial domain, we just have one type of unconnected lines, henceforth called open indices.
19+
We sketch such a rearrangement in the following picture
20+
21+
```@raw html
22+
<img src="../img/tensor-bosoniccontraction.svg" alt="tensor unitary" class="color-invertible"/>
23+
```
24+
25+
Hence, we can now specify such a tensor diagram, henceforth called a tensor contraction or also tensor network, using a one-dimensional syntax that mimicks [abstract index notation](https://en.wikipedia.org/wiki/Abstract_index_notation) and specifies which indices are connected by the evaluation map using Einstein's summation conventation.
26+
Indeed, for `BraidingStyle(I) == Bosonic()`, such a tensor contraction can take the same format as if all tensors were just multi-dimensional arrays.
27+
For this, we rely on the interface provided by the package [TensorOperations.jl](https://github.com/QuantumKitHub/TensorOperations.jl).
28+
29+
The above picture would be encoded as
30+
```julia
31+
@tensor E[a, b, c, d, e] := A[v, w, d, x] * B[y, z, c, x] * C[v, e, y, b] * D[a, w, z]
32+
```
33+
or
34+
```julia
35+
@tensor E[:] := A[1, 2, -4, 3] * B[4, 5, -3, 3] * C[1, -5, 4, -2] * D[-1, 2, 5]
36+
```
37+
where the latter syntax is known as NCON-style, and labels the unconnected or outgoing indices with negative integers, and the contracted indices with positive integers.
38+
39+
A number of remarks are in order.
40+
TensorOperations.jl accepts both integers and any valid variable name as dummy label for indices, and everything in between `[ ]` is not resolved in the current context but interpreted as a dummy label.
41+
Here, we label the indices of a `TensorMap`, like `A::TensorMap{T, S, N₁, N₂}`, in a linear fashion, where the first position corresponds to the first space in `codomain(A)`, and so forth, up to position `N₁`.
42+
Index `N₁ + 1` then corresponds to the first space in `domain(A)`.
43+
However, because we have applied the coevaluation ``η``, it actually corresponds to the corresponding dual space, in accordance with the interface of [`space(A, i)`](@ref) that we introduced [above](@ref ss_tensor_properties), and as indiated by the dotted box around ``A`` in the above picture.
44+
The same holds for the other tensor maps.
45+
Note that our convention also requires that we braid indices that we brought from the domain to the codomain, and so this is only unambiguous for a symmetric braiding, where there is a unique way to permute the indices.
46+
47+
With the current syntax, we create a new object `E` because we use the definition operator `:=`.
48+
Furthermore, with the current syntax, it will be a `Tensor`, i.e. it will have a trivial domain, and correspond to the dotted box in the picture above, rather than the actual morphism `E`.
49+
We can also directly define `E` with the correct codomain and domain by rather using
50+
```julia
51+
@tensor E[a b c;d e] := A[v, w, d, x] * B[y, z, c, x] * C[v, e, y, b] * D[a, w, z]
52+
```
53+
or
54+
```julia
55+
@tensor E[(a, b, c);(d, e)] := A[v, w, d, x] * B[y, z, c, x] * C[v, e, y, b] * D[a, w, z]
56+
```
57+
where the latter syntax can also be used when the codomain is empty.
58+
When using the assignment operator `=`, the `TensorMap` `E` is assumed to exist and the contents will be written to the currently allocated memory.
59+
Note that for existing tensors, both on the left hand side and right hand side, trying to specify the indices in the domain and the codomain seperately using the above syntax, has no effect, as the bipartition of indices are already fixed by the existing object.
60+
Hence, if `E` has been created by the previous line of code, all of the following lines are now equivalent
61+
```julia
62+
@tensor E[(a, b, c);(d, e)] = A[v, w, d, x] * B[y, z, c, x] * C[v, e, y, b] * D[a, w, z]
63+
@tensor E[a, b, c, d, e] = A[v w d; x] * B[(y, z, c); (x, )] * C[v e y; b] * D[a, w, z]
64+
@tensor E[a b; c d e] = A[v; w d x] * B[y, z, c, x] * C[v, e, y, b] * D[a w; z]
65+
```
66+
and none of those will or can change the partition of the indices of `E` into its codomain and its domain.
67+
68+
Two final remarks are in order.
69+
Firstly, the order of the tensors appearing on the right hand side is irrelevant, as we can reorder them by using the allowed moves of the Penrose graphical calculus, which yields some crossings and a twist.
70+
As the latter is trivial, it can be omitted, and we just use the same rules to evaluate the newly ordered tensor network.
71+
For the particular case of matrix-matrix multiplication, which also captures more general settings by appropriotely combining spaces into a single line, we indeed find
72+
73+
```@raw html
74+
<img src="../img/tensor-contractionreorder.svg" alt="tensor contraction reorder" class="color-invertible"/>
75+
```
76+
77+
or thus, the following two lines of code yield the same result
78+
```julia
79+
@tensor C[i, j] := B[i, k] * A[k, j]
80+
@tensor C[i, j] := A[k, j] * B[i, k]
81+
```
82+
Reordering of tensors can be used internally by the `@tensor` macro to evaluate the contraction in a more efficient manner.
83+
In particular, the NCON-style of specifying the contraction gives the user control over the order, and there are other macros, such as `@tensoropt`, that try to automate this process.
84+
There is also an `@ncon` macro and `ncon` function, an we recommend reading the [manual of TensorOperations.jl](https://quantumkithub.github.io/TensorOperations.jl/stable/) to learn more about the possibilities and how they work.
85+
86+
A final remark involves the use of adjoints of tensors.
87+
The current framework is such that the user should not be too worried about the actual bipartition into codomain and domain of a given `TensorMap` instance.
88+
Indeed, for tensor contractions the `@tensor` macro figures out the correct manipulations automatically.
89+
However, when wanting to use the `adjoint` of an instance `t::TensorMap{T, S, N₁, N₂}`, the resulting `adjoint(t)` is an `AbstractTensorMap{T, S, N₂, N₁}` and one needs to know the values of `N₁` and `N₂` to know exactly where the `i`th index of `t` will end up in `adjoint(t)`, and hence the index order of `t'`.
90+
Within the `@tensor` macro, one can instead use `conj()` on the whole index expression so as to be able to use the original index ordering of `t`.
91+
For example, for `TensorMap{T, S, 1, 1}` instances, this yields exactly the equivalence one expects, namely one between the following two expressions:
92+
93+
```julia
94+
@tensor C[i, j] := B'[i, k] * A[k, j]
95+
@tensor C[i, j] := conj(B[k, i]) * A[k, j]
96+
```
97+
98+
For e.g. an instance `A::TensorMap{T, S, 3, 2}`, the following two syntaxes have the same effect within an `@tensor` expression: `conj(A[a, b, c, d, e])` and `A'[d, e, a, b, c]`.
99+
100+
## Fermionic tensor contractions
101+
102+
TODO
103+
104+
## Anyonic tensor contractions
105+
106+
TODO

docs/src/man/factorizations.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# [Tensor factorizations](@id ss_tensor_factorization)
2+
3+
```@setup tensors
4+
using TensorKit
5+
using LinearAlgebra
6+
```
7+
8+
As tensors are linear maps, they suport various kinds of factorizations.
9+
These functions all interpret the provided `AbstractTensorMap` instances as a map from `domain` to `codomain`, which can be thought of as reshaping the tensor into a matrix according to the current bipartition of the indices.
10+
11+
TensorKit's factorizations are provided by [MatrixAlgebraKit.jl](https://github.com/QuantumKitHub/MatrixAlgebraKit.jl), which is used to supply both the interface, as well as the implementation of the various operations on the blocks of data.
12+
For specific details on the provided functionality, we refer to its [documentation page](https://quantumkithub.github.io/MatrixAlgebraKit.jl/stable/user_interface/decompositions/).
13+
14+
Finally, note that each of the factorizations takes the current partition of `domain` and `codomain` as the *axis* along which to matricize and perform the factorization.
15+
In order to obtain factorizations according to a different bipartition of the indices, we can use any of the previously mentioned [index manipulations](@ref s_indexmanipulations) before the factorization.
16+
17+
Some examples to conclude this section
18+
```@repl tensors
19+
V1 = SU₂Space(0 => 2, 1/2 => 1)
20+
V2 = SU₂Space(0 => 1, 1/2 => 1, 1 => 1)
21+
22+
t = randn(V1 ⊗ V1, V2);
23+
U, S, Vh = svd_compact(t);
24+
t ≈ U * S * Vh
25+
D, V = eigh_full(t' * t);
26+
D ≈ S * S
27+
U' * U ≈ id(domain(U))
28+
S
29+
30+
Q, R = left_orth(t; alg = :svd);
31+
Q' * Q ≈ id(domain(Q))
32+
t ≈ Q * R
33+
34+
U2, S2, Vh2, ε = svd_trunc(t; trunc = truncspace(V1));
35+
Vh2 * Vh2' ≈ id(codomain(Vh2))
36+
S2
37+
ε ≈ norm(block(S, Irrep[SU₂](1))) * sqrt(dim(Irrep[SU₂](1)))
38+
39+
L, Q = right_orth(permute(t, ((1,), (2, 3))));
40+
codomain(L), domain(L), domain(Q)
41+
Q * Q'
42+
P = Q' * Q;
43+
P ≈ P * P
44+
t′ = permute(t, ((1,), (2, 3)));
45+
t′ ≈ t′ * P
46+
```

docs/src/man/indexmanipulations.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ They form a hierarchy from most general to most restricted:
6565

6666
- [`braid`](@ref) is the most general: it accepts any permutation and requires a `levels` argument — a tuple of heights, one per index — that determines whether each index crosses over or under the others it has to pass.
6767
- [`permute`](@ref) is a simpler interface for sector types with a symmetric braiding (`BraidingStyle(I) isa SymmetricBraiding`), where over- and under-crossings are equivalent and `levels` is therefore not needed.
68-
- [`transpose`](@ref) is restricted to *cyclic* permutations (indices do not cross). Unlike `braid`, it introduces a compensating (inverse) twist to satisfy the categorical definition of transpose, as illustrated below:
68+
- [`transpose`](@ref) is restricted to *cyclic* permutations (indices do not cross).
6969
- [`repartition`](@ref) only moves the codomain/domain boundary without reordering the indices at all.
7070

7171
For plain tensors (`sectortype(t) == Trivial`), `permute` and `braid` act like `permutedims` on the underlying array:

docs/src/man/linearalgebra.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# [Basic linear algebra](@id ss_tensor_linalg)
2+
3+
```@setup tensors
4+
using TensorKit
5+
using LinearAlgebra
6+
```
7+
8+
`AbstractTensorMap` instances `t` represent linear maps, i.e. homomorphisms in a `𝕜`-linear category, just like matrices.
9+
To a large extent, they follow the interface of `Matrix` in Julia's `LinearAlgebra` standard library.
10+
Many methods from `LinearAlgebra` are (re)exported by TensorKit.jl, and can then us be used without `using LinearAlgebra` explicitly.
11+
In all of the following methods, the implementation acts directly on the underlying matrix blocks (typically using the same method) and never needs to perform any basis transforms.
12+
13+
In particular, `AbstractTensorMap` instances can be composed, provided the domain of the first object coincides with the codomain of the second.
14+
Composing tensor maps uses the regular multiplication symbol as in `t = t1 * t2`, which is also used for matrix multiplication.
15+
TensorKit.jl also supports (and exports) the mutating method `mul!(t, t1, t2)`.
16+
We can then also try to invert a tensor map using `inv(t)`, though this can only exist if the domain and codomain are isomorphic, which can e.g. be checked as `fuse(codomain(t)) == fuse(domain(t))`.
17+
If the inverse is composed with another tensor `t2`, we can use the syntax `t1 \ t2` or `t2 / t1`.
18+
However, this syntax also accepts instances `t1` whose domain and codomain are not isomorphic, and then amounts to `pinv(t1)`, the Moore-Penrose pseudoinverse.
19+
This, however, is only really justified as minimizing the least squares problem if `InnerProductStyle(t) <: EuclideanProduct`.
20+
21+
`AbstractTensorMap` instances behave themselves as vectors (i.e. they are `𝕜`-linear) and so they can be multiplied by scalars and, if they live in the same space, i.e. have the same domain and codomain, they can be added to each other.
22+
There is also a `zero(t)`, the additive identity, which produces a zero tensor with the same domain and codomain as `t`.
23+
In addition, `TensorMap` supports basic Julia methods such as `fill!` and `copy!`, as well as `copy(t)` to create a copy with independent data.
24+
Aside from basic `+` and `*` operations, TensorKit.jl reexports a number of efficient in-place methods from `LinearAlgebra`, such as `axpy!` (for `y ← α * x + y`), `axpby!` (for `y ← α * x + β * y`), `lmul!` and `rmul!` (for `y ← α * y` and `y ← y * α`, which is typically the same) and `mul!`, which can also be used for out-of-place scalar multiplication `y ← α * x`.
25+
26+
For `S = spacetype(t)` where `InnerProductStyle(S) <: EuclideanProduct`, we can compute `norm(t)`, and for two such instances, the inner product `dot(t1, t2)`, provided `t1` and `t2` have the same domain and codomain.
27+
Furthermore, there is `normalize(t)` and `normalize!(t)` to return a scaled version of `t` with unit norm.
28+
These operations should also exist for `InnerProductStyle(S) <: HasInnerProduct`, but require an interface for defining a custom inner product in these spaces.
29+
Currently, there is no concrete subtype of `HasInnerProduct` that is not an `EuclideanProduct`.
30+
In particular, `CartesianSpace`, `ComplexSpace` and `GradedSpace` all have `InnerProductStyle(S) <: EuclideanProduct`.
31+
32+
With tensors that have `InnerProductStyle(t) <: EuclideanProduct` there is associated an adjoint operation, given by `adjoint(t)` or simply `t'`, such that `domain(t') == codomain(t)` and `codomain(t') == domain(t)`.
33+
Note that for an instance `t::TensorMap{S, N₁, N₂}`, `t'` is simply stored in a wrapper called `AdjointTensorMap{S, N₂, N₁}`, which is another subtype of `AbstractTensorMap`.
34+
This should be mostly invisible to the user, as all methods should work for this type as well.
35+
It can be hard to reason about the index order of `t'`, i.e. index `i` of `t` appears in `t'` at index position `j = TensorKit.adjointtensorindex(t, i)`, where the latter method is typically not necessary and hence unexported.
36+
There is also a plural `TensorKit.adjointtensorindices` to convert multiple indices at once.
37+
Note that, because the adjoint interchanges domain and codomain, we have `space(t', j) == space(t, i)'`.
38+
39+
`AbstractTensorMap` instances can furthermore be tested for exact (`t1 == t2`) or approximate (`t1 ≈ t2`) equality, though the latter requires that `norm` can be computed.
40+
41+
When tensor map instances are endomorphisms, i.e. they have the same domain and codomain, there is a multiplicative identity which can be obtained as `one(t)` or `one!(t)`, where the latter overwrites the contents of `t`.
42+
The multiplicative identity on a space `V` can also be obtained using `id(A, V)` as discussed [above](@ref ss_tensor_construction), such that for a general homomorphism `t′`, we have `t′ == id(codomain(t′)) * t′ == t′ * id(domain(t′))`.
43+
Returning to the case of endomorphisms `t`, we can compute the trace via `tr(t)` and exponentiate them using `exp(t)`, or if the contents of `t` can be destroyed in the process, `exp!(t)`.
44+
Furthermore, there are a number of tensor factorizations for both endomorphisms and general homomorphisms that we discuss on the [Tensor factorizations](@ref ss_tensor_factorization) page.
45+
46+
Finally, there are a number of operations that also belong in this paragraph because of their analogy to common matrix operations.
47+
The tensor product of two `TensorMap` instances `t1` and `t2` is obtained as `t1 ⊗ t2` and results in a new `TensorMap` with `codomain(t1 ⊗ t2) = codomain(t1) ⊗ codomain(t2)` and `domain(t1 ⊗ t2) = domain(t1) ⊗ domain(t2)`.
48+
If we have two `TensorMap{T, S, N, 1}` instances `t1` and `t2` with the same codomain, we can combine them in a way that is analogous to `hcat`, i.e. we stack them such that the new tensor `catdomain(t1, t2)` has also the same codomain, but has a domain which is `domain(t1) ⊕ domain(t2)`.
49+
Similarly, if `t1` and `t2` are of type `TensorMap{T, S, 1, N}` and have the same domain, the operation `catcodomain(t1, t2)` results in a new tensor with the same domain and a codomain given by `codomain(t1) ⊕ codomain(t2)`, which is the analogy of `vcat`.
50+
Note that direct sum only makes sense between `ElementarySpace` objects, i.e. there is no way to give a tensor product meaning to a direct sum of tensor product spaces.
51+
52+
Time for some more examples:
53+
```@repl tensors
54+
using TensorKit # hide
55+
V1 = ℂ^2
56+
t = randn(V1 ← V1 ⊗ V1 ⊗ V1)
57+
t == t + zero(t) == t * id(domain(t)) == id(codomain(t)) * t
58+
t2 = randn(ComplexF64, codomain(t), domain(t));
59+
dot(t2, t)
60+
tr(t2' * t)
61+
dot(t2, t) ≈ dot(t', t2')
62+
dot(t2, t2)
63+
norm(t2)^2
64+
t3 = copy!(similar(t, ComplexF64), t);
65+
t3 == t
66+
rmul!(t3, 0.8);
67+
t3 ≈ 0.8 * t
68+
axpby!(0.5, t2, 1.3im, t3);
69+
t3 ≈ 0.5 * t2 + 0.8 * 1.3im * t
70+
t4 = randn(fuse(codomain(t)), codomain(t));
71+
t5 = TensorMap{Float64}(undef, fuse(codomain(t)), domain(t));
72+
mul!(t5, t4, t) == t4 * t
73+
inv(t4) * t4 ≈ id(codomain(t))
74+
t4 * inv(t4) ≈ id(fuse(codomain(t)))
75+
t4 \ (t4 * t) ≈ t
76+
t6 = randn(ComplexF64, V1, codomain(t));
77+
numout(t4) == numout(t6) == 1
78+
t7 = catcodomain(t4, t6);
79+
foreach(println, (codomain(t4), codomain(t6), codomain(t7)))
80+
norm(t7) ≈ sqrt(norm(t4)^2 + norm(t6)^2)
81+
t8 = t4 ⊗ t6;
82+
foreach(println, (codomain(t4), codomain(t6), codomain(t8)))
83+
foreach(println, (domain(t4), domain(t6), domain(t8)))
84+
norm(t8) ≈ norm(t4)*norm(t6)
85+
```

0 commit comments

Comments
 (0)