7.9. Manipulating a UniTensor¶
After having introduced the initialization and structure of the three UniTensor types (un-tagged, tagged, and tagged with symmetries), we show basic functionalities to manipulate UniTensors.
Permutation, reshaping and arithmetic operations are accessed similarly to Tensor objects as introduced before, with slight modifications for symmetric UniTensors.
7.9.1. Permute¶
The bond order can be changed with permute for all UniTensor types. The order can either be defined by the index order as for the permute method of a Tensor, or by specifying the label order after the permutation.
For example, we permute the indices of the symmetric tensor that we introduced before:
In Python:
1bond_d = cytnx.Bond(
2 cytnx.BD_IN,
3 [cytnx.Qs(1)>>1, cytnx.Qs(-1)>>1],
4 [cytnx.Symmetry.U1()])
5
6bond_e = cytnx.Bond(
7 cytnx.BD_IN,
8 [cytnx.Qs(1)>>1, cytnx.Qs(-1)>>1],
9 [cytnx.Symmetry.U1()])
10
11bond_f = cytnx.Bond(
12 cytnx.BD_OUT,
13 [cytnx.Qs(2)>>1, cytnx.Qs(0)>>2,
14 cytnx.Qs(-2)>>1],
15 [cytnx.Symmetry.U1()])
16
17Tsymm = cytnx.UniTensor(
18 [bond_d, bond_e, bond_f],
19 name="symm. tensor", labels=["d","e","f"])
20
21Tsymm.print_diagram()
22
23Tsymm_perm_ind=Tsymm.permute([2,0,1])
24Tsymm_perm_ind.print_diagram()
25
26Tsymm_perm_label=Tsymm.permute(["f","d","e"])
27Tsymm_perm_label.print_diagram()
Output >>
-----------------------
tensor Name : symm. tensor
tensor Rank : 3
contiguous : True
valid blocks : 4
is diag : False
on device : cytnx device: CPU
row col
-----------
| |
d -->| 2 4 |--> f
| |
e -->| 2 |
| |
-----------
-----------------------
tensor Name : symm. tensor
tensor Rank : 3
contiguous : False
valid blocks : 4
is diag : False
on device : cytnx device: CPU
row col
-----------
| |
f *<--| 4 2 |<--* e
| |
d -->| 2 |
| |
-----------
-----------------------
tensor Name : symm. tensor
tensor Rank : 3
contiguous : False
valid blocks : 4
is diag : False
on device : cytnx device: CPU
row col
-----------
| |
f *<--| 4 2 |<--* e
| |
d -->| 2 |
| |
-----------
We did the same permutation in two ways in this example, once using indices, once using labels. The first index of the permuted tensor corresponds to the last index of the original tensor (original index 2, label “f”), the second new index to the first old index (old index 0, label “d”) and the last new bond has the old index 1 and label “e”.
7.9.2. Reshape¶
Untagged UniTensors can be reshaped just like normal Tensors.
In Python:
1T = cytnx.UniTensor.arange(12).reshape(4,3) \
2 .relabel(["a", "b"]).set_name("T")
3T.reshape_(2,3,2)
4T.print_diagram()
Output >>
-----------------------
tensor Name : T
tensor Rank : 3
block_form : False
is_diag : False
on device : cytnx device: CPU
--------
/ \
| 2 |____ 0
| |
| 3 |____ 1
| |
| 2 |____ 2
\ /
--------
Note
A tagged UniTensor can not be reshaped. This includes symmetric UniTensors as well.
7.9.3. Combine bonds¶
Tagged UniTensors, including symmetric UniTensors, cannot be reshaped. The reason for this is that the bonds to be combined or split include the direction and quantum number information. A reshape is therefore replaced by the fusion or splitting of indices, which takes into account the transformation of the quantum numbers for the given symmetries. For this, Cytnx provides the combineBonds API for tagged UniTensors as an alternative to the usual reshape. Note that there is currently no API for splitting bonds, since the way to split the quantum basis is ambiguous. The method to combine bonds can be used as follows:
- UniTensor.combineBonds(indicators, force)¶
- Parameters:
indicators (list) – A list of integer indicating the indices of bonds to be combined. If a list of string is passed, the bonds with those string labels will be combined.
force (bool) – If set to True the bonds will be combined regardless the direction or type of the bonds, otherwise the bond types will be checked. The default is False.
The use of combineBonds is demonstrated in the following example:
In Python:
1bd1 = cytnx.Bond(cytnx.BD_IN,[[1],[-1]],[1,1])
2bd2 = cytnx.Bond(cytnx.BD_IN,[[1],[-1]],[1,1])
3bd3 = cytnx.Bond(cytnx.BD_OUT,[[2],[0],[0],[-2]],[1,1,1,1])
4
5ut = cytnx.UniTensor([bd1,bd2,bd3],rowrank=2) \
6 .relabel(["a", "b", "c"]).set_name("uT")
7
8print(ut)
9ut.combineBonds([0,1], True)
10print(ut)
Output >>
-------- start of print ---------
Tensor name: uT
Tensor type: Block
braket_form : True
is_diag : False
[OVERALL] contiguous : True
========================
BLOCK [#0]
|- [] : Qn index
|- Sym(): Qnum of correspond symmetry
-----------
| |
[0] U1(1) -->| 1 1 |--> [0] U1(2)
| |
[0] U1(1) -->| 1 |
| |
-----------
Total elem: 1
type : Double (Float64)
cytnx device: CPU
Shape : (1,1,1)
[[[0.00000e+00 ]]]
========================
BLOCK [#1]
|- [] : Qn index
|- Sym(): Qnum of correspond symmetry
-----------
| |
[0] U1(1) -->| 1 1 |--> [1] U1(0)
| |
[1] U1(-1) -->| 1 |
| |
-----------
Total elem: 1
type : Double (Float64)
cytnx device: CPU
Shape : (1,1,1)
[[[0.00000e+00 ]]]
========================
BLOCK [#2]
|- [] : Qn index
|- Sym(): Qnum of correspond symmetry
-----------
| |
[0] U1(1) -->| 1 1 |--> [2] U1(0)
| |
[1] U1(-1) -->| 1 |
| |
-----------
Total elem: 1
type : Double (Float64)
cytnx device: CPU
Shape : (1,1,1)
[[[0.00000e+00 ]]]
========================
BLOCK [#3]
|- [] : Qn index
|- Sym(): Qnum of correspond symmetry
-----------
| |
[1] U1(-1) -->| 1 1 |--> [1] U1(0)
| |
[0] U1(1) -->| 1 |
| |
-----------
Total elem: 1
type : Double (Float64)
cytnx device: CPU
Shape : (1,1,1)
[[[0.00000e+00 ]]]
========================
BLOCK [#4]
|- [] : Qn index
|- Sym(): Qnum of correspond symmetry
-----------
| |
[1] U1(-1) -->| 1 1 |--> [2] U1(0)
| |
[0] U1(1) -->| 1 |
| |
-----------
Total elem: 1
type : Double (Float64)
cytnx device: CPU
Shape : (1,1,1)
[[[0.00000e+00 ]]]
========================
BLOCK [#5]
|- [] : Qn index
|- Sym(): Qnum of correspond symmetry
-----------
| |
[1] U1(-1) -->| 1 1 |--> [3] U1(-2)
| |
[1] U1(-1) -->| 1 |
| |
-----------
Total elem: 1
type : Double (Float64)
cytnx device: CPU
Shape : (1,1,1)
[[[0.00000e+00 ]]]
-------- start of print ---------
Tensor name: uT
Tensor type: Block
braket_form : False
is_diag : False
[OVERALL] contiguous : True
========================
BLOCK [#0]
|- [] : Qn index
|- Sym(): Qnum of correspond symmetry
----------
| |
[2] U1(2) -->| 1 |
| |
[2] U1(2) *<--| 1 |
| |
----------
Total elem: 1
type : Double (Float64)
cytnx device: CPU
Shape : (1,1)
[[0.00000e+00 ]]
========================
BLOCK [#1]
|- [] : Qn index
|- Sym(): Qnum of correspond symmetry
----------
| |
[1] U1(0) -->| 2 |
| |
[1] U1(0) *<--| 2 |
| |
----------
Total elem: 4
type : Double (Float64)
cytnx device: CPU
Shape : (2,2)
[[0.00000e+00 0.00000e+00 ]
[0.00000e+00 0.00000e+00 ]]
========================
BLOCK [#2]
|- [] : Qn index
|- Sym(): Qnum of correspond symmetry
----------
| |
[0] U1(-2) -->| 1 |
| |
[0] U1(-2) *<--| 1 |
| |
----------
Total elem: 1
type : Double (Float64)
cytnx device: CPU
Shape : (1,1)
[[0.00000e+00 ]]
7.9.4. Arithmetic¶
Arithmetic operations for un-tagged UniTensors can be done in the exact same way as for Tensors, see Tensor arithmetic. The supported arithmetic operations and further linear algebra functions are listed in Linear algebra.
7.9.5. Rowrank¶
Another property of a UniTensor that we need to control is its rowrank. It defines how the legs of the a UniTensor are split into two groups, one part belonging to the rowspace (left indices) and the other to the column space (right indices). A UniTensor can then be seen as a linear operator between these two spaces, or as a matrix. The matrix form corresponds to combining the first rowrank indices to a single (row-)index and the remaining indices to a second (column-)index.
Most of the linear algebra algorithms assume this matrix form as an input. We thus use rowrank to specify how to interpret the input UniTensor as a matrix. This specification makes it easy to use linear algebra operations on UniTensors, even if they have more than two indices.
The rowrank can either be specified when initializing the UniTensor, or the .set_rowrank() method can be used to modify the rowrank of a UniTensor:
In Python:
1# set the rowrank to be 2.
2uT = cytnx.UniTensor.ones([5,5,5,5,5]).set_rowrank(2) \
3 .relabel(["a", "b", "c", "d", "e"]) \
4 .set_name("uT")
5uT.print_diagram()
6
7uT.set_rowrank_(3) # modify the rowrank.
8uT.print_diagram()
Output >>
-----------------------
tensor Name : uT
tensor Rank : 5
block_form : False
is_diag : False
on device : cytnx device: CPU
---------
/ \
a ____| 5 5 |____ c
| |
b ____| 5 5 |____ d
| |
| 5 |____ e
\ /
---------
-----------------------
tensor Name : uT
tensor Rank : 5
block_form : False
is_diag : False
on device : cytnx device: CPU
---------
/ \
a ____| 5 5 |____ d
| |
b ____| 5 5 |____ e
| |
c ____| 5 |
\ /
---------
How linear algebra functions such as the singular value decomposition (SVD) make use of the rowrank is described in chapter Tensor decomposition.
7.9.6. Transpose¶
A UniTensor can be transposed by the method .Transpose() (or the in-placed method .Transpose_()). We first show the behavior for a non-tagged UniTensor:
In Python:
1T = cytnx.UniTensor.ones([5,5,5]).relabel(["a","b","c"]) \
2 .set_rowrank(2).set_name("T")
3T.print_diagram()
4print("Rowrank of T = ", T.rowrank())
5T.Transpose().print_diagram() # print the transposed T
6print("Rowrank of transposed T = ", T.Transpose().rowrank())
Output >>
-----------------------
tensor Name : T
tensor Rank : 3
block_form : False
is_diag : False
on device : cytnx device: CPU
---------
/ \
a ____| 5 5 |____ c
| |
b ____| 5 |
\ /
---------
Rowrank of T = 2
-----------------------
tensor Name : T
tensor Rank : 3
block_form : False
is_diag : False
on device : cytnx device: CPU
---------
/ \
c ____| 5 5 |____ b
| |
| 5 |____ a
\ /
---------
Rowrank of transposed T = 1
We see that .Transpose() inverts the index order and modifies the rowrank, such that the legs in the row space and the column space are swapped.
Next we consider the transposition of a tagged UniTensor:
In Python:
1bd1 = cytnx.Bond(cytnx.BD_IN,[[1],[-1]],[1,1])
2bd2 = cytnx.Bond(cytnx.BD_OUT,[[1],[-1]],[1,1])
3bd3 = cytnx.Bond(cytnx.BD_OUT,[[2],[0],[0],[-2]],[1,1,1,1])
4uT = cytnx.UniTensor([bd1,bd2,bd3]).set_rowrank(2) \
5 .relabel(["a","b","c"]).set_name("uT")
6uT.print_diagram()
7print("Rowrank of T = ", uT.rowrank())
8uTt = uT.Transpose().set_name("uT transposed")
9uTt.print_diagram() # print the transposed uT
10print("Rowrank of transposed T = ", uTt.rowrank())
Output >>
-----------------------
tensor Name : uT
tensor Rank : 3
contiguous : True
valid blocks: 6
is diag : False
on device : cytnx device: CPU
row col
-----------
| |
a -->| 2 4 |--> c
| |
b *<--| 2 |
| |
-----------
Rowrank of T = 2
-----------------------
tensor Name : uT transposed
tensor Rank : 3
contiguous : False
valid blocks: 6
is diag : False
on device : cytnx device: CPU
row col
-----------
| |
c -->| 4 2 |<--* b
| |
| 2 |--> a
| |
-----------
Rowrank of transposed T = 1
In addition to exchanging the roles of row- and column-space as before, the direction of each bond is inverted: incoming indices become outgoing indices and vice versa.
Note
The method Transpose_() works similarly, but changes the UniTensor directly instead of generating a new UniTensor.
7.9.7. Dagger¶
The methods .Dagger() and .Dagger_() correspond to the conjugate-transpose of a tensor, similar to applying .Conj() and .Transpose(). See the previous section Transpose for the behavior.