7.9. Manipulate UniTensor

After having introduced the initialization and structure of the three UniTensor types (un-tagged, tagged and tagged with symmetries), we show the 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 kinds of UniTensors. 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(cytnx.arange(12).reshape(4,3))
2T.reshape_(2,3,2)
3T.print_diagram()

Output >>

-----------------------
tensor Name :
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

The tagged UniTensors include symmetric UniTensors cannot be reshaped, since the bonds to be combinded or splited now includes the direction and quantum number infomation, the reshape process involves the fusion or split of the qunatum basis, we provide combindBonds API for the tagged UniTensor as an alternative to the usual reshape function. Note that currently there is no API for splitting a bond, since the way to split the quantum basis will be ambiguous. Let’s see the complete function usage for combining bonds:

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.

Consider a specific example:

  • In Python:

 1from cytnx import Bond, BD_IN, BD_OUT, Qs, Symmetry
 2# bond1 = Bond(BD_IN,[[2,0], [4,1]],[3,5],[Symmetry.U1(), Symmetry.Zn(2)])
 3# bond2 = Bond(BD_IN,[Qs(2,0)>>3, Qs(4,1)>>5],[Symmetry.U1(), Symmetry.Zn(2)])
 4bd1 = cytnx.Bond(cytnx.BD_IN,[[1],[-1]],[1,1])
 5bd2 = cytnx.Bond(cytnx.BD_IN,[[1],[-1]],[1,1])
 6bd3 = cytnx.Bond(cytnx.BD_OUT,[[2],[0],[0],[-2]],[1,1,1,1])
 7
 8ut = cytnx.UniTensor([bd1,bd2,bd3],rowrank=2)
 9print(ut)
10
11ut.combineBonds([0,1])
12print(ut)

Output >>

-------- start of print ---------
Tensor name:
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:
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 exactly the same as with Tensors, see Tensor arithmetic. The supported arithmetic operations and further linear algebra functions are listed in Linear algebra.

7.9.5. Rowrank

Another property that we may want to maintain in UniTensor is its rowrank. It tells us how the legs of the a UniTensor are split into two halves, one part belongs to the rowspace and the other to the column space. A UniTensor can then be seen as a linear operator between these two spaces, or as a matrix. The matrix results in having the first rowrank indices combined to the first (row-)index and the other indices combined to the second (column-)index.

Most of the linear algebra algorithms take a matrix as an input. We thus use rowrank to specify how to cast the input UniTensor into a matrix. In Cytnx, this specification makes it easy to use linear algebra operations on UniTensors.

The rowrank can be specified when initializing the UniTenosr, one can also use .set_rowrank() to modify the rowrank of a UniTensor:

  • In Python:

1# set the rowrank to be 2.
2T = cytnx.UniTensor(cytnx.ones([5,5,5,5,5]), rowrank = 2)
3T.print_diagram()
4
5T.set_rowrank(3)  # modify the rowrank.
6T.print_diagram()

Output >>

-----------------------
tensor Name :
tensor Rank : 5
block_form  : False
is_diag     : False
on device   : cytnx device: CPU
          ---------
         /         \
   0 ____| 5     5 |____ 2
         |         |
   1 ____| 5     5 |____ 3
         |         |
         |       5 |____ 4
         \         /
          ---------
-----------------------
tensor Name :
tensor Rank : 5
block_form  : False
is_diag     : False
on device   : cytnx device: CPU
          ---------
         /         \
   0 ____| 5     5 |____ 2
         |         |
   1 ____| 5     5 |____ 3
         |         |
         |       5 |____ 4
         \         /
          ---------

We leave the examples of linalg algebra operations incoporating the rowrank concept such as singular value decomposition (SVD) to the chapter linalg extension.

7.9.6. Transpose

One common operation that is sensitive to the rowrank of a UniTensor is the tranpose, one can transpose a UniTensor using .Transpose() (or the in-placed method .Transpose_()), let’s see the behavior of this operation, first consider the transpose of a non-tagged UniTensor:

  • In Python:

1T = cytnx.UniTensor(cytnx.ones([5,5,5]), rowrank = 2, labels = ["a","b","c"])
2T.print_diagram()
3print("Rowrank of T = ", T.rowrank())
4T.Transpose().print_diagram()  # print the transposed T
5print("Rowrank of transposed T = ", T.Transpose().rowrank())

Output >>

-----------------------
tensor Name :
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 :
tensor Rank : 3
block_form  : False
is_diag     : False
on device   : cytnx device: CPU
          ---------
         /         \
   c ____| 5     5 |____ a
         |         |
         |       5 |____ b
         \         /
          ---------
Rowrank of transposed T =  1

We see that .Transpose() swap the legs in the row space and column space, also the rowrank itself is modified.

Next we consider the tranposition of a tagged UniTensor:

  • 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
5T = cytnx.UniTensor([bd1,bd2,bd3], rowrank = 2, labels = ["a","b","c"])
6T.print_diagram()
7print("Rowrank of T = ", T.rowrank())
8T.Transpose().print_diagram()  # print the transposed T
9print("Rowrank of transposed T = ", T.Transpose().rowrank())

Output >>

-----------------------
tensor Name :
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 :
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 transposed T =  2

We see that for the tagged UniTensor the rowrank (and the row/column space the legs belong to) is not changed, instead the .Transpose() inverted the direction of each bond.

Note

The operation .Dagger() (which is the transposition plus a conjugation) shows same behavior as transpose discussed above.