7.2. Creating a UniTensor

As mentioned in the introduction, a UniTensor consists of Block(s), Bond(s) and label(s). The Block(s) contain the data, while Bond(s) and label(s) are the meta data that describe the properties of the UniTensor.

../../_images/utcomp.png

Generally, there are two types of UniTensor types: un-tagged and tagged, depending on whether the Bonds have a direction. In more advanced applications, a UniTensor may have block diagonal or other more complicated structure when symmetries are involved. Therefore, UniTensors can further be categorized into non-symmetric and symmetric (block form):

non-symmetric

symmetric (block-diagonal)

tagged

O

O

untagged

O

X

In the following, we will explain how to construct a UniTensor.

7.2.1. Using generators

Similar to the initialization of a Tensor, one can create a UniTensor through generators such as zero, ones, normal, uniform, arange and eye. The first argument provides shape information, which is used to construct the Bond objects and to determine the rank – the number of tensor indices. Labels can be specified when creating a UniTensor, otherwise they are set to be “0”, “1”, “2”, … by default.

This gives us the first type of a UniTensor: an untagged UniTensor.

  • In Python:

 1# Create a rank-3 UniTensor with shape [2,3,4]
 2# 1) initialized by zeros, using method chaining (preferred)
 3uT0 = cytnx.UniTensor.zeros([2,3,4]).relabel(["a","b","c"]).set_name("all zeros")
 4
 5# 2) initialized by ones, using initializer arguments
 6uT1 = cytnx.UniTensor.ones([2,3,4], ["a","b","c"], name="all ones")
 7
 8# 3) initialize by normally distributed elements with mean value 0 and standard deviation 1
 9uTg = cytnx.UniTensor.normal([2,3,4],0,1).relabel(["a","b","c"]).set_name("Gaussian")
10
11# 4) initialize by uniformly distributed elements between 0 and 100
12uTu = cytnx.UniTensor.uniform([2,3,4],0,100).relabel(["a","b","c"]).set_name("random uniform")
13
14# 5) initialize with subsequent numbers from 0 to 2*3*4 - 1 = 23
15uTarr0 = cytnx.UniTensor.arange(2*3*4).reshape(2,3,4).set_rowrank(1) \
16                        .relabel(["a","b","c"]).set_name("range 0, 1, ..., 23")
17
18# 6) initialize with subsequent numbers from starting from 5 to 53 with a stepwidth of 2
19uTarr5 = cytnx.UniTensor.arange(5, 53, 2).reshape(2,3,4).set_rowrank(1) \
20                        .relabel(["a","b","c"]).set_name("range 5, 7, ...., 53")
21
22# Create a 4x4 identity matrix
23uTe = cytnx.UniTensor.eye(4, ["left","right"], name="identity")

Note

The generator eye expects the number of diagonal elements as a first argument instead of the shape of the resulting UniTensor.

Note

The generator arange creates a one-dimensional UniTensor. In order to obtain a desired shape, use reshape and set_rowrank (see Reshape and Rowrank). If arange receives one argument, it is the number of elements. If three arguments are given, these correspond to start, stop, and stepsize. This syntax is similar to numpy.arange().

We can use print_diagram() to visualize a UniTensors in a more straightforward way as a diagram:

  • In Python:

1uT0.print_diagram()
2uTe.print_diagram()

Output >>

-----------------------
tensor Name : all zeros
tensor Rank : 3
block_form  : False
is_diag     : False
on device   : cytnx device: CPU
          ---------
         /         \
   a ____| 2     3 |____ b
         |         |
         |       4 |____ c
         \         /
          ---------
-----------------------
tensor Name : identity
tensor Rank : 2
block_form  : False
is_diag     : False
on device   : cytnx device: CPU
             ---------
            /         \
   left ____| 4     4 |____ right
            \         /
             ---------

The information provided by this output is explained in detail in print_diagram().

7.2.2. Constructing from Tensor

We can also convert a cytnx.Tensor into a UniTensor to create an untagged UniTensor.

In the following, we consider a simple rank-3 tensor as an example. The tensor diagram looks like:

../../_images/untag.png

We can convert such a Tensor to a UniTensor:

  • In Python:

1# create a rank-3 tensor with shape [2,3,4]
2T = cytnx.arange(2*3*4).reshape(2,3,4)
3# convert to UniTensor:
4uT = cytnx.UniTensor(T)

Here, the Tensor T is converted to a UniTensor uT simply by wrapping it with constructor cytnx.UniTensor(). Formally, we can think of this as constructing a UniTensor uT with T being its block (data). If we want to create a UniTensor with different dtype, for example, a complex UniTensor, we can do:

  • In Python:

1# initialize a UniTensor with complex data type
2uT = cytnx.UniTensor.zeros([2,3,4], dtype=cytnx.Type.ComplexDouble) \
3          .set_name("uT").relabel(["a", "b", "c"])
4# randomize the elements with a uniform distribution in the range [low, high]
5cytnx.random.uniform_(uT, low = -1., high = 1.)
6# visualize UniTensor
7uT.print_diagram()

Output >>

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

We see that a UniTensor with the same shape as T was created. The bond labels are set to the default values “0”, “1” and “2”.

7.2.3. From scratch

Next, let’s introduce the complete API for constructing a UniTensor from scratch:

UniTensor(bonds, labels, rowrank, dtype, device, is_diag)
Parameters:
  • bonds (List[cytnx.Bond]) – list of bonds

  • labels (List[string]) – list of labels associate to each bond

  • rowrank (int) – rowrank used when flattened into a matrix

  • dtype (cytnx.Type) – the dtype of the block(s)

  • device (cytnx.Device) – the device where the block(s) are held

  • is_diag (bool) – whether the UniTensor is diagonal

The first argument bonds is a list of Bond objects. These correspond to the shape of a cytnx.Tensor where the elements in shape indicate the dimensions of the bonds. Here, each bond is represent by a cytnx.Bond object. In general, cytnx.Bond contains three things:

  1. The dimension of the bond.

  2. The direction of the bond (it can be BD_REG–undirectional, BD_KET (BD_IN)–inward, BD_BRA (BD_OUT)–outward)

  3. The symmetry and the associate quantum numbers.

For more details, see Bond. Here, for simplicity, we will use only the dimension property of a Bond.

Now let’s construct the rank-3 UniTensor with the same shape as in the above example. We assign the three bonds with labels (“a”, “b”, “c”) and also set name to be “uT2 scratch”.

../../_images/ut2.png
  • In Python:

1uT2 = cytnx.UniTensor([cytnx.Bond(2),cytnx.Bond(3),cytnx.Bond(4)],labels=["a","b","c"],rowrank=1)
2uT2.set_name("uT2 scratch")
3uT2.print_diagram()
4print(uT2)

Output >>

-----------------------
tensor Name : uT2 scratch
tensor Rank : 3
block_form  : False
is_diag     : False
on device   : cytnx device: CPU
          ---------
         /         \
   a ____| 2     3 |____ b
         |         |
         |       4 |____ c
         \         /
          ---------
-------- start of print ---------
Tensor name: uT2 scratch
is_diag    : False
contiguous : True

Total elem: 24
type  : Double (Float64)
cytnx device: CPU
Shape : (2,3,4)
[[[0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 ]
  [0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 ]
  [0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 ]]
 [[0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 ]
  [0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 ]
  [0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 ]]]

Note

The UniTensor will have all the elements in the block initialized with zeros.

7.2.4. Type conversion

It is possible to convert a UniTensor to a different data type. To convert the data type, simply use UniTensor.astype().

For example, consider a UniTensor A with dtype=Type.Int64, which shall be converted to Type.Double:

  • In Python:

1A = cytnx.UniTensor.ones([3,4],dtype=cytnx.Type.Int64)
2B = A.astype(cytnx.Type.Double)
3print(A.dtype_str())
4print(B.dtype_str())

Output >>

Int64
Double (Float64)

Note

UniTensor.dtype() returns a type-id, while UniTensor.dtype_str() returns the type name.

7.2.5. Transfer between devices

Moving a UniTensor between different devices is very easy. We can use UniTensor.to() to move the UniTensor to a different device.

For example, let’s create a UniTensor in the memory accessible by the CPU and transfer it to the GPU with gpu-id=0.

  • In Python:

1A = cytnx.UniTensor.ones([2,2]) #on CPU
2B = A.to(cytnx.Device.cuda+0)
3print(A) # on CPU
4print(B) # on GPU
5
6A.to_(cytnx.Device.cuda)
7print(A) # on GPU

Output >>

Note

  1. You can use UniTensor.device() to get the current device-id (cpu = -1), whereas UniTensor.device_str() returns the device name.

  2. UniTensor.to() will return a copy on the target device. If you want to move the current Tensor to another device, use UniTensor.to_() (with underscore).

7.2.6. Tagged UniTensors and UniTensors with Symmetries

The creation of tagged, non-symmetric UniTensors will be explained in Tagged UniTensor. Symmetric UniTensors are discussed in UniTensor with Symmetries.