Metadata-Version: 2.1
Name: PyTAT
Version: 0.3.6
Summary: python binding for TAT(TAT is A Tensor library)
Home-page: https://github.com/USTC-TNS/TAT/tree/TAT/PyTAT
Author: Hao Zhang
Author-email: zh970205@mail.ustc.edu.cn
License: GPLv3
Description-Content-Type: text/markdown
Requires-Dist: numpy



# PyTAT

PyTAT is python binding for [TAT](https://github.com/USTC-TNS/TAT), which is a c++ tensor library with
support for symmetry and fermion tensor.


## Install

You can build by yourself, see [this](https://github.com/USTC-TNS/TAT/blob/TAT/README.org) for details. Or use `pip` to obtain
built distribution: `pip install PyTAT`.


## Documents


### Create tensor

To create a no symmetry tensor, pass names and dimension for each dimension of the tensor

    from TAT.No.D import Tensor
    
    A = Tensor(["i", "j"], [3, 4]).zero()
    print(A)

    {names:[i,j],edges:[3,4],blocks:[0,0,0,0,0,0,0,0,0,0,0,0]}

the code above create a rank-2 tensor named `A` which two edges are `i` and `j`,
and their dimensions are `3` and `4`, then print tensor `A` to `std::cout`.

Please notice that TAT will NOT initialize content of tensor when create it
so remember to clear the value of tensor by calling method `zero`.

To create a Z2 symmetry tensor, you need to describe the detail of each edge

    from TAT.Z2.D import Tensor
    
    A = Tensor(["i", "j"], [[(False, 2), (True, 4)], [(False, 3), (True, 1)]]).range()
    print(A)

    {names:[i,j],edges:[{0:2,1:4},{0:3,1:1}],blocks:{[0,0]:[0,1,2,3,4,5],[1,1]:[6,7,8,9]}}

It means this symmetric tensor have two block, one's symmetries is 0, 0 and the other's is 1, 1.
`range` is a function to initialize the value of tensor for test.

    from TAT.Z2.D import Tensor
    
    A = Tensor(["i", "j"], [[(False, 2), (True, 4)], [(False, 3), (True, 1)]]).range()
    B = A.clear_symmetry()
    print(B)

    {names:[i,j],edges:[6,4],blocks:[0,1,2,0,3,4,5,0,0,0,0,6,0,0,0,7,0,0,0,8,0,0,0,9]}

You can clear the symmetry and convert a symmetric tensor to a normal no symmetry tensor by method `clear_symmetry`.

the U1 symmety edge can be more complex

    from TAT.U1.D import Tensor
    
    A = Tensor(["i", "j"], [[(0, 2), (2, 4), (1, 1)], [(0, 3), (-2, 1), (-1, 3)]]).range()
    B = A.clear_symmetry()
    print(A)
    print(B)

    {names:[i,j],edges:[{0:2,2:4,1:1},{0:3,-2:1,-1:3}],blocks:{[0,0]:[0,1,2,3,4,5],[2,-2]:[6,7,8,9],[1,-1]:[10,11,12]}}
    {names:[i,j],edges:[7,7],blocks:[0,1,2,0,0,0,0,3,4,5,0,0,0,0,0,0,0,6,0,0,0,0,0,0,7,0,0,0,0,0,0,8,0,0,0,0,0,0,9,0,0,0,0,0,0,0,10,11,12]}

Please notice that the order of symmetry segment is important.


### Access element of tensor

You can easily access elements of tensor by a map from name of edge to index

    from TAT.No.D import Tensor
    # Create a tensor and initialize it to zero
    A = Tensor(["i", "j"], [3, 4]).zero()
    # Set an element of tensor A to 3
    A[{"i": 2, "j": 2}] = 3
    # print tensor A
    print(A)
    # print the element set as 3
    print(A[{"i": 2, "j": 2}])

    {names:[i,j],edges:[3,4],blocks:[0,0,0,0,0,0,0,0,0,0,3,0]}
    3.0

For symmetric tensor, you can specify the pair of symmetry and sub-index or the total index.

    from TAT.U1.D import Tensor
    
    A = Tensor(["i", "j"], [[(0, 2), (2, 4), (1, 1)], [(0, 3), (-2, 1), (-1, 3)]]).zero()
    A[{"i": 1, "j": 2}] = 233
    A[{"i": (2, 2), "j": (-2, 0)}] = 43
    # print tensor A
    print(A)
    # print the element set
    print(A[{"i": (0, 1), "j": (0, 2)}])
    print(A[{"j": 3, "i": 4}])
    B = A.clear_symmetry()
    print(B[{"j": 3, "i": 4}])

    {names:[i,j],edges:[{0:2,2:4,1:1},{0:3,-2:1,-1:3}],blocks:{[0,0]:[0,0,0,0,0,233],[2,-2]:[0,0,43,0],[1,-1]:[0,0,0]}}
    233.0
    43.0
    43.0


### Scalar operators

You can do scalar operators directly

    from TAT.No.D import Tensor
    # Create two rank-1 tensors
    A = Tensor(["i"], [4])
    B = Tensor(["i"], [4])
    A[{"i": 0}] = 1
    A[{"i": 1}] = 2
    A[{"i": 2}] = 3
    A[{"i": 3}] = 4
    B[{"i": 0}] = 10
    B[{"i": 1}] = 20
    B[{"i": 2}] = 30
    B[{"i": 3}] = 40
    
    # Add two tensor
    print(A + B)
    
    # A number over a tensor
    print(1 / A)

    {names:[i],edges:[4],blocks:[11,22,33,44]}
    {names:[i],edges:[4],blocks:[1,0.5,0.333333,0.25]}

It always requires two tensor share the same shape, but edge order is not important

    from TAT.U1.D import Tensor
    
    A = Tensor(["i", "j"], [[(0, 2), (2, 4), (1, 1)], [(0, 3), (-2, 1), (-1, 3)]]).range()
    B = Tensor(["j", "i"], [[(0, 3), (-2, 1), (-1, 3)], [(0, 2), (2, 4), (1, 1)]]).range()
    print(A + B)

    {names:[i,j],edges:[{0:2,2:4,1:1},{0:3,-2:1,-1:3}],blocks:{[0,0]:[0,3,6,4,7,10],[2,-2]:[12,14,16,18],[1,-1]:[20,22,24]}}

For symmetry tensor, symmetry segment order is also important,
if their order is different, an error will be thrown.

    from TAT.U1.D import Tensor
    
    A = Tensor(["i", "j"], [[(0, 2), (2, 4), (1, 1)], [(0, 3), (-2, 1), (-1, 3)]]).range()
    B = Tensor(["j", "i"], [[(0, 3), (-2, 1), (-1, 3)], [(0, 2), (1, 1), (2, 4)]]).range()
    try:
        print(A + B)
    except RuntimeError as error:
        print(error)

    {names:[i,j],edges:[{0:2,2:4,1:1},{0:3,-2:1,-1:3}],blocks:{[0,0]:[0,3,6,4,7,10],[2,-2]:[16,18,20,15],[1,-1]:[17,19,21]}}


### Rank-0 tensor and number

You can convert between rank-0 tensor and number directly

    import TAT
    
    # Directly initialize a tensor with a number
    A = TAT.No.D.Tensor(233)
    # Convert rank-0 tensor to number
    a = float(A)
    print(a)
    
    B = TAT.U1.D.Tensor(233)
    print(B)
    b = float(B)
    print(b)
    
    C = TAT.U1.Z.Tensor(233 + 666j, ["i", "j"], [2, -2])
    print(C)
    c = complex(C)
    print(c)

    233.0
    {names:[],edges:[],blocks:{[]:[233]}}
    233.0
    {names:[i,j],edges:[{2:1},{-2:1}],blocks:{[2,-2]:[233+666i]}}
    (233+666j)

You can also create a scalar like non-rank-0 tensor directly,
it can also be converted into scalar directly.


### Explicitly copy

    from TAT.No.D import Tensor
    
    # Due to python feature, assigning will not copy data, it would share the same data,
    # So changing data in A will get tensor B changed.
    A = Tensor(233)
    B = A
    A[{}] = 1
    print(B)
    
    # Copy tensor excplicitly, A and B is two tensor without data shared.
    A = Tensor(233)
    B = A.copy()
    A[{}] = 1
    print(B)

    {names:[],edges:[],blocks:[1]}
    {names:[],edges:[],blocks:[233]}


### Create same shape tensor and some elementwise operator

Create a tensor with same shape to another can be achieve by method `same_shape`.

    from TAT.No.D import Tensor
    
    A = Tensor(["i", "j"], [2, 2])
    A[{"i": 0, "j": 0}] = 1
    A[{"i": 0, "j": 1}] = 2
    A[{"i": 1, "j": 0}] = 3
    A[{"i": 1, "j": 1}] = 4
    # tensor B copy the shape of A but not content of A
    B = A.same_shape().zero()
    print(B)

    {names:[i,j],edges:[2,2],blocks:[0,0,0,0]}

`map=/=transform` is outplace/inplace elementwise operator method.

    from TAT.No.D import Tensor
    
    A = Tensor(["i", "j"], [2, 2])
    # Another easy test data setter for tensor
    # which will fill meanless test data into tensor
    A.range()
    # Every element is transformed by a function inplacely
    A.transform(lambda x: x * x)
    print(A)
    
    # Every element is transformed by a function outplacely
    B = A.map(lambda x: x + 1)
    print(B)
    print(A)

    {names:[i,j],edges:[2,2],blocks:[0,1,4,9]}
    {names:[i,j],edges:[2,2],blocks:[1,2,5,10]}
    {names:[i,j],edges:[2,2],blocks:[0,1,4,9]}

method `to` is used for type conversion.

    import TAT
    
    A = TAT.No.D.Tensor(233)
    print(type(A))
    # Convert A to a complex tensor
    B = A.to(complex)
    print(type(B))

    <class 'TAT.No.D.Tensor'>
    <class 'TAT.No.Z.Tensor'>


### Norm

    from TAT.No.D import Tensor
    
    A = Tensor(["i"], [10]).range()
    # Get maximum norm
    print(A.norm_max())
    # Get 0 norm
    print(A.norm_num())
    # Get 1 norm
    print(A.norm_sum())
    # Get 2 norm
    print(A.norm_2())

    9.0
    10.0
    45.0
    16.881943016134134


### Contract

    from TAT.No.D import Tensor
    
    A = Tensor(["i", "j", "k"], [2, 3, 4]).range()
    B = Tensor(["a", "b", "c", "d"], [2, 5, 3, 6]).range()
    # Contract edge i of A and edge a of B, edge j of A and edge c of B
    C = A.contract(B, {("i", "a"), ("j", "c")})
    print(C)

    {names:[k,b,d],edges:[4,5,6],blocks:[4776,4836,4896,4956,5016,5076,5856,5916,5976,6036,6096,6156,6936,6996,7056,7116,7176,7236,8016,8076,8136,8196,8256,8316,9096,9156,9216,9276,9336,9396,5082,5148,5214,5280,5346,5412,6270,6336,6402,6468,6534,6600,7458,7524,7590,7656,7722,7788,8646,8712,8778,8844,8910,8976,9834,9900,9966,10032,10098,10164,5388,5460,5532,5604,5676,5748,6684,6756,6828,6900,6972,7044,7980,8052,8124,8196,8268,8340,9276,9348,9420,9492,9564,9636,10572,10644,10716,10788,10860,10932,5694,5772,5850,5928,6006,6084,7098,7176,7254,7332,7410,7488,8502,8580,8658,8736,8814,8892,9906,9984,10062,10140,10218,10296,11310,11388,11466,11544,11622,11700]}

    from TAT.U1.D import Tensor
    
    a = Tensor(["A", "B", "C", "D"], [
        [(-1, 1), (0, 1), (-2, 1)],
        [(0, 1), (1, 2)],
        [(0, 2), (1, 2)],
        [(-2, 2), (-1, 1), (0, 2)],
    ]).range()
    b = Tensor(["E", "F", "G", "H"], [
        [(0, 2), (1, 1)],
        [(-2, 1), (-1, 1), (0, 2)],
        [(0, 1), (-1, 2)],
        [(2, 2), (1, 1), (0, 2)],
    ]).range()
    print(a)
    print(b)
    print(Tensor.contract(a, b, {("B", "G"), ("D", "H")}))
    print(Tensor.contract(a.transpose(["A", "C", "B", "D"]), b.transpose(["G", "H", "E", "F"]), {("B", "G"), ("D", "H")}))
    c = a.clear_symmetry()
    d = b.clear_symmetry()
    e = Tensor.contract(a, b, {("B", "G"), ("D", "H")}).clear_symmetry()
    f = type(c).contract(c, d, {("B", "G"), ("D", "H")})
    print(e)
    print(f)

    {names:[A,B,C,D],edges:[{-1:1,0:1,-2:1},{0:1,1:2},{0:2,1:2},{-2:2,-1:1,0:2}],blocks:{[-1,0,1,0]:[0,1,2,3],[-1,1,0,0]:[4,5,6,7,8,9,10,11],[-1,1,1,-1]:[12,13,14,15],[0,0,0,0]:[16,17,18,19],[0,0,1,-1]:[20,21],[0,1,0,-1]:[22,23,24,25],[0,1,1,-2]:[26,27,28,29,30,31,32,33],[-2,1,1,0]:[34,35,36,37,38,39,40,41]}}
    {names:[E,F,G,H],edges:[{0:2,1:1},{-2:1,-1:1,0:2},{0:1,-1:2},{2:2,1:1,0:2}],blocks:{[0,-2,0,2]:[0,1,2,3],[0,-1,0,1]:[4,5],[0,-1,-1,2]:[6,7,8,9,10,11,12,13],[0,0,0,0]:[14,15,16,17,18,19,20,21],[0,0,-1,1]:[22,23,24,25,26,27,28,29],[1,-2,0,1]:[30],[1,-2,-1,2]:[31,32,33,34],[1,-1,0,0]:[35,36],[1,-1,-1,1]:[37,38],[1,0,-1,0]:[39,40,41,42,43,44,45,46]}}
    {names:[A,C,E,F],edges:[{-1:1,0:1,-2:1},{0:2,1:2},{0:2,1:1},{-2:1,-1:1,0:2}],blocks:{[-1,0,1,0]:[1062,1166,1386,1522],[-1,1,0,0]:[601,655,709,763,704,770,836,902],[-1,1,1,-1]:[1012,1229],[0,0,0,0]:[1515,1673,1831,1989,1618,1788,1958,2128],[0,0,1,-1]:[2898,3115],[0,1,0,-1]:[944,1420,1008,1517],[0,1,1,-2]:[4314,4604],[-2,1,1,0]:[5922,6506,6246,6862]}}
    {names:[A,C,E,F],edges:[{-1:1,0:1,-2:1},{0:2,1:2},{0:2,1:1},{-2:1,-1:1,0:2}],blocks:{[-1,0,1,0]:[1062,1166,1386,1522],[-1,1,0,0]:[601,655,709,763,704,770,836,902],[-1,1,1,-1]:[1012,1229],[0,0,0,0]:[1515,1673,1831,1989,1618,1788,1958,2128],[0,0,1,-1]:[2898,3115],[0,1,0,-1]:[944,1420,1008,1517],[0,1,1,-2]:[4314,4604],[-2,1,1,0]:[5922,6506,6246,6862]}}
    {names:[A,C,E,F],edges:[3,4,3,4],blocks:[0,0,0,0,0,0,0,0,0,0,1062,1166,0,0,0,0,0,0,0,0,0,0,1386,1522,0,0,601,655,0,0,709,763,0,1012,0,0,0,0,704,770,0,0,836,902,0,1229,0,0,0,0,1515,1673,0,0,1831,1989,0,2898,0,0,0,0,1618,1788,0,0,1958,2128,0,3115,0,0,0,944,0,0,0,1420,0,0,4314,0,0,0,0,1008,0,0,0,1517,0,0,4604,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5922,6506,0,0,0,0,0,0,0,0,0,0,6246,6862]}
    {names:[A,C,E,F],edges:[3,4,3,4],blocks:[0,0,0,0,0,0,0,0,0,0,1062,1166,0,0,0,0,0,0,0,0,0,0,1386,1522,0,0,601,655,0,0,709,763,0,1012,0,0,0,0,704,770,0,0,836,902,0,1229,0,0,0,0,1515,1673,0,0,1831,1989,0,2898,0,0,0,0,1618,1788,0,0,1958,2128,0,3115,0,0,0,944,0,0,0,1420,0,0,4314,0,0,0,0,1008,0,0,0,1517,0,0,4604,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5922,6506,0,0,0,0,0,0,0,0,0,0,6246,6862]}

Since edge "B" and edge "G", edge "D" and edge "H" have the compatible order, the contract result
of clear<sub>symmetry</sub> equals to clear<sub>symmetry</sub> of contract result.


### Merge and split edge

    from TAT.No.D import Tensor
    
    A = Tensor(["i", "j", "k"], [2, 3, 4]).range()
    # Merge edge i and edge j into a single edge a,
    # and Merge no edge to get a trivial edge b
    B = A.merge_edge({"a": ["i", "j"], "b": []})
    print(B)
    
    # Split edge a back to edge i and edge j, and split
    # trivial edge b to no edge
    C = B.split_edge({"b": [], "a": [("i", 2), ("j", 3)]})
    print(C)

    {names:[b,a,k],edges:[1,6,4],blocks:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]}
    {names:[i,j,k],edges:[2,3,4],blocks:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]}


### Edge rename and transpose

    from TAT.No.D import Tensor
    
    A = Tensor(["i", "j", "k"], [2, 3, 4]).range()
    # Rename edge i to edge x
    B = A.edge_rename({"i": "x"})
    print(B)
    # =edge_rename= is an outplace operator
    print(A)
    
    # Transpose tensor A with specific order
    C = A.transpose(["k", "j", "i"])
    print(C)

    {names:[x,j,k],edges:[2,3,4],blocks:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]}
    {names:[i,j,k],edges:[2,3,4],blocks:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]}
    {names:[k,j,i],edges:[4,3,2],blocks:[0,12,4,16,8,20,1,13,5,17,9,21,2,14,6,18,10,22,3,15,7,19,11,23]}


### SVD and QR decomposition

1.  QR decomposition

        def f_edge(*args):
            return (args, False)
        
        
        def t_edge(*args):
            return (args, True)
        
        
        from TAT.Fermi.D import Tensor
        
        A = Tensor(["i", "j", "k"], [
            t_edge((-1, 2), (0, 2), (-2, 2)),
            f_edge((0, 2), (1, 2)),
            f_edge((0, 2), (1, 2)),
        ]).range()
        
        # Do QR decomposition, specify Q matrix edge is edge k
        # You can also write is as =Q, R = A.qr('r', {"i", "j"}, "Q", "R")=
        # The last two argument is the name of new edges generated
        # by QR decomposition
        Q, R = A.qr('q', {"k"}, "Q", "R")
        # Q is an unitary matrix, which edge name is Q and k
        print(Q.conjugate().edge_rename({"Q": "Q1"}).contract(Q.edge_rename({"Q": "Q2"}), {("k", "k")}))
        # Q R - A is 0
        print((Q.contract(R, {("Q", "R")}) - A).norm_max())
    
        {names:[Q1,Q2],edges:[{arrow:0,segment:{1:2,0:2}},{arrow:1,segment:{-1:2,0:2}}],blocks:{[1,-1]:[1,-0,-0,1],[0,0]:[1,5.55112e-17,5.55112e-17,1]}}
        3.552713678800501e-15

2.  SVD decomposition

        def f_edge(*args):
            return (args, False)
        
        
        def t_edge(*args):
            return (args, True)
        
        
        from TAT.Fermi.D import Tensor
        
        A = Tensor(["i", "j", "k"], [
            t_edge((-1, 2), (0, 2), (-2, 2)),
            f_edge((0, 2), (1, 2)),
            f_edge((0, 2), (1, 2)),
        ]).range()
        
        # Do SVD decomposition with cut=3, if cut not specified,
        # svd will not cut the edge.
        # The first argument is edge set of matrix U, SVD does not
        # supply function to specify edge set of matrix V like what
        # is done in QR since SVD is symmetric between U and V.
        # The later two argument is new edges generated in tensor U
        # and tensor V. The later two argument is new edges of tensor
        # S. and the last argument is dimension cut.
        U, S, V = A.svd({"k"}, "U", "V", "SU", "SV")
        # U is an rank-3 unitary matrix
        print(U.conjugate().edge_rename({"U": "U1"}).contract(U.edge_rename({"U": "U2"}), {("k", "k")}))
        # U S V - A is a small value
        print((U.contract(S, {("U", "SU")}).contract(V, {("SV", "V")}) - A).norm_max())
    
        {names:[U1,U2],edges:[{arrow:0,segment:{1:2,0:2}},{arrow:1,segment:{-1:2,0:2}}],blocks:{[1,-1]:[1,-0,-0,1],[0,0]:[1,0,0,1]}}
        7.105427357601002e-15


### Identity, exponential and trace

    from TAT.No.D import Tensor
    # Please notice that identity is INPLACE operator
    # For any i, j, k, l, we have
    # =A[{"i":i, "j":j, "k":k, "l":l}] = delta(i,l) * delta(j,k)=
    A = Tensor(["i", "j", "k", "l"], [2, 3, 3, 2]).identity({("i", "l"), ("j", "k")})
    
    # calculate matrix exponential B = exp(A)
    # second argument is iteration steps, with default value 2
    B = A.exponential({("i", "l"), ("j", "k")}, 4)
    print(B)
    
    # Calculate trace or partial trace of a tenso
    # Here it calculate =A[{"i":i, "j":j, "k":k, "l":l}] * delta(i,l) * delta(j,k)=
    C = A.trace({("i", "l"), ("j", "k")})
    print(C)

    {names:[j,i,k,l],edges:[3,2,3,2],blocks:[2.71828,0,0,0,0,0,0,2.71828,0,0,0,0,0,0,2.71828,0,0,0,-0,-0,-0,2.71828,-0,-0,0,0,0,0,2.71828,0,0,0,0,0,0,2.71828]}
    {names:[],edges:[],blocks:[6]}

    from TAT.U1.D import Tensor
    
    A = Tensor(["i", "j", "k", "l", "m"], [
        [(-1, 2), (0, 2), (+1, 2)],
        [(0, 2), (1, 2)],
        [(0, 2), (-1, 2)],
        [(0, 2), (2, 3)],
        [(0, 2), (-2, 3)],
    ]).range()
    identity = Tensor(["k", "j", "m", "l"], [
        [(0, 2), (1, 2)],
        [(0, 2), (-1, 2)],
        [(0, 2), (2, 3)],
        [(0, 2), (-2, 3)],
    ]).identity({("j", "k"), ("m", "l")})
    print(A.trace({("j", "k"), ("l", "m")}))
    print(A.contract(identity, {("j", "j"), ("k", "k"), ("l", "l"), ("m", "m")}))

    {names:[i],edges:[{-1:2,0:2,1:2}],blocks:{[0]:[4734,5294]}}
    {names:[i],edges:[{-1:2,0:2,1:2}],blocks:{[0]:[4734,5294]}}


### IO

You can pickle load/dump a tensor directly, and even read data from `str` printed by a tensor.

    import pickle
    from TAT.No.D import Tensor
    
    A = Tensor(["i", "j", "k", "l"], [2, 3, 3, 2]).identity({("i", "l"), ("j", "k")})
    B = pickle.loads(pickle.dumps(A))
    C = Tensor(str(B))
    print(A)
    print(B)
    print(C)

    {names:[i,j,k,l],edges:[2,3,3,2],blocks:[1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1]}
    {names:[i,j,k,l],edges:[2,3,3,2],blocks:[1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1]}
    {names:[i,j,k,l],edges:[2,3,3,2],blocks:[1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1]}


### Fill random number into tensor

PyTAT contains a random generator inside.

    import TAT
    # Sed random seed
    TAT.random.seed(2333)
    # Generate normal distributed random variables into a tensor.
    A = TAT.No.D.Tensor(["i"], [10]).randn()
    print(A)
    # Generate uniform distributed random variables into a tensor.
    B = TAT.No.Z.Tensor(["i"], [10]).randn()
    print(B)

    {names:[i],edges:[10],blocks:[0.465246,0.529748,1.17368,0.544866,1.13411,0.611783,0.7999,-0.9954,0.9015,-1.69905]}
    {names:[i],edges:[10],blocks:[-0.263321+0.872706i,0.340596+0.75738i,-1.00095+0.630635i,1.24853-1.67437i,-0.352834+0.158421i,0.0561893-1.15738i,-0.579757-0.761532i,-2.13231-1.00284i,-0.118861+1.90261i,-0.271529-0.554287i]}

PyTAT also provide function to generate single random number.

    import TAT
    # Sed random seed
    TAT.random.seed(6666)
    # Generate uniform distributed random int in =[0, 9]=.
    generator = TAT.random.uniform_int(0, 4)
    print([generator() for _ in range(10)])
    # Generate uniform distributed random real in =[-1, +1]=.
    generator = TAT.random.uniform_real(-1, +1)
    print([generator() for _ in range(10)])
    # And normal distribution with $\mu=0, \sigma=2$.
    generator = TAT.random.normal(0, 2)
    print([generator() for _ in range(10)])

    [0, 4, 4, 1, 2, 2, 2, 1, 0, 3]
    [-0.14092759016536094, -0.08462244818883502, 0.9629775447195736, -0.3677814327981205, 0.14071059784799678, -0.9461988098776835, -0.42065228922511544, 0.014462778869043236, -0.952394632542909, -0.7269948652715256]
    [-0.8028506463569831, 2.467669174703166, 2.646095074776552, -2.238083341719702, 0.14214106921890585, 2.1826114150031, -2.0435554117053156, -0.22644183616582628, 2.4731844087444723, 5.112734591946305]


### Import from and export to numpy array

TAT can use [buffer protocol](https://docs.python.org/3/c-api/buffer.html) to transfer data from and to numpy.

    import numpy as np
    from TAT.No.D import Tensor
    
    A = Tensor(["i", "j"], [3, 4]).zero()
    # Export tensor to numpy array, which shape is [4, 3], because the
    # dimension order is set as =["j", "i"]=, where "i" correspond to an
    # edge with dimension 3 and "j" correspond to an edge with dimension
    # 4, so the result shape is =(4, 3)=.
    data_A = A.blocks[["j", "i"]]
    print(data_A.shape)
    
    # You can directly modify numpy array =data_A= to change data of tensor A,
    # namely, numpy array and TAT tensor share the data.
    data_A[1, 2] = 233
    print(A)
    
    # You can also direcly set =A.blocks[...]= to a numpy array.
    A.blocks[["j", "i"]] = np.arange(3 * 4).reshape([4, 3])
    print(A)

    (4, 3)
    {names:[i,j],edges:[3,4],blocks:[0,0,0,0,0,0,0,0,0,233,0,0]}
    {names:[i,j],edges:[3,4],blocks:[0,3,6,9,1,4,7,10,2,5,8,11]}


## FAQ


### I get error message like this when `import TAT`

    mca_base_component_repository_open: unable to open mca_patcher_overwrite: /usr/lib/x86_64-linux-gnu/openmpi/lib/openmpi/mca_patcher_overwrite.so: undefined symbol: mca_patcher_base_patch_t_class (ignored)
    mca_base_component_repository_open: unable to open mca_shmem_posix: /usr/lib/x86_64-linux-gnu/openmpi/lib/openmpi/mca_shmem_posix.so: undefined symbol: opal_shmem_base_framework (ignored)
    mca_base_component_repository_open: unable to open mca_shmem_mmap: /usr/lib/x86_64-linux-gnu/openmpi/lib/openmpi/mca_shmem_mmap.so: undefined symbol: opal_show_help (ignored)
    mca_base_component_repository_open: unable to open mca_shmem_sysv: /usr/lib/x86_64-linux-gnu/openmpi/lib/openmpi/mca_shmem_sysv.so: undefined symbol: opal_show_help (ignored)

It is a problem for some old mpi version(for example, openmpi 2.1.1 in ubuntu 18.04 LTS) if you compile mpi
support into PyTAT, you need to load mpi dynamic shared library manually before `import TAT`,
The way to load it manually is `import ctypes` and `ctypes.CDLL("libmpi.so", mode=ctypes.RTLD_GLOBAL)`.

It is recommended to use `mpi4py` instead, not to compile mpi into PyTAT.


### I get error message like this when `import TAT`

    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ImportError: /home/hzhangxyz/.local/lib/python3.10/site-packages/TAT.cpython-310-x86_64-linux-gnu.so: undefined symbol: cgesv_

It happens because you forgot to link `lapack` and `blas` when compile the library. You need to recompile it
with correct compiling flags, **OR** add lapack/blas library path to environment variables `LD_PRELOAD`, just
like `export LD_PRELOAD=/lib64/liblapack.so.3`, before running python directly.

