PyTAT


NamePyTAT JSON
Version 0.3.16 PyPI version JSON
download
home_pageNone
Summarypython binding for TAT(TAT is A Tensor library)
upload_time2024-03-20 08:23:47
maintainerNone
docs_urlNone
authorNone
requires_pythonNone
licenseGPLv3
keywords tensor tensor network tensor network state peps mps quantum many body system
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            PyTAT is a Python wrapper for the C++ tensor library called [TAT](https://github.com/USTC-TNS/TNSP/tree/main/TAT), offering support for both symmetry and fermion tensors.
The most of the interface of PyTAT keep consistent with TAT.


# Install

Users can create a Python wheel package using any modern build system, like [`wheel`](https://build.pypa.io/en/stable/) (with command `python -m pip wheel .`) or [`build`](https://pip.pypa.io/en/stable/reference/build-system/) (with command `python -m build .`).
Alternatively, users can simply use `pip install pytat` to install a pre-built distribution on widely-used operating systems.

To build PyTAT in the pyodide environment, refer to [this link](https://pyodide.org/en/stable/development/building-and-testing-packages.html).
Remember to add `--exports pyinit` to the `pyodide build` arguments.
We can't upload our pre-built emscripten platform distribution to [pypi.org](https://pypi.org) as they don't allow it.
Instead, users can download a pre-built emscripten platform wheel from the [release page](https://github.com/USTC-TNS/TNSP/releases).


# Documents


## The construction of tensors

As PyTAT simply wraps around a C++ header-only library, it does not support polymorphism for scalar types or symmetry types.
Consequently, each distinct tensor in Python has its own specific type.
The naming convention for tensor types is as follows: `TAT.<SymmetryType>.<ScalarType>.Tensor`.
Here, `<SymmetryType>` denotes the symmetry property maintained by the tensor, while `<ScalarType>` represents the scalar data type utilized within the tensors.
The available values for `<ScalarType>` are summarized in the table below.

<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">


<colgroup>
<col  class="org-left" />

<col  class="org-left" />

<col  class="org-left" />
</colgroup>
<thead>
<tr>
<th scope="col" class="org-left">`<ScalarType>`</th>
<th scope="col" class="org-left">scalar type in C++</th>
<th scope="col" class="org-left">equivalent in Fortran</th>
</tr>
</thead>

<tbody>
<tr>
<td class="org-left">`S`, `float32`</td>
<td class="org-left">`float`</td>
<td class="org-left">`real(kind=4)`</td>
</tr>


<tr>
<td class="org-left">`D`, `float64`, `float`</td>
<td class="org-left">`double`</td>
<td class="org-left">`real(kind=8)`</td>
</tr>


<tr>
<td class="org-left">`C`, `complex64`</td>
<td class="org-left">`std::complex<float>`</td>
<td class="org-left">`complex(kind=4)`</td>
</tr>


<tr>
<td class="org-left">`Z`, `complex128`, `complex`</td>
<td class="org-left">`std::complex<double>`</td>
<td class="org-left">`complex(kind=8)`</td>
</tr>
</tbody>
</table>

The available values for `<SymmetryType>` are summarized in the table below.

<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">


<colgroup>
<col  class="org-left" />

<col  class="org-left" />

<col  class="org-left" />
</colgroup>
<thead>
<tr>
<th scope="col" class="org-left">`<SymmetryType>`</th>
<th scope="col" class="org-left">symmetry type in C++</th>
<th scope="col" class="org-left">conservation example</th>
</tr>
</thead>

<tbody>
<tr>
<td class="org-left">`No`, `Normal`</td>
<td class="org-left">`Symmetry<>`</td>
<td class="org-left">nothing</td>
</tr>


<tr>
<td class="org-left">`BoseZ2`, `Z2`</td>
<td class="org-left">`Symmetry<bose<Z2>>`</td>
<td class="org-left">parity of spin z</td>
</tr>


<tr>
<td class="org-left">`BoseU1`, `U1`</td>
<td class="org-left">`Symmetry<bose<U1>>`</td>
<td class="org-left">spin z</td>
</tr>


<tr>
<td class="org-left">`FermiU1`</td>
<td class="org-left">`Symmetry<fermi<U1>>`</td>
<td class="org-left">fermion number</td>
</tr>


<tr>
<td class="org-left">`FermiU1BoseZ2`</td>
<td class="org-left">`Symmetry<fermi<U1>, bose<Z2>>`</td>
<td class="org-left">fermion number & parity of spin z</td>
</tr>


<tr>
<td class="org-left">`FermiU1BoseU1`</td>
<td class="org-left">`Symmetry<fermi<U1>, bose<U1>>`</td>
<td class="org-left">fermion number & spin z</td>
</tr>


<tr>
<td class="org-left">`FermiZ2`</td>
<td class="org-left">`Symmetry<fermi<Z2>>`</td>
<td class="org-left">parity of fermion number</td>
</tr>


<tr>
<td class="org-left">`FermiU1FermiU1`</td>
<td class="org-left">`Symmetry<fermi<U1>, fermi<U1>>`</td>
<td class="org-left">numbers of two kinds of fermions</td>
</tr>
</tbody>
</table>

Users can create tensors of various types by using the same interface `Tensor(name_list, edge_list)`,
in which `name_list` is simply a list of strings,
whereas `edge_list` may vary significantly depending on the specific symmetry type being considered.

For a tensor without any symmetry, users can simply use an integer list to define its edges.
Here's an example that creates a tensor filled with zeros.
Please note that the data in the tensor will not be automatically initialized to zero unless it is explicitly set to zero using the `zero_()` function.

    import TAT
    
    A = TAT.No.D.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 creates a rank-2 tensor called `A` with two edges `i` and `j`,
where the dimensions of these edges are 3 and 4 respectively.
Then, it prints the tensor A.

Non-fermion symmetry tensors define edges using "segments", which are a list of pairs of quantum numbers and their respective degeneracy.
The quantum numbers and their degeneracy are also referred to as irreducible representations and their multiplicity in the terminology of group theory,
or so-called "symmetry" and the corresponding dimension in the context of this package.
The following code generates a \(Z(2)\) symmetry tensor and a \(U(1)\) symmetry tensor.
In this case, the irreducible representation of \(Z(2)\) symmetry is represented as a boolean value, while for \(U(1)\) symmetry it's an integer.

    import TAT
    
    A = TAT.BoseZ2.D.Tensor(["i", "j"], [
        [(False, 2), (True, 4)],
        [(False, 3), (True, 1)],
    ]).range_()
    print(A)
    
    B = TAT.BoseU1.D.Tensor(["i", "j"], [
        [(-1, 2), (0, 4), (+1, 1)],
        [(-1, 3), (0, 2), (+1, 1)],
    ]).range_()
    print(B)

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

For tensor `A`, there are two blocks. The first block has irreducible representations `[False, False]` and a dimension of `2 * 4`.
The second block has irreducible representations `[True, True]`, resulting in a dimension of `4 * 1`.
For tensor B, it consists of three blocks. The irreducible representations are `[-1, +1]`, `[0, 0]`, and `[+1, -1]`.
Each block has different dimensions based on these multiplicity.
In the given code, the `range_()` function generates range data into the tensor.

The situation regarding fermion tensors can be quite complicated.
The edge is determined by pairs of segments along with the so-called "fermi-arrow", which is a boolean value.
The example below creates a fermion \(U(1)\) symmetry tensor, with fermionic properties carried by the \(U(1)\) symmetry,
where the fermi-arrow of its two edges are `False` and `True`, respectively.

    import TAT
    
    A = TAT.FermiU1.D.Tensor(["i", "j"], [
        ([(-1, 2), (0, 4), (+1, 1)], False),
        ([(-1, 3), (0, 2), (+1, 1)], True),
    ]).range_()
    print(A)

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

The fermi-arrow is introduced in the context of the fermion tensor network,
which posits the existence of a fermionic EPR pair behind each edge of the network.
The two tensors connected by an edge contain two operators of the EPR pair,
and for a fermionic EPR pair, the order of two operators matters.
Therefore, in TAT, a fermi-arrow is used to represent which side's operator is in front of the other.
Specifically, TAT assumes the operator of fermi-arrow of False is in front of the fermi-arrow of True.

For symmetry tensors of non-simple groups, their irreducible representations can indeed be represented by a tuple instead of a single boolean or integer, as shown in the example below.

    import TAT
    
    A = TAT.FermiU1BoseZ2.D.Tensor(["i", "j"], [
        ([
    	((-1, False), 1),
    	((0, False), 1),
    	((+1, False), 1),
    	((-1, True), 1),
    	((0, True), 1),
    	((+1, True), 1),
        ], False),
        ([
    	((-1, False), 1),
    	((0, False), 1),
    	((+1, False), 1),
    	((-1, True), 1),
    	((0, True), 1),
    	((+1, True), 1),
        ], True),
    ]).range_()
    print(A)

    {names:[i,j],edges:[{arrow:0,segment:{(-1,0):1,(0,0):1,(1,0):1,(-1,1):1,(0,1):1,(1,1):1}},{arrow:1,segment:{(-1,0):1,(0,0):1,(1,0):1,(-1,1):1,(0,1):1,(1,1):1}}],blocks:{[(-1,0),(1,0)]:[0],[(0,0),(0,0)]:[1],[(1,0),(-1,0)]:[2],[(-1,1),(1,1)]:[3],[(0,1),(0,1)]:[4],[(1,1),(-1,1)]:[5]}}


## The clearance of symmetry information

As a symmetry tensor is a blocked tensor, it is always possible to remove the symmetry information from such a tensor, thereby obtaining a non-symmetry tensor.
This functionality is achieved through the use of the `clear_symmetry` function, as demonstrated in the following code snippet:

    import TAT
    
    A = TAT.BoseZ2.D.Tensor(["i", "j"], [
        [(False, 2), (True, 4)],
        [(False, 3), (True, 1)],
    ]).range_()
    B = A.clear_symmetry()
    print(A)
    print(B)
    
    C = TAT.BoseU1.D.Tensor(["i", "j"], [
        [(0, 2), (2, 4), (1, 1)],
        [(0, 3), (-2, 1), (-1, 3)],
    ]).range_()
    D = C.clear_symmetry()
    print(C)
    print(D)

    {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]}}
    {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]}
    {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]}

For a fermion symmetry tensor, direct removal of fermion anti-commutation relation is not feasible.
Instead, only a portion of the symmetry can be cleared, resulting in a fermion \(Z(2)\) symmetry tensor rather than a non-symmetry tensor, as illustrated below:

    import TAT
    
    C = TAT.FermiU1.D.Tensor(["i", "j"], [
        ([(0, 2), (2, 4), (1, 1)], False),
        ([(0, 3), (-2, 1), (-1, 3)], True),
    ]).range_()
    D = C.clear_symmetry()
    print(C)
    print(D)

    {names:[i,j],edges:[{arrow:0,segment:{0:2,2:4,1:1}},{arrow:1,segment:{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:[{arrow:0,segment:{0:6,1:1}},{arrow:1,segment:{0:4,1:3}}],blocks:{[0,0]:[0,1,2,0,3,4,5,0,0,0,0,6,0,0,0,7,0,0,0,8,0,0,0,9],[1,1]:[10,11,12]}}


## Attributes within a tensor

A tensor primarily consists of three parts: names, edges, and content.
Users can access the names list through the read-only property `A.names` and the edges list via the read-only property `A.edges`.
In practical scenarios, `A.edge_by_name(name)` is a valuable method for obtaining the corresponding edge based on a given edge name directly.
Moreover, the rank of a tensor can be obtained using `A.rank`.

    import TAT
    
    A = TAT.BoseU1.D.Tensor(["i", "j"], [
        [(-1, 1), (0, 1), (+2, 1)],
        [(-2, 2), (+1, 1), (0, 2)],
    ])
    print(A.names)
    print(A.edges[0], A.edges[1])
    print(A.edge_by_name("i"), A.edge_by_name("j"))
    print(A.rank)

    ['i', 'j']
    {-1:1,0:1,2:1} {-2:2,1:1,0:2}
    {-1:1,0:1,2:1} {-2:2,1:1,0:2}
    2

To access the content of the tensor, there are three available methods:

-   Retrieve all the content as a one-dimensional array using `A.storage`, which is a NumPy array with data shared with the TAT tensor.
    Operating on this storage array is the recommended method for performing allreduce or broadcast operations on data in an MPI program.

    import TAT
    
    A = TAT.BoseU1.D.Tensor(["i", "j"], [
        [(-1, 1), (0, 1), (+2, 1)],
        [(-2, 2), (+1, 1), (0, 2)],
    ]).range_()
    print(A.storage)
    print(type(A.storage))
    print(A.storage.flags.owndata)

    [0. 1. 2. 3. 4.]
    <class 'numpy.ndarray'>
    False

-   Obtain a block of the tensor based on the specified edge name order and symmetry for each edge.
    In the case of non-symmetry tensors, there is no need to specify symmetry for each edge.
    Therefore, this interface also accepts a list of edge names to pass the edge name order for non-symmetry tensors.
    This block is also a NumPy array with shared data.

    import TAT
    
    A = TAT.BoseU1.D.Tensor(["i", "j"], [
        [(-1, 2), (0, 2), (+2, 2)],
        [(-2, 2), (+1, 2), (0, 2)],
    ]).range_()
    block = A.blocks[("j", -2), ("i", +2)]
    print(block)
    
    B = TAT.No.D.Tensor(["i", "j"], [3, 4]).range_()
    print(B.blocks["j", "i"])

    [[ 8. 10.]
     [ 9. 11.]]
    [[ 0.  4.  8.]
     [ 1.  5.  9.]
     [ 2.  6. 10.]
     [ 3.  7. 11.]]

-   Retrieve a specific element of the tensor using a dictionary that describes its exact location within the tensor.
    The exact location within the tensor can be specified using a dictionary mapping from edge names to the total index for that edge,
    or to the pair consisting of symmetry (indicating the segment inside the edge) and local index (indicating the specific index within that segment).

    import TAT
    
    A = TAT.BoseU1.D.Tensor(["i", "j"], [
        [(-1, 2), (0, 2), (+2, 2)],
        [(-2, 2), (+1, 2), (0, 2)],
    ]).range_()
    print(A[{"j": (-2, 0), "i": (+2, 1)}])

    10.0

All of these three methods also support setting elements using the same interface.


## Attributes of tensor type

Tensor types include several static attributes, such as:

-   `btypes`: The scalar type represented by the BLAS convention.
-   `dtypes`: The scalar type represented by the NumPy convention.
-   `is_complex`: A boolean indicating whether the tensor is complex.
-   `is_real`: A boolean indicating whether the tensor is real.
-   `model`: An alias for the symmetry model of the tensor. For example, getting the attribute `model` of `TAT.FermiU1.D.Tensor` results in `TAT.FermiU1`.


## Conversion between single-element tensor and number

Users can convert between a rank-0 tensor and a number directly.
For non-rank-0 tensors that contain only one element, users can also convert them to a number directly.
Conversely, users can create a one-element tensor with several 1-dimensional edges directly as the inverse operation.
In this case, for a non-symmetry tensor, users should only pass the name list when creating a one-element tensor that is not rank-0.
For non-fermion symmetry tensors, users should provide additional symmetry information for each edge as the third argument.
For fermion symmetry tensors, users should provide additional fermi-arrow information for each edge as the fourth argument.

    import TAT
    
    A = TAT.No.Z.Tensor(233)
    a = complex(A)
    print(A)
    print(a)
    
    B = TAT.BoseU1.D.Tensor(233)
    b = float(B)
    print(B)
    print(b)
    
    C = TAT.No.D.Tensor(233, ["i", "j"])
    c = float(C)
    print(C)
    print(c)
    
    D = TAT.BoseU1.D.Tensor(233, ["i", "j"], [-1, +1])
    d = float(D)
    print(D)
    print(d)
    
    E = TAT.FermiU1.D.Tensor(233, ["i", "j"], [-1, +1], [False, True])
    e = float(E)
    print(E)
    print(e)

    {names:[],edges:[],blocks:[233]}
    (233+0j)
    {names:[],edges:[],blocks:{[]:[233]}}
    233.0
    {names:[i,j],edges:[1,1],blocks:[233]}
    233.0
    {names:[i,j],edges:[{-1:1},{1:1}],blocks:{[-1,1]:[233]}}
    233.0
    {names:[i,j],edges:[{arrow:0,segment:{-1:1}},{arrow:1,segment:{1:1}}],blocks:{[-1,1]:[233]}}
    233.0


## Type conversion

To convert the type of the content of a tensor, users can use the `to` function.

    import TAT
    
    A = TAT.FermiU1.D.Tensor(["i", "j"], [
        ([(0, 2), (-1, 2)], False),
        ([(0, 2), (1, 2)], False),
    ]).range_()
    print(type(A))
    print(type(A.to("complex")))
    print(type(A.to("complex64")))
    print(type(A.to("complex128")))
    print(type(A.to("float")))
    print(type(A.to("float32")))
    print(type(A.to("float64")))

    <class 'TAT.FermiU1.D.Tensor'>
    <class 'TAT.FermiU1.Z.Tensor'>
    <class 'TAT.FermiU1.C.Tensor'>
    <class 'TAT.FermiU1.Z.Tensor'>
    <class 'TAT.FermiU1.D.Tensor'>
    <class 'TAT.FermiU1.S.Tensor'>
    <class 'TAT.FermiU1.D.Tensor'>


## Serialization and deserialization

Users can employ the `pickle.dump(s)` function to binary serialize a tensor,
and the `pickle.load(s)` function to binary deserialize a tensor.
For text serialization, the `str` function can be utilized,
and tensor deserialization from text format can be accomplished using the tensor constructor.

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

    {names:[i,j,k,l],edges:[2,3,3,2],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,24,25,26,27,28,29,30,31,32,33,34,35]}
    {names:[i,j,k,l],edges:[2,3,3,2],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,24,25,26,27,28,29,30,31,32,33,34,35]}
    {names:[i,j,k,l],edges:[2,3,3,2],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,24,25,26,27,28,29,30,31,32,33,34,35]}


## Explicit copying

Because of Python's behavior, a simple assignment will not create a copy of the data, but share the same data instead.
In the following example, when B is assigned to A, modifying data in A will also result in changes to tensor B.
To perform a deep copy of a tensor, users can use the tensor's member function `copy`, or they can directly use `copy.copy`.
To copy the shape of a tensor without copying its content, users can utilize the `same_shape` function,
which creates a tensor with the same shape but with uninitialized data.

    import copy
    import TAT
    
    A = TAT.No.D.Tensor(233)
    B = A
    A[{}] = 1
    print(B)
    
    C = TAT.No.D.Tensor(233)
    D = C.copy()
    C[{}] = 1
    print(D)
    
    E = TAT.No.D.Tensor(233)
    F = copy.copy(E)
    E[{}] = 1
    print(F)

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


## Elementwise operations

Users can apply custom functions to the elements of a tensor element-wise using the `map` function for out-of-place operations
or the `transform_` function for in-place operations.
Additionally, there is a function called `set_`, which is similar to `transform_`, but it does not accept an input value.
In other words, `A.set_(f)` is equivalent to `A.transform_(lambda _: f())`.

    import TAT
    
    A = TAT.No.D.Tensor(["i", "j"], [2, 2]).range_()
    A.transform_(lambda x: x * x)
    print(A)
    
    B = A.map(lambda x: x + 1)
    print(B)
    print(A)
    
    A.set_(iter([1, 6, 2, 5]).__next__)
    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]}
    {names:[i,j],edges:[2,2],blocks:[1,6,2,5]}

In practice, there are several elementwise operations that are commonly used,
so the TAT Python interface provides individual functions to wrap them for convenience. These include:

-   `A.reciprocal()`: Acts like `A.map(lambda x: 0 if x == 0 else 1 / x)`.
-   `A.sqrt()`: Acts like `A.map(lambda x: x**(1 / 2))`.


## Norm of a tensor

Users can compute the norm of a tensor using the following functions:

-   `norm_2` for the 2-norm.
-   `norm_max` for the &infin;-norm.
-   `norm_num` for the 0-norm.
-   `norm_sum` for the 1-norm.

    import TAT
    
    A = TAT.No.D.Tensor(["i"], [6]).range_(1, 2)
    print(A)
    print(A.norm_2())
    print(A.norm_max())
    print(A.norm_num())
    print(A.norm_sum())

    {names:[i],edges:[6],blocks:[1,3,5,7,9,11]}
    16.911534525287763
    11.0
    6.0
    36.0


## Filling random numbers into a tensor

Filling a tensor with random numbers can be accomplished using the `set_` function,
but Python function calls can be relatively slow, and random filling operations might be frequently used.
To address this, the TAT Python interface provides two functions: `randn_` and `rand_`.

-   `randn_`: This function fills the tensor with normally distributed random numbers.
    It accepts optional arguments for specifying the mean (defaulting to 0) and standard deviation (defaulting to 1).
-   `rand_`: This function fills the tensor with uniformly distributed random numbers.
    It also accepts optional arguments for specifying the minimum (defaulting to 0) and maximum (defaulting to 1) values.

Both of these functions utilize the `std::mt19937_64` random engine, and users can set the seed for random number engine using `TAT.random.seed`.

    import TAT
    TAT.random.seed(2333)
    A = TAT.No.D.Tensor(["i"], [10]).randn_()
    print(A)
    B = TAT.No.Z.Tensor(["i"], [10]).randn_()
    print(B)

    {names:[i],edges:[10],blocks:[0.766553,1.42783,-0.802786,0.231369,-0.144274,0.75302,-0.930606,-0.90363,1.58645,-1.66505]}
    {names:[i],edges:[10],blocks:[0.93897-2.03094i,-1.04394+0.724667i,0.0607228+0.802331i,-0.0634779+0.261524i,-0.0182935-0.00331999i,-0.809166+0.358002i,0.108272+0.293261i,-0.685203-0.874357i,-1.02724+0.898064i,-1.16878-0.312219i]}

Certainly, there are cases where users may want to use the TAT random number generator for generating random numbers outside of tensors.
This can be achieved through functions within the `TAT.random` submodule, which includes:

-   `uniform_int`: Generates uniformly distributed random integers.
-   `uniform_real`: Generates uniformly distributed random real numbers.
-   `normal`: Generates normally distributed random numbers.

    import TAT
    
    TAT.random.seed(2333)
    a = TAT.random.uniform_int(0, 1)
    print([a() for _ in range(10)])
    b = TAT.random.uniform_real(0, 1)
    print([b() for _ in range(10)])
    c = TAT.random.normal(0, 1)
    print([c() for _ in range(10)])

    [1, 1, 1, 0, 1, 1, 1, 0, 0, 0]
    [0.40352081782045557, 0.5919243832286168, 0.27290914845486797, 0.7042572953540996, 0.5525455768177127, 0.3527365854756287, 0.13938916269629487, 0.844959553591226, 0.6296832832042462, 0.8978555690178844]
    [-0.018293519693094607, -0.8091660392771898, -0.0033199925772919928, 0.35800177574398406, 0.1082722439575567, -0.6852033252925772, 0.29326095246544526, -0.8743569677337741, -1.0272406882246077, -1.1687800551936816]


## Setting range data into a tensor

Users can set a range of data into a tensor using `A.range_(first, step)`,
which fills the tensor with data in the sequence of \(first\), \(first+step\), \(first+step \times 2\), and so on.
By default, `first` is set to 0 and `step` is set to 1.
In practical tensor network state programming, this function is not frequently utilized
and is primarily employed for generating examples to illustrate other functions discussed in this document.

    import TAT
    
    A = TAT.FermiU1.C.Tensor(["i", "j", "k"], [
        ([(-1, 2), (0, 2), (-2, 2)], True),
        ([(0, 2), (1, 2)], False),
        ([(0, 2), (1, 2)], False),
    ]).range_(0, 1 + 1j)
    print(A)

    {names:[i,j,k],edges:[{arrow:1,segment:{-1:2,0:2,-2:2}},{arrow:0,segment:{0:2,1:2}},{arrow:0,segment:{0:2,1:2}}],blocks:{[-1,0,1]:[0,1+1i,2+2i,3+3i,4+4i,5+5i,6+6i,7+7i],[-1,1,0]:[8+8i,9+9i,10+10i,11+11i,12+12i,13+13i,14+14i,15+15i],[0,0,0]:[16+16i,17+17i,18+18i,19+19i,20+20i,21+21i,22+22i,23+23i],[-2,1,1]:[24+24i,25+25i,26+26i,27+27i,28+28i,29+29i,30+30i,31+31i]}}


## Filling Zeros into a Tensor

The content of a tensor is not initialized by default in the TAT package. To manually initialize it with zeros, users can invoke the `zero_` function.

    import TAT
    
    A = TAT.FermiU1.D.Tensor(["i", "j"], [
        ([(0, 2), (-1, 2)], False),
        ([(0, 2), (1, 2)], False),
    ]).zero_()
    print(A)

    {names:[i,j],edges:[{arrow:0,segment:{0:2,-1:2}},{arrow:0,segment:{0:2,1:2}}],blocks:{[0,0]:[0,0,0,0],[-1,1]:[0,0,0,0]}}


## Arithmetic scalar operations

Users can perform arithmetic scalar operations directly on tensors.
When performing arithmetic operations between two tensors,
their shapes should be the same except for the order of edges, as TAT can automatically transpose them as needed.

    import TAT
    
    a = TAT.No.D.Tensor(["i"], [4]).range_(0, 1)
    b = TAT.No.D.Tensor(["i"], [4]).range_(0, 10)
    print(a)
    print(b)
    print(a + b)
    print(a * b)
    print(1 / a)
    print(b - 1)
    a *= 2
    print(a)
    b /= 2
    print(b)

    {names:[i],edges:[4],blocks:[0,1,2,3]}
    {names:[i],edges:[4],blocks:[0,10,20,30]}
    {names:[i],edges:[4],blocks:[0,11,22,33]}
    {names:[i],edges:[4],blocks:[0,10,40,90]}
    {names:[i],edges:[4],blocks:[inf,1,0.5,0.333333]}
    {names:[i],edges:[4],blocks:[-1,9,19,29]}
    {names:[i],edges:[4],blocks:[0,2,4,6]}
    {names:[i],edges:[4],blocks:[0,5,10,15]}


## The tensor conjugation

Conjugating a tensor induces a reversal of symmetry in all segments across every edge, while simultaneously altering the values of all elements within the tensor, as illustrated below.

    import TAT
    
    A = TAT.BoseU1.Z.Tensor(["i", "j"], [
        [(0, 2), (2, 4), (1, 1)],
        [(0, 3), (-2, 1), (-1, 3)],
    ]).range_(0, 1 + 1j)
    B = A.conjugate()
    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+1i,2+2i,3+3i,4+4i,5+5i],[2,-2]:[6+6i,7+7i,8+8i,9+9i],[1,-1]:[10+10i,11+11i,12+12i]}}
    {names:[i,j],edges:[{0:2,-2:4,-1:1},{0:3,2:1,1:3}],blocks:{[0,0]:[0,1-1i,2-2i,3-3i,4-4i,5-5i],[-2,2]:[6-6i,7-7i,8-8i,9-9i],[-1,1]:[10-10i,11-11i,12-12i]}}

Please note that, in the case of \(U(1)\) symmetry, the reversal of the irreducible representation results in its negation, whereas for \(Z(2)\) symmetry, the reversal remains unchanged.

In the case of a fermion tensor, the conjugation of the tensor, when contracted with the original one, may result in a non-positive number.
This peculiar phenomenon indicates that the metric of the fermion tensor is not positive-semidefinite.
This unusual occurrence can disrupt the plain gradient method in high-level programming.
To compute the conjugation with a fixed metric, users can utilize an argument named `trivial_metric=True` when calling the conjugate function, as demonstrated below.
However, it's important to note that this metric fixing will lead to a situation where \((AB)^\dagger \neq A^\dagger B^\dagger\) .

    import TAT
    
    A = TAT.FermiZ2.Z.Tensor(["i", "j"], [
        ([(False, 2), (True, 4)], False),
        ([(False, 3), (True, 1)], True),
    ]).range_(0, 1 + 1j)
    B = A.conjugate()
    C = A.conjugate(trivial_metric=True)
    print(A)
    print(B)
    print(C)
    print(A.contract(B, {("i", "i"), ("j", "j")}))
    print(A.contract(C, {("i", "i"), ("j", "j")}))

    {names:[i,j],edges:[{arrow:0,segment:{0:2,1:4}},{arrow:1,segment:{0:3,1:1}}],blocks:{[0,0]:[0,1+1i,2+2i,3+3i,4+4i,5+5i],[1,1]:[6+6i,7+7i,8+8i,9+9i]}}
    {names:[i,j],edges:[{arrow:1,segment:{0:2,1:4}},{arrow:0,segment:{0:3,1:1}}],blocks:{[0,0]:[0,1-1i,2-2i,3-3i,4-4i,5-5i],[1,1]:[-6+6i,-7+7i,-8+8i,-9+9i]}}
    {names:[i,j],edges:[{arrow:1,segment:{0:2,1:4}},{arrow:0,segment:{0:3,1:1}}],blocks:{[0,0]:[0,1-1i,2-2i,3-3i,4-4i,5-5i],[1,1]:[6-6i,7-7i,8-8i,9-9i]}}
    {names:[],edges:[],blocks:{[]:[-350]}}
    {names:[],edges:[],blocks:{[]:[570]}}


## The tensor contraction

To perform the contraction of two tensors, users can provide a set of edge pairs as argument to the `contract` function.
Each pair consists of an edge from the first tensor to be contracted and the corresponding edge from the second tensor.
In the following example, edge 'i' of tensor A is contracted with edge 'a' of tensor B, and edge 'j' of tensor A is contracted with edge 'c' of tensor B.

    import TAT
    
    A = TAT.No.D.Tensor(["i", "j", "k"], [2, 3, 4]).range_()
    B = TAT.No.D.Tensor(["a", "b", "c", "d"], [2, 5, 3, 6]).range_()
    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]}

Since the function `clear_symmetry` solely removes symmetry information without making any other modifications,
the symmetry-cleared tensor resulting from the contraction is equal to the contraction of the symmetry-cleared tensors individually.

    import TAT
    
    a = TAT.BoseU1.D.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 = TAT.BoseU1.D.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_()
    c = a.contract(b, {("B", "G"), ("D", "H")})
    
    A = a.clear_symmetry()
    B = b.clear_symmetry()
    C = A.contract(B, {("B", "G"), ("D", "H")})
    print((c.clear_symmetry() - C).norm_2())

    0.0

The same principle applies to fermion symmetry tensors.

    import TAT
    
    a = TAT.FermiU1.D.Tensor(["A", "B", "C", "D"], [
        ([(-1, 1), (0, 1), (-2, 1)], False),
        ([(0, 1), (1, 2)], True),
        ([(0, 2), (1, 2)], False),
        ([(-2, 2), (-1, 1), (0, 2)], True),
    ]).range_()
    b = TAT.FermiU1.D.Tensor(["E", "F", "G", "H"], [
        ([(0, 2), (1, 1)], False),
        ([(-2, 1), (-1, 1), (0, 2)], True),
        ([(0, 1), (-1, 2)], False),
        ([(2, 2), (1, 1), (0, 2)], False),
    ]).range_()
    c = a.contract(b, {("B", "G"), ("D", "H")})
    
    A = a.clear_symmetry()
    B = b.clear_symmetry()
    C = A.contract(B, {("B", "G"), ("D", "H")})
    print((c.clear_symmetry() - C).norm_2())

    0.0

Sometimes, users may wish to construct a hypergraph that connects multiple edges (more than two) together.
This functionality is implemented using an additional argument in the `contract` function.
This argument is a set of edge names that specifies which edges should be fused together while keeping them as free edges without summation.
It's important to note that this type of fusion operation is not well-defined for symmetry tensors and can only be applied to non-symmetry tensors.
The following code snippet provides an example of this functionality:

    import TAT
    
    A = TAT.No.D.Tensor(["i", "j", "x"], [2, 3, 5]).range_()
    B = TAT.No.D.Tensor(["a", "x", "c", "d"], [2, 5, 3, 6]).range_()
    C = A.contract(B, {("i", "a"), ("j", "c")}, {"x"})
    print(C)

    {names:[x,d],edges:[5,6],blocks:[5970,6045,6120,6195,6270,6345,7734,7815,7896,7977,8058,8139,9714,9801,9888,9975,10062,10149,11910,12003,12096,12189,12282,12375,14322,14421,14520,14619,14718,14817]}


## Edge renaming

To rename the edge names of a tensor, users can utilize the `edge_rename` function with a dictionary as an argument,
where the keys represent the old names and the values represent the new names.
In the example provided, "i" is renamed to "j" and "j" is renamed to "i".

    import TAT
    
    A = TAT.No.D.Tensor(["i", "j", "k"], [2, 3, 4]).range_()
    B = A.edge_rename({"i": "j", "j": "i"})
    print(A)
    print(B)

    {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:[j,i,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]}


## Tensor exponential

Similar to the matrix exponential, the tensor exponential is obtained by summing the power series of tensor contractions.
To specify the way to contract tensors, users should define the relations between edges using a set of pairs of two edge names.
These pairs identify the corresponding relations, and the two edges in each pair will be contracted during the tensor contraction calculations.

    import TAT
    
    A = TAT.No.D.Tensor(
        ["i", "j", "k", "l"],
        [2, 3, 3, 2],
    ).range_()
    
    B = A.exponential({("i", "l"), ("j", "k")})
    print(B)

    {names:[j,i,k,l],edges:[3,2,3,2],blocks:[5.98438e+45,6.36586e+45,6.74734e+45,7.12882e+45,7.5103e+45,7.89178e+45,3.97807e+46,4.23166e+46,4.48524e+46,4.73883e+46,4.99242e+46,5.246e+46,1.72498e+46,1.83494e+46,1.9449e+46,2.05486e+46,2.16483e+46,2.27479e+46,5.10462e+46,5.43002e+46,5.75542e+46,6.08081e+46,6.40621e+46,6.73161e+46,2.85153e+46,3.0333e+46,3.21507e+46,3.39685e+46,3.57862e+46,3.76039e+46,6.23116e+46,6.62837e+46,7.02559e+46,7.4228e+46,7.82001e+46,8.21722e+46]}


## Setting an identity tensor

There are situations where users may want to obtain a tensor equivalent to an identity matrix.
This can be achieved by setting a tensor to an identity tensor using the `identity_` function.
This function accepts the same arguments as the exponential function to identify the corresponding relations within the edges.
The example provided below sets the tensor A to an identity tensor in place. After setting, we have \(A = \delta_{il}\delta_{jk}\).

    import TAT
    
    A = TAT.BoseU1.D.Tensor(["i", "j", "k", "l"], [
        [(-1, 1), (0, 1), (+2, 1)],
        [(-2, 2), (+1, 2), (0, 2)],
        [(+2, 2), (-1, 2), (0, 2)],
        [(+1, 1), (0, 1), (-2, 1)],
    ]).identity_({("i", "l"), ("j", "k")})
    print(A)

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


## Merging and splitting edges

Users have the ability to merge or split edges within a tensor using the functions `merge_edge` and `split_edge`.
When merging edges, users need to provide a dictionary that maps from the new edge name to the list of old edge names,
specifying which edges should be merged into a single edge and the order of the edges before merging.
The interface for splitting edges is similar, but due to the information loss during edge merging,
users also need to specify the edge segment information at this stage.
An edge consists of two parts: segment information and a possible fermi-arrow.
In this context, fermi-arrow is not needed, as TAT will automatically derive it.
For non-symmetry tensors, the segment information can be replaced by the edge dimension in a straightforward manner.
Users are free to merge zero edges into one edge or split one edge into zero edges, which simplifies handling corner cases in high-level code.

    import TAT
    
    A = TAT.FermiU1.D.Tensor(["i", "j", "k", "l"], [
        ([(-1, 1), (0, 1), (+2, 1)], False),
        ([(-2, 2), (+1, 2), (0, 2)], True),
        ([(+2, 2), (-1, 2), (0, 2)], False),
        ([(+1, 1), (0, 1), (-2, 1)], True),
    ]).range_()
    print(A)
    
    B = A.merge_edge({"a": ["i", "k"], "b": [], "c": ["l", "j"]})
    print(B)
    
    C = B.split_edge({
        "a": [
    	("i", [(-1, 1), (0, 1), (+2, 1)]),
    	("k", [(+2, 2), (-1, 2), (0, 2)]),
        ],
        "b": [],
        "c": [
    	("l", [(+1, 1), (0, 1), (-2, 1)]),
    	("j", [(-2, 2), (+1, 2), (0, 2)]),
        ]
    })
    print(C)
    print((A - C).norm_2())

    {names:[i,j,k,l],edges:[{arrow:0,segment:{-1:1,0:1,2:1}},{arrow:1,segment:{-2:2,1:2,0:2}},{arrow:0,segment:{2:2,-1:2,0:2}},{arrow:1,segment:{1:1,0:1,-2:1}}],blocks:{[-1,-2,2,1]:[0,1,2,3],[-1,1,2,-2]:[4,5,6,7],[-1,1,-1,1]:[8,9,10,11],[-1,1,0,0]:[12,13,14,15],[-1,0,0,1]:[16,17,18,19],[0,-2,2,0]:[20,21,22,23],[0,1,-1,0]:[24,25,26,27],[0,0,2,-2]:[28,29,30,31],[0,0,-1,1]:[32,33,34,35],[0,0,0,0]:[36,37,38,39],[2,-2,2,-2]:[40,41,42,43],[2,-2,-1,1]:[44,45,46,47],[2,-2,0,0]:[48,49,50,51],[2,1,-1,-2]:[52,53,54,55],[2,0,0,-2]:[56,57,58,59]}}
    {names:[b,c,a],edges:[{arrow:0,segment:{0:1}},{arrow:1,segment:{-1:4,2:2,1:4,-2:4,0:2,-4:2}},{arrow:0,segment:{1:4,-2:2,-1:4,2:4,0:2,4:2}}],blocks:{[0,-1,1]:[-0,-1,-44,-45,-2,-3,-46,-47,-4,-5,52,53,-6,-7,54,55],[0,2,-2]:[8,9,10,11],[0,1,-1]:[-16,-17,-32,-33,-18,-19,-34,-35,-12,-13,24,25,-14,-15,26,27],[0,-2,2]:[20,21,48,49,22,23,50,51,28,29,56,57,30,31,58,59],[0,0,0]:[36,37,38,39],[0,-4,4]:[40,41,42,43]}}
    {names:[l,j,i,k],edges:[{arrow:1,segment:{1:1,0:1,-2:1}},{arrow:1,segment:{-2:2,1:2,0:2}},{arrow:0,segment:{-1:1,0:1,2:1}},{arrow:0,segment:{2:2,-1:2,0:2}}],blocks:{[1,-2,-1,2]:[-0,-1,-2,-3],[1,-2,2,-1]:[-44,-45,-46,-47],[1,1,-1,-1]:[8,9,10,11],[1,0,-1,0]:[-16,-17,-18,-19],[1,0,0,-1]:[-32,-33,-34,-35],[0,-2,0,2]:[20,21,22,23],[0,-2,2,0]:[48,49,50,51],[0,1,-1,0]:[-12,-13,-14,-15],[0,1,0,-1]:[24,25,26,27],[0,0,0,0]:[36,37,38,39],[-2,-2,2,2]:[40,41,42,43],[-2,1,-1,2]:[-4,-5,-6,-7],[-2,1,2,-1]:[52,53,54,55],[-2,0,0,2]:[28,29,30,31],[-2,0,2,0]:[56,57,58,59]}}
    0.0

It's crucial to note that when two fermion symmetry tensors with connected edges, which will be contracted,
undergo merging or splitting of common edges, it results in the generation of a single sign.
So, users needs to specify which of the two tensors should contain the generated sign using the additional two arguments provided by the corresponding functions.
In the examples below, we initially contract the common edges "i" and "j" from connected tensors A1 and B1 to obtain tensor C1.
Subsequently, we merge the two common edges "i" and "j" into a single common edge "k" for both tensors, resulting in tensors A2 and B2.
Afterward, tensor C2 is obtained by contracting A2 and B2, demonstrating that C1 equals C2.
In this example, we apply the sign to B1 but not to A1, as we should apply it only once.
Moreover, there is a third argument in the function, which consists of a set of edge names selected from the merged edges,
and these particular edges are expected to exhibit behavior opposite to what is determined by the second argument.
In the case of splitting functions, the third argument should consist of a set of names representing edges that will exhibit opposite behavior when they are split.

    import TAT
    
    TAT.random.seed(7)
    
    A1 = TAT.FermiZ2.D.Tensor(["i", "j", "a"], [
        ([(False, 2), (True, 2)], False),
        ([(False, 2), (True, 2)], False),
        ([(False, 2), (True, 2)], True),
    ]).randn_()
    B1 = TAT.FermiZ2.D.Tensor(["i", "j", "b"], [
        ([(False, 2), (True, 2)], True),
        ([(False, 2), (True, 2)], True),
        ([(False, 2), (True, 2)], False),
    ]).randn_()
    C1 = A1.contract(B1, {("i", "i"), ("j", "j")})
    
    A2 = A1.merge_edge({"k": ["i", "j"]}, False)
    B2 = B1.merge_edge({"k": ["i", "j"]}, True)
    C2 = A2.contract(B2, {("k", "k")})
    
    print(C1 - C2)

    {names:[a,b],edges:[{arrow:1,segment:{0:2,1:2}},{arrow:0,segment:{0:2,1:2}}],blocks:{[0,0]:[0,0,0,0],[1,1]:[0,0,0,0]}}


## Reversing fermi-arrow of edges

The fermi-arrow of two edges that are connected with each other can be reversed together using the `reversed_edge` function.
It's important to note that when reversing a pair of edges, a single sign is generated.
Therefore, users need to specify which tensor the generated sign should be applied to.
This is handled by the last two arguments of the function.
In the example below, we first contract tensors A1 and B1 to obtain C1.
Then, we reverse the edges of A1 and B1 that will be contracted to create new tensors A2 and B2.
After reversing, we contract A2 and B2 to obtain C2. The code demonstrates that C1 and C2 are equal.
When reversing, the second argument indicates whether to apply the sign to the current tensor.
In this example, we apply the sign to B1 but not to A1, as we should apply it only once.
Additionally, there is a third argument in the function, which consists of a set of names selected from the edges that have undergone reversal,
and these specific edges are expected to exhibit behavior opposite to what is determined by the second argument.

    import TAT
    
    TAT.random.seed(7)
    
    A1 = TAT.FermiZ2.D.Tensor(["i", "j"], [
        ([(False, 2), (True, 2)], False),
        ([(False, 2), (True, 2)], True),
    ]).randn_()
    B1 = TAT.FermiZ2.D.Tensor(["i", "j"], [
        ([(False, 2), (True, 2)], False),
        ([(False, 2), (True, 2)], True),
    ]).randn_()
    C1 = A1.contract(B1, {("i", "j")})
    
    A2 = A1.reverse_edge({"i"}, False)
    B2 = B1.reverse_edge({"j"}, True)
    C2 = A2.contract(B2, {("i", "j")})
    
    print(C1 - C2)

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


## QR decomposition on a tensor

The `qr` function can be used to perform QR decomposition on a tensor.
To use this function, users should provide the set of free edges of the tensor after decomposition,
as well as the two edge names created as a result of the decomposition.
In the provided example, the fermion tensor A has three edges: "i", "j" and "k".
During the QR decomposition, we configure that the edges of the Q tensor should include "k" only,
while the remaining edges, namely "i" and "j", should be included in the R tensor.
The first argument of the qr function can be either 'q' or 'r', specifying whether the second argument represents the set of free edges of the Q tensor or the R tensor.
After the QR decomposition, the Q tensor will have two edges: the original "k" edge from the input tensor and the edge created during the decomposition, which is named "Q".
For the R tensor, it should contain three edges, with two of them coming from the original tensor ("i" and "j") and the newly created edge, named "R".

    import TAT
    
    A = TAT.FermiU1.D.Tensor(["i", "j", "k"], [
        ([(-1, 2), (0, 2), (-2, 2)], True),
        ([(0, 2), (1, 2)], False),
        ([(0, 2), (1, 2)], False),
    ]).range_()
    
    Q, R = A.qr('q', {"k"}, "Q", "R")
    Q_dagger = Q.conjugate().edge_rename({"Q": "Q'"})
    print(Q_dagger.contract(Q, {("k", "k")}))
    print((Q.contract(R, {("Q", "R")}) - A).norm_max())

    {names:[Q',Q],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


## Singular value decomposition (SVD) on a tensor

The `svd` function can be used to perform SVD on a tensor.
To use this function, users need to provide the set of free edges of the tensor after decomposition,
as well as the four edge names created as a result of the decomposition.
In the provided example, the fermion tensor A has three edges: "i", "j", and "k".
During the SVD, we configure the edges of the U tensor to include only the "k" edge, while the remaining edges, namely "i" and "j", should be included in the V tensor.
The first argument of the svd function is the set of free edges of the U tensor.
After the SVD, the U tensor will have two edges: the original "k" edge from the input tensor and the edge created during decomposition, which is named "U".
For the V tensor, it should contain three edges, with two of them coming from the original tensor ("i" and "j") and the newly created edge, named "V".
As for the S tensor, it is indeed a diagonal matrix with two edges, named "SU" and "SV," as specified in the later two arguments.
The last argument, which represents the SVD dimension cut, can be set to -1 for no cutting (default behavior),
a positive integer for absolute dimension cutting, or a real number between 0 and 1 for relative dimension cutting.

    import TAT
    
    A = TAT.FermiU1.D.Tensor(["i", "j", "k"], [
        ([(-1, 2), (0, 2), (-2, 2)], True),
        ([(0, 2), (1, 2)], False),
        ([(0, 2), (1, 2)], False),
    ]).range_()
    
    U, S, V = A.svd({"k"}, "U", "V", "SU", "SV", -1)
    U_dagger = U.conjugate().edge_rename({"U": "U'"})
    print(U_dagger.contract(U, {("k", "k")}))
    USV = U.contract(S, {("U", "SU")}).contract(V, {("SV", "V")})
    print((USV - A).norm_max())

    {names:[U',U],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]}}
    1.0658141036401503e-14


## The tensor tracing

To trace a subset of edges within a tensor, users can utilize the `trace` function.
This involves providing a set of pairs consisting of two edge names that are intended for tracing.
In the provided example, we perform a trace operation on tensor A, specifically targeting edges labeled "j" and "k".
This tensor encompasses three edges: "i", "j", and "k".
Consequently, the outcome of this operation will yield a tensor with a solitary edge labeled "i".

    import TAT
    
    A = TAT.FermiZ2.C.Tensor(["i", "j", "k"], [
        ([(False, 2), (True, 2)], True),
        ([(False, 2), (True, 2)], False),
        ([(False, 2), (True, 2)], True),
    ]).range_()
    print(A)
    B = A.trace({("j", "k")})
    print(B)

    {names:[i,j,k],edges:[{arrow:1,segment:{0:2,1:2}},{arrow:0,segment:{0:2,1:2}},{arrow:1,segment:{0:2,1:2}}],blocks:{[0,0,0]:[0,1,2,3,4,5,6,7],[0,1,1]:[8,9,10,11,12,13,14,15],[1,0,1]:[16,17,18,19,20,21,22,23],[1,1,0]:[24,25,26,27,28,29,30,31]}}
    {names:[i],edges:[{arrow:1,segment:{0:2,1:2}}],blocks:{[0]:[-16,-16]}}

Specifically tailored for non-symmetric tensors, similar to the contract operation,
this interface allows users to establish a connection between two edges within the same tensor while leaving them unsummarized.
This functionality is realized through the utilization of the second argument,
which takes the form of a dictionary mapping new edge names to pairs of two existing edge names.
In the provided examples, a non-symmetric tensor is created, featuring five edges: "i", "j", "k", "l", and "m".
During the tracing process, "j" and "k" are connected and combined, resulting in the omission of these two edges in the resulting tensor.
On the other hand, "l" and "m" are connected but not aggregated, leading to their consolidation into a single edge labeled "n" within the resultant tensor.

    import TAT
    
    A = TAT.No.Z.Tensor(
        ["i", "j", "k", "l", "m"],
        [4, 3, 3, 2, 2],
    ).range_()
    print(A)
    B = A.trace({("j", "k")}, {"n": ("l", "m")})
    print(B)

    {names:[i,j,k,l,m],edges:[4,3,3,2,2],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,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143]}
    {names:[n,i],edges:[2,4],blocks:[48,156,264,372,57,165,273,381]}


## The tensor transposition

In practical tensor operations, manual tensor transposition is typically unnecessary.
However, transposition becomes valuable when preparing tensors for external operations, such as MPI operations on tensor storage.
The `transpose` function accommodates this need by accepting a list of edge names that specify the desired edge order for the resulting tensor.

    import TAT
    
    A = TAT.FermiZ2.C.Tensor(["i", "j", "k"], [
        ([(False, 2), (True, 2)], True),
        ([(False, 2), (True, 2)], False),
        ([(False, 2), (True, 2)], True),
    ]).range_()
    print(A)
    B = A.transpose(["k", "j", "i"])
    print(B)

    {names:[i,j,k],edges:[{arrow:1,segment:{0:2,1:2}},{arrow:0,segment:{0:2,1:2}},{arrow:1,segment:{0:2,1:2}}],blocks:{[0,0,0]:[0,1,2,3,4,5,6,7],[0,1,1]:[8,9,10,11,12,13,14,15],[1,0,1]:[16,17,18,19,20,21,22,23],[1,1,0]:[24,25,26,27,28,29,30,31]}}
    {names:[k,j,i],edges:[{arrow:1,segment:{0:2,1:2}},{arrow:0,segment:{0:2,1:2}},{arrow:1,segment:{0:2,1:2}}],blocks:{[0,0,0]:[0,4,2,6,1,5,3,7],[0,1,1]:[-24,-28,-26,-30,-25,-29,-27,-31],[1,0,1]:[-16,-20,-18,-22,-17,-21,-19,-23],[1,1,0]:[-8,-12,-10,-14,-9,-13,-11,-15]}}


## Symmetry operations

While all interfaces accept integers, booleans, or tuples comprised of integers and booleans to represent symmetries,
often referred to as irreducible representations, each symmetry type has its specific class.
For instance, there is `TAT.FermiZ2.Symmetry`, which can be instantiated using a boolean value.
In practice, it's worth mentioning that all interfaces perform an implicit conversion of the input to the appropriate symmetry type internally.
For all symmetry types, users have the flexibility to perform various operations,
including addition of two symmetries,
subtraction of two symmetries,
obtaining the negation of a symmetry,
comparing two symmetries,
and retrieving the parity of the symmetry.

    import TAT
    
    r1 = TAT.BoseZ2.Symmetry(False)
    r2 = TAT.BoseZ2.Symmetry(True)
    print(r1, r2)
    print(r1 + r2, r1 - r2)
    print(-r1, -r2)
    print(r1 > r2, r1 < r2, r1 == r2)
    print(r1.parity, r2.parity)
    
    s1 = TAT.FermiZ2.Symmetry(False)
    s2 = TAT.FermiZ2.Symmetry(True)
    print(s1, s2)
    print(s1 + s2, s1 - s2)
    print(-s1, -s2)
    print(s1 > s2, s1 < s2, s1 == s2)
    print(s1.parity, s2.parity)
    
    t1 = TAT.FermiU1.Symmetry(-2)
    t2 = TAT.FermiU1.Symmetry(+3)
    print(t1, t2)
    print(t1 + t2, t1 - t2)
    print(-t1, -t2)
    print(t1 > t2, t1 < t2, t1 == t2)
    print(t1.parity, t2.parity)

    0 1
    1 1
    0 1
    False True False
    False False
    0 1
    1 1
    0 1
    False True False
    False True
    -2 3
    1 -5
    2 -3
    False True False
    False True


## Edge operations

Similarly to symmetry types, edge types are also defined, and interfaces that accept edges will automatically perform implicit type conversion for input edge types.
For instance, `TAT.FermiU1.Edge` is the designated edge type utilized in all tensors within the submodule `TAT.FermiU1`.
Edge types encompass several functions and attributes, including:

-   `edge.arrow`: Retrieves the fermi arrow of the edge. It is always set to False for non-fermion symmetry edges and non-symmetry edges.
-   `edge.dimension`: Obtains the total dimension of the edge.
-   `edge.segments`: Provides a read-only list of segment pairs comprising symmetry and its corresponding local dimension.
-   `edge.segments_size`: Determines the length of the segments list.
-   `edge.conjugate()`: Computes the conjugated edge.
-   `edge.dimension_by_symmetry(symmetry)`: Retrieves the local dimension based on the given symmetry.
-   `edge.position_by_symmetry(symmetry)`: Retrieves the position in the segments list using the specified symmetry.
-   `edge.<x>_by_<y>(...)`: Facilitates conversion between three indexing methods, where `<x>` and `<y>` can be either `index`, `coord`, or `point`.
    In the context of `index`, it represents the total index across the entire edge.
    In the case of `coord`, it consists of a pair denoting the position of the local segment within the segments list and the local index within that segment.
    Lastly, for `point`, it comprises a pair consisting of the symmetry of the current segment and the local index within that segment.


# 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)

This issue may arise due to problems with older MPI versions, such as OpenMPI 2.1.1 on Ubuntu 18.04 LTS.
If you have compiled MPI support into PyTAT, you may need to load the MPI dynamic shared library manually before importing TAT.
You can do this by using `import ctypes` and `ctypes.CDLL("libmpi.so", mode=ctypes.RTLD_GLOBAL)`.
It is recommended to refrain from integrating MPI support into TAT while compiling PyTAT, as we have no intention of using it.
Instead, our preference is to utilize mpi4py directly within the high-level code.


## 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_

This error arises due to the omission of linking LAPACK and BLAS libraries during the library compilation process.
To resolve this issue, you must either recompile the library with the correct compilation flags,
or alternatively, you can include the LAPACK/BLAS library path in the `LD_PRELOAD` environment variable.
For instance, you can achieve this by executing the command `export LD_PRELOAD=/lib64/liblapack.so.3` before running Python.


            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "PyTAT",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": "tensor tensor network tensor network state PEPS MPS quantum many body system",
    "author": null,
    "author_email": "Hao Zhang <zh970205@mail.ustc.edu.cn>",
    "download_url": null,
    "platform": null,
    "description": "PyTAT is a Python wrapper for the C++ tensor library called [TAT](https://github.com/USTC-TNS/TNSP/tree/main/TAT), offering support for both symmetry and fermion tensors.\nThe most of the interface of PyTAT keep consistent with TAT.\n\n\n# Install\n\nUsers can create a Python wheel package using any modern build system, like [`wheel`](https://build.pypa.io/en/stable/) (with command `python -m pip wheel .`) or [`build`](https://pip.pypa.io/en/stable/reference/build-system/) (with command `python -m build .`).\nAlternatively, users can simply use `pip install pytat` to install a pre-built distribution on widely-used operating systems.\n\nTo build PyTAT in the pyodide environment, refer to [this link](https://pyodide.org/en/stable/development/building-and-testing-packages.html).\nRemember to add `--exports pyinit` to the `pyodide build` arguments.\nWe can't upload our pre-built emscripten platform distribution to [pypi.org](https://pypi.org) as they don't allow it.\nInstead, users can download a pre-built emscripten platform wheel from the [release page](https://github.com/USTC-TNS/TNSP/releases).\n\n\n# Documents\n\n\n## The construction of tensors\n\nAs PyTAT simply wraps around a C++ header-only library, it does not support polymorphism for scalar types or symmetry types.\nConsequently, each distinct tensor in Python has its own specific type.\nThe naming convention for tensor types is as follows: `TAT.<SymmetryType>.<ScalarType>.Tensor`.\nHere, `<SymmetryType>` denotes the symmetry property maintained by the tensor, while `<ScalarType>` represents the scalar data type utilized within the tensors.\nThe available values for `<ScalarType>` are summarized in the table below.\n\n<table border=\"2\" cellspacing=\"0\" cellpadding=\"6\" rules=\"groups\" frame=\"hsides\">\n\n\n<colgroup>\n<col  class=\"org-left\" />\n\n<col  class=\"org-left\" />\n\n<col  class=\"org-left\" />\n</colgroup>\n<thead>\n<tr>\n<th scope=\"col\" class=\"org-left\">`<ScalarType>`</th>\n<th scope=\"col\" class=\"org-left\">scalar type in C++</th>\n<th scope=\"col\" class=\"org-left\">equivalent in Fortran</th>\n</tr>\n</thead>\n\n<tbody>\n<tr>\n<td class=\"org-left\">`S`, `float32`</td>\n<td class=\"org-left\">`float`</td>\n<td class=\"org-left\">`real(kind=4)`</td>\n</tr>\n\n\n<tr>\n<td class=\"org-left\">`D`, `float64`, `float`</td>\n<td class=\"org-left\">`double`</td>\n<td class=\"org-left\">`real(kind=8)`</td>\n</tr>\n\n\n<tr>\n<td class=\"org-left\">`C`, `complex64`</td>\n<td class=\"org-left\">`std::complex<float>`</td>\n<td class=\"org-left\">`complex(kind=4)`</td>\n</tr>\n\n\n<tr>\n<td class=\"org-left\">`Z`, `complex128`, `complex`</td>\n<td class=\"org-left\">`std::complex<double>`</td>\n<td class=\"org-left\">`complex(kind=8)`</td>\n</tr>\n</tbody>\n</table>\n\nThe available values for `<SymmetryType>` are summarized in the table below.\n\n<table border=\"2\" cellspacing=\"0\" cellpadding=\"6\" rules=\"groups\" frame=\"hsides\">\n\n\n<colgroup>\n<col  class=\"org-left\" />\n\n<col  class=\"org-left\" />\n\n<col  class=\"org-left\" />\n</colgroup>\n<thead>\n<tr>\n<th scope=\"col\" class=\"org-left\">`<SymmetryType>`</th>\n<th scope=\"col\" class=\"org-left\">symmetry type in C++</th>\n<th scope=\"col\" class=\"org-left\">conservation example</th>\n</tr>\n</thead>\n\n<tbody>\n<tr>\n<td class=\"org-left\">`No`, `Normal`</td>\n<td class=\"org-left\">`Symmetry<>`</td>\n<td class=\"org-left\">nothing</td>\n</tr>\n\n\n<tr>\n<td class=\"org-left\">`BoseZ2`, `Z2`</td>\n<td class=\"org-left\">`Symmetry<bose<Z2>>`</td>\n<td class=\"org-left\">parity of spin z</td>\n</tr>\n\n\n<tr>\n<td class=\"org-left\">`BoseU1`, `U1`</td>\n<td class=\"org-left\">`Symmetry<bose<U1>>`</td>\n<td class=\"org-left\">spin z</td>\n</tr>\n\n\n<tr>\n<td class=\"org-left\">`FermiU1`</td>\n<td class=\"org-left\">`Symmetry<fermi<U1>>`</td>\n<td class=\"org-left\">fermion number</td>\n</tr>\n\n\n<tr>\n<td class=\"org-left\">`FermiU1BoseZ2`</td>\n<td class=\"org-left\">`Symmetry<fermi<U1>, bose<Z2>>`</td>\n<td class=\"org-left\">fermion number & parity of spin z</td>\n</tr>\n\n\n<tr>\n<td class=\"org-left\">`FermiU1BoseU1`</td>\n<td class=\"org-left\">`Symmetry<fermi<U1>, bose<U1>>`</td>\n<td class=\"org-left\">fermion number & spin z</td>\n</tr>\n\n\n<tr>\n<td class=\"org-left\">`FermiZ2`</td>\n<td class=\"org-left\">`Symmetry<fermi<Z2>>`</td>\n<td class=\"org-left\">parity of fermion number</td>\n</tr>\n\n\n<tr>\n<td class=\"org-left\">`FermiU1FermiU1`</td>\n<td class=\"org-left\">`Symmetry<fermi<U1>, fermi<U1>>`</td>\n<td class=\"org-left\">numbers of two kinds of fermions</td>\n</tr>\n</tbody>\n</table>\n\nUsers can create tensors of various types by using the same interface `Tensor(name_list, edge_list)`,\nin which `name_list` is simply a list of strings,\nwhereas `edge_list` may vary significantly depending on the specific symmetry type being considered.\n\nFor a tensor without any symmetry, users can simply use an integer list to define its edges.\nHere's an example that creates a tensor filled with zeros.\nPlease note that the data in the tensor will not be automatically initialized to zero unless it is explicitly set to zero using the `zero_()` function.\n\n    import TAT\n    \n    A = TAT.No.D.Tensor([\"i\", \"j\"], [3, 4]).zero_()\n    print(A)\n\n    {names:[i,j],edges:[3,4],blocks:[0,0,0,0,0,0,0,0,0,0,0,0]}\n\nThe code above creates a rank-2 tensor called `A` with two edges `i` and `j`,\nwhere the dimensions of these edges are 3 and 4 respectively.\nThen, it prints the tensor A.\n\nNon-fermion symmetry tensors define edges using \"segments\", which are a list of pairs of quantum numbers and their respective degeneracy.\nThe quantum numbers and their degeneracy are also referred to as irreducible representations and their multiplicity in the terminology of group theory,\nor so-called \"symmetry\" and the corresponding dimension in the context of this package.\nThe following code generates a \\(Z(2)\\) symmetry tensor and a \\(U(1)\\) symmetry tensor.\nIn this case, the irreducible representation of \\(Z(2)\\) symmetry is represented as a boolean value, while for \\(U(1)\\) symmetry it's an integer.\n\n    import TAT\n    \n    A = TAT.BoseZ2.D.Tensor([\"i\", \"j\"], [\n        [(False, 2), (True, 4)],\n        [(False, 3), (True, 1)],\n    ]).range_()\n    print(A)\n    \n    B = TAT.BoseU1.D.Tensor([\"i\", \"j\"], [\n        [(-1, 2), (0, 4), (+1, 1)],\n        [(-1, 3), (0, 2), (+1, 1)],\n    ]).range_()\n    print(B)\n\n    {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]}}\n    {names:[i,j],edges:[{-1:2,0:4,1:1},{-1:3,0:2,1:1}],blocks:{[-1,1]:[0,1],[0,0]:[2,3,4,5,6,7,8,9],[1,-1]:[10,11,12]}}\n\nFor tensor `A`, there are two blocks. The first block has irreducible representations `[False, False]` and a dimension of `2 * 4`.\nThe second block has irreducible representations `[True, True]`, resulting in a dimension of `4 * 1`.\nFor tensor B, it consists of three blocks. The irreducible representations are `[-1, +1]`, `[0, 0]`, and `[+1, -1]`.\nEach block has different dimensions based on these multiplicity.\nIn the given code, the `range_()` function generates range data into the tensor.\n\nThe situation regarding fermion tensors can be quite complicated.\nThe edge is determined by pairs of segments along with the so-called \"fermi-arrow\", which is a boolean value.\nThe example below creates a fermion \\(U(1)\\) symmetry tensor, with fermionic properties carried by the \\(U(1)\\) symmetry,\nwhere the fermi-arrow of its two edges are `False` and `True`, respectively.\n\n    import TAT\n    \n    A = TAT.FermiU1.D.Tensor([\"i\", \"j\"], [\n        ([(-1, 2), (0, 4), (+1, 1)], False),\n        ([(-1, 3), (0, 2), (+1, 1)], True),\n    ]).range_()\n    print(A)\n\n    {names:[i,j],edges:[{arrow:0,segment:{-1:2,0:4,1:1}},{arrow:1,segment:{-1:3,0:2,1:1}}],blocks:{[-1,1]:[0,1],[0,0]:[2,3,4,5,6,7,8,9],[1,-1]:[10,11,12]}}\n\nThe fermi-arrow is introduced in the context of the fermion tensor network,\nwhich posits the existence of a fermionic EPR pair behind each edge of the network.\nThe two tensors connected by an edge contain two operators of the EPR pair,\nand for a fermionic EPR pair, the order of two operators matters.\nTherefore, in TAT, a fermi-arrow is used to represent which side's operator is in front of the other.\nSpecifically, TAT assumes the operator of fermi-arrow of False is in front of the fermi-arrow of True.\n\nFor symmetry tensors of non-simple groups, their irreducible representations can indeed be represented by a tuple instead of a single boolean or integer, as shown in the example below.\n\n    import TAT\n    \n    A = TAT.FermiU1BoseZ2.D.Tensor([\"i\", \"j\"], [\n        ([\n    \t((-1, False), 1),\n    \t((0, False), 1),\n    \t((+1, False), 1),\n    \t((-1, True), 1),\n    \t((0, True), 1),\n    \t((+1, True), 1),\n        ], False),\n        ([\n    \t((-1, False), 1),\n    \t((0, False), 1),\n    \t((+1, False), 1),\n    \t((-1, True), 1),\n    \t((0, True), 1),\n    \t((+1, True), 1),\n        ], True),\n    ]).range_()\n    print(A)\n\n    {names:[i,j],edges:[{arrow:0,segment:{(-1,0):1,(0,0):1,(1,0):1,(-1,1):1,(0,1):1,(1,1):1}},{arrow:1,segment:{(-1,0):1,(0,0):1,(1,0):1,(-1,1):1,(0,1):1,(1,1):1}}],blocks:{[(-1,0),(1,0)]:[0],[(0,0),(0,0)]:[1],[(1,0),(-1,0)]:[2],[(-1,1),(1,1)]:[3],[(0,1),(0,1)]:[4],[(1,1),(-1,1)]:[5]}}\n\n\n## The clearance of symmetry information\n\nAs a symmetry tensor is a blocked tensor, it is always possible to remove the symmetry information from such a tensor, thereby obtaining a non-symmetry tensor.\nThis functionality is achieved through the use of the `clear_symmetry` function, as demonstrated in the following code snippet:\n\n    import TAT\n    \n    A = TAT.BoseZ2.D.Tensor([\"i\", \"j\"], [\n        [(False, 2), (True, 4)],\n        [(False, 3), (True, 1)],\n    ]).range_()\n    B = A.clear_symmetry()\n    print(A)\n    print(B)\n    \n    C = TAT.BoseU1.D.Tensor([\"i\", \"j\"], [\n        [(0, 2), (2, 4), (1, 1)],\n        [(0, 3), (-2, 1), (-1, 3)],\n    ]).range_()\n    D = C.clear_symmetry()\n    print(C)\n    print(D)\n\n    {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]}}\n    {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]}\n    {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]}}\n    {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]}\n\nFor a fermion symmetry tensor, direct removal of fermion anti-commutation relation is not feasible.\nInstead, only a portion of the symmetry can be cleared, resulting in a fermion \\(Z(2)\\) symmetry tensor rather than a non-symmetry tensor, as illustrated below:\n\n    import TAT\n    \n    C = TAT.FermiU1.D.Tensor([\"i\", \"j\"], [\n        ([(0, 2), (2, 4), (1, 1)], False),\n        ([(0, 3), (-2, 1), (-1, 3)], True),\n    ]).range_()\n    D = C.clear_symmetry()\n    print(C)\n    print(D)\n\n    {names:[i,j],edges:[{arrow:0,segment:{0:2,2:4,1:1}},{arrow:1,segment:{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]}}\n    {names:[i,j],edges:[{arrow:0,segment:{0:6,1:1}},{arrow:1,segment:{0:4,1:3}}],blocks:{[0,0]:[0,1,2,0,3,4,5,0,0,0,0,6,0,0,0,7,0,0,0,8,0,0,0,9],[1,1]:[10,11,12]}}\n\n\n## Attributes within a tensor\n\nA tensor primarily consists of three parts: names, edges, and content.\nUsers can access the names list through the read-only property `A.names` and the edges list via the read-only property `A.edges`.\nIn practical scenarios, `A.edge_by_name(name)` is a valuable method for obtaining the corresponding edge based on a given edge name directly.\nMoreover, the rank of a tensor can be obtained using `A.rank`.\n\n    import TAT\n    \n    A = TAT.BoseU1.D.Tensor([\"i\", \"j\"], [\n        [(-1, 1), (0, 1), (+2, 1)],\n        [(-2, 2), (+1, 1), (0, 2)],\n    ])\n    print(A.names)\n    print(A.edges[0], A.edges[1])\n    print(A.edge_by_name(\"i\"), A.edge_by_name(\"j\"))\n    print(A.rank)\n\n    ['i', 'j']\n    {-1:1,0:1,2:1} {-2:2,1:1,0:2}\n    {-1:1,0:1,2:1} {-2:2,1:1,0:2}\n    2\n\nTo access the content of the tensor, there are three available methods:\n\n-   Retrieve all the content as a one-dimensional array using `A.storage`, which is a NumPy array with data shared with the TAT tensor.\n    Operating on this storage array is the recommended method for performing allreduce or broadcast operations on data in an MPI program.\n\n    import TAT\n    \n    A = TAT.BoseU1.D.Tensor([\"i\", \"j\"], [\n        [(-1, 1), (0, 1), (+2, 1)],\n        [(-2, 2), (+1, 1), (0, 2)],\n    ]).range_()\n    print(A.storage)\n    print(type(A.storage))\n    print(A.storage.flags.owndata)\n\n    [0. 1. 2. 3. 4.]\n    <class 'numpy.ndarray'>\n    False\n\n-   Obtain a block of the tensor based on the specified edge name order and symmetry for each edge.\n    In the case of non-symmetry tensors, there is no need to specify symmetry for each edge.\n    Therefore, this interface also accepts a list of edge names to pass the edge name order for non-symmetry tensors.\n    This block is also a NumPy array with shared data.\n\n    import TAT\n    \n    A = TAT.BoseU1.D.Tensor([\"i\", \"j\"], [\n        [(-1, 2), (0, 2), (+2, 2)],\n        [(-2, 2), (+1, 2), (0, 2)],\n    ]).range_()\n    block = A.blocks[(\"j\", -2), (\"i\", +2)]\n    print(block)\n    \n    B = TAT.No.D.Tensor([\"i\", \"j\"], [3, 4]).range_()\n    print(B.blocks[\"j\", \"i\"])\n\n    [[ 8. 10.]\n     [ 9. 11.]]\n    [[ 0.  4.  8.]\n     [ 1.  5.  9.]\n     [ 2.  6. 10.]\n     [ 3.  7. 11.]]\n\n-   Retrieve a specific element of the tensor using a dictionary that describes its exact location within the tensor.\n    The exact location within the tensor can be specified using a dictionary mapping from edge names to the total index for that edge,\n    or to the pair consisting of symmetry (indicating the segment inside the edge) and local index (indicating the specific index within that segment).\n\n    import TAT\n    \n    A = TAT.BoseU1.D.Tensor([\"i\", \"j\"], [\n        [(-1, 2), (0, 2), (+2, 2)],\n        [(-2, 2), (+1, 2), (0, 2)],\n    ]).range_()\n    print(A[{\"j\": (-2, 0), \"i\": (+2, 1)}])\n\n    10.0\n\nAll of these three methods also support setting elements using the same interface.\n\n\n## Attributes of tensor type\n\nTensor types include several static attributes, such as:\n\n-   `btypes`: The scalar type represented by the BLAS convention.\n-   `dtypes`: The scalar type represented by the NumPy convention.\n-   `is_complex`: A boolean indicating whether the tensor is complex.\n-   `is_real`: A boolean indicating whether the tensor is real.\n-   `model`: An alias for the symmetry model of the tensor. For example, getting the attribute `model` of `TAT.FermiU1.D.Tensor` results in `TAT.FermiU1`.\n\n\n## Conversion between single-element tensor and number\n\nUsers can convert between a rank-0 tensor and a number directly.\nFor non-rank-0 tensors that contain only one element, users can also convert them to a number directly.\nConversely, users can create a one-element tensor with several 1-dimensional edges directly as the inverse operation.\nIn this case, for a non-symmetry tensor, users should only pass the name list when creating a one-element tensor that is not rank-0.\nFor non-fermion symmetry tensors, users should provide additional symmetry information for each edge as the third argument.\nFor fermion symmetry tensors, users should provide additional fermi-arrow information for each edge as the fourth argument.\n\n    import TAT\n    \n    A = TAT.No.Z.Tensor(233)\n    a = complex(A)\n    print(A)\n    print(a)\n    \n    B = TAT.BoseU1.D.Tensor(233)\n    b = float(B)\n    print(B)\n    print(b)\n    \n    C = TAT.No.D.Tensor(233, [\"i\", \"j\"])\n    c = float(C)\n    print(C)\n    print(c)\n    \n    D = TAT.BoseU1.D.Tensor(233, [\"i\", \"j\"], [-1, +1])\n    d = float(D)\n    print(D)\n    print(d)\n    \n    E = TAT.FermiU1.D.Tensor(233, [\"i\", \"j\"], [-1, +1], [False, True])\n    e = float(E)\n    print(E)\n    print(e)\n\n    {names:[],edges:[],blocks:[233]}\n    (233+0j)\n    {names:[],edges:[],blocks:{[]:[233]}}\n    233.0\n    {names:[i,j],edges:[1,1],blocks:[233]}\n    233.0\n    {names:[i,j],edges:[{-1:1},{1:1}],blocks:{[-1,1]:[233]}}\n    233.0\n    {names:[i,j],edges:[{arrow:0,segment:{-1:1}},{arrow:1,segment:{1:1}}],blocks:{[-1,1]:[233]}}\n    233.0\n\n\n## Type conversion\n\nTo convert the type of the content of a tensor, users can use the `to` function.\n\n    import TAT\n    \n    A = TAT.FermiU1.D.Tensor([\"i\", \"j\"], [\n        ([(0, 2), (-1, 2)], False),\n        ([(0, 2), (1, 2)], False),\n    ]).range_()\n    print(type(A))\n    print(type(A.to(\"complex\")))\n    print(type(A.to(\"complex64\")))\n    print(type(A.to(\"complex128\")))\n    print(type(A.to(\"float\")))\n    print(type(A.to(\"float32\")))\n    print(type(A.to(\"float64\")))\n\n    <class 'TAT.FermiU1.D.Tensor'>\n    <class 'TAT.FermiU1.Z.Tensor'>\n    <class 'TAT.FermiU1.C.Tensor'>\n    <class 'TAT.FermiU1.Z.Tensor'>\n    <class 'TAT.FermiU1.D.Tensor'>\n    <class 'TAT.FermiU1.S.Tensor'>\n    <class 'TAT.FermiU1.D.Tensor'>\n\n\n## Serialization and deserialization\n\nUsers can employ the `pickle.dump(s)` function to binary serialize a tensor,\nand the `pickle.load(s)` function to binary deserialize a tensor.\nFor text serialization, the `str` function can be utilized,\nand tensor deserialization from text format can be accomplished using the tensor constructor.\n\n    import pickle\n    import TAT\n    \n    A = TAT.No.D.Tensor(\n        [\"i\", \"j\", \"k\", \"l\"],\n        [2, 3, 3, 2],\n    ).range_()\n    B = pickle.loads(pickle.dumps(A))\n    C = TAT.No.D.Tensor(str(B))\n    print(A)\n    print(B)\n    print(C)\n\n    {names:[i,j,k,l],edges:[2,3,3,2],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,24,25,26,27,28,29,30,31,32,33,34,35]}\n    {names:[i,j,k,l],edges:[2,3,3,2],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,24,25,26,27,28,29,30,31,32,33,34,35]}\n    {names:[i,j,k,l],edges:[2,3,3,2],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,24,25,26,27,28,29,30,31,32,33,34,35]}\n\n\n## Explicit copying\n\nBecause of Python's behavior, a simple assignment will not create a copy of the data, but share the same data instead.\nIn the following example, when B is assigned to A, modifying data in A will also result in changes to tensor B.\nTo perform a deep copy of a tensor, users can use the tensor's member function `copy`, or they can directly use `copy.copy`.\nTo copy the shape of a tensor without copying its content, users can utilize the `same_shape` function,\nwhich creates a tensor with the same shape but with uninitialized data.\n\n    import copy\n    import TAT\n    \n    A = TAT.No.D.Tensor(233)\n    B = A\n    A[{}] = 1\n    print(B)\n    \n    C = TAT.No.D.Tensor(233)\n    D = C.copy()\n    C[{}] = 1\n    print(D)\n    \n    E = TAT.No.D.Tensor(233)\n    F = copy.copy(E)\n    E[{}] = 1\n    print(F)\n\n    {names:[],edges:[],blocks:[1]}\n    {names:[],edges:[],blocks:[233]}\n    {names:[],edges:[],blocks:[233]}\n\n\n## Elementwise operations\n\nUsers can apply custom functions to the elements of a tensor element-wise using the `map` function for out-of-place operations\nor the `transform_` function for in-place operations.\nAdditionally, there is a function called `set_`, which is similar to `transform_`, but it does not accept an input value.\nIn other words, `A.set_(f)` is equivalent to `A.transform_(lambda _: f())`.\n\n    import TAT\n    \n    A = TAT.No.D.Tensor([\"i\", \"j\"], [2, 2]).range_()\n    A.transform_(lambda x: x * x)\n    print(A)\n    \n    B = A.map(lambda x: x + 1)\n    print(B)\n    print(A)\n    \n    A.set_(iter([1, 6, 2, 5]).__next__)\n    print(A)\n\n    {names:[i,j],edges:[2,2],blocks:[0,1,4,9]}\n    {names:[i,j],edges:[2,2],blocks:[1,2,5,10]}\n    {names:[i,j],edges:[2,2],blocks:[0,1,4,9]}\n    {names:[i,j],edges:[2,2],blocks:[1,6,2,5]}\n\nIn practice, there are several elementwise operations that are commonly used,\nso the TAT Python interface provides individual functions to wrap them for convenience. These include:\n\n-   `A.reciprocal()`: Acts like `A.map(lambda x: 0 if x == 0 else 1 / x)`.\n-   `A.sqrt()`: Acts like `A.map(lambda x: x**(1 / 2))`.\n\n\n## Norm of a tensor\n\nUsers can compute the norm of a tensor using the following functions:\n\n-   `norm_2` for the 2-norm.\n-   `norm_max` for the &infin;-norm.\n-   `norm_num` for the 0-norm.\n-   `norm_sum` for the 1-norm.\n\n    import TAT\n    \n    A = TAT.No.D.Tensor([\"i\"], [6]).range_(1, 2)\n    print(A)\n    print(A.norm_2())\n    print(A.norm_max())\n    print(A.norm_num())\n    print(A.norm_sum())\n\n    {names:[i],edges:[6],blocks:[1,3,5,7,9,11]}\n    16.911534525287763\n    11.0\n    6.0\n    36.0\n\n\n## Filling random numbers into a tensor\n\nFilling a tensor with random numbers can be accomplished using the `set_` function,\nbut Python function calls can be relatively slow, and random filling operations might be frequently used.\nTo address this, the TAT Python interface provides two functions: `randn_` and `rand_`.\n\n-   `randn_`: This function fills the tensor with normally distributed random numbers.\n    It accepts optional arguments for specifying the mean (defaulting to 0) and standard deviation (defaulting to 1).\n-   `rand_`: This function fills the tensor with uniformly distributed random numbers.\n    It also accepts optional arguments for specifying the minimum (defaulting to 0) and maximum (defaulting to 1) values.\n\nBoth of these functions utilize the `std::mt19937_64` random engine, and users can set the seed for random number engine using `TAT.random.seed`.\n\n    import TAT\n    TAT.random.seed(2333)\n    A = TAT.No.D.Tensor([\"i\"], [10]).randn_()\n    print(A)\n    B = TAT.No.Z.Tensor([\"i\"], [10]).randn_()\n    print(B)\n\n    {names:[i],edges:[10],blocks:[0.766553,1.42783,-0.802786,0.231369,-0.144274,0.75302,-0.930606,-0.90363,1.58645,-1.66505]}\n    {names:[i],edges:[10],blocks:[0.93897-2.03094i,-1.04394+0.724667i,0.0607228+0.802331i,-0.0634779+0.261524i,-0.0182935-0.00331999i,-0.809166+0.358002i,0.108272+0.293261i,-0.685203-0.874357i,-1.02724+0.898064i,-1.16878-0.312219i]}\n\nCertainly, there are cases where users may want to use the TAT random number generator for generating random numbers outside of tensors.\nThis can be achieved through functions within the `TAT.random` submodule, which includes:\n\n-   `uniform_int`: Generates uniformly distributed random integers.\n-   `uniform_real`: Generates uniformly distributed random real numbers.\n-   `normal`: Generates normally distributed random numbers.\n\n    import TAT\n    \n    TAT.random.seed(2333)\n    a = TAT.random.uniform_int(0, 1)\n    print([a() for _ in range(10)])\n    b = TAT.random.uniform_real(0, 1)\n    print([b() for _ in range(10)])\n    c = TAT.random.normal(0, 1)\n    print([c() for _ in range(10)])\n\n    [1, 1, 1, 0, 1, 1, 1, 0, 0, 0]\n    [0.40352081782045557, 0.5919243832286168, 0.27290914845486797, 0.7042572953540996, 0.5525455768177127, 0.3527365854756287, 0.13938916269629487, 0.844959553591226, 0.6296832832042462, 0.8978555690178844]\n    [-0.018293519693094607, -0.8091660392771898, -0.0033199925772919928, 0.35800177574398406, 0.1082722439575567, -0.6852033252925772, 0.29326095246544526, -0.8743569677337741, -1.0272406882246077, -1.1687800551936816]\n\n\n## Setting range data into a tensor\n\nUsers can set a range of data into a tensor using `A.range_(first, step)`,\nwhich fills the tensor with data in the sequence of \\(first\\), \\(first+step\\), \\(first+step \\times 2\\), and so on.\nBy default, `first` is set to 0 and `step` is set to 1.\nIn practical tensor network state programming, this function is not frequently utilized\nand is primarily employed for generating examples to illustrate other functions discussed in this document.\n\n    import TAT\n    \n    A = TAT.FermiU1.C.Tensor([\"i\", \"j\", \"k\"], [\n        ([(-1, 2), (0, 2), (-2, 2)], True),\n        ([(0, 2), (1, 2)], False),\n        ([(0, 2), (1, 2)], False),\n    ]).range_(0, 1 + 1j)\n    print(A)\n\n    {names:[i,j,k],edges:[{arrow:1,segment:{-1:2,0:2,-2:2}},{arrow:0,segment:{0:2,1:2}},{arrow:0,segment:{0:2,1:2}}],blocks:{[-1,0,1]:[0,1+1i,2+2i,3+3i,4+4i,5+5i,6+6i,7+7i],[-1,1,0]:[8+8i,9+9i,10+10i,11+11i,12+12i,13+13i,14+14i,15+15i],[0,0,0]:[16+16i,17+17i,18+18i,19+19i,20+20i,21+21i,22+22i,23+23i],[-2,1,1]:[24+24i,25+25i,26+26i,27+27i,28+28i,29+29i,30+30i,31+31i]}}\n\n\n## Filling Zeros into a Tensor\n\nThe content of a tensor is not initialized by default in the TAT package. To manually initialize it with zeros, users can invoke the `zero_` function.\n\n    import TAT\n    \n    A = TAT.FermiU1.D.Tensor([\"i\", \"j\"], [\n        ([(0, 2), (-1, 2)], False),\n        ([(0, 2), (1, 2)], False),\n    ]).zero_()\n    print(A)\n\n    {names:[i,j],edges:[{arrow:0,segment:{0:2,-1:2}},{arrow:0,segment:{0:2,1:2}}],blocks:{[0,0]:[0,0,0,0],[-1,1]:[0,0,0,0]}}\n\n\n## Arithmetic scalar operations\n\nUsers can perform arithmetic scalar operations directly on tensors.\nWhen performing arithmetic operations between two tensors,\ntheir shapes should be the same except for the order of edges, as TAT can automatically transpose them as needed.\n\n    import TAT\n    \n    a = TAT.No.D.Tensor([\"i\"], [4]).range_(0, 1)\n    b = TAT.No.D.Tensor([\"i\"], [4]).range_(0, 10)\n    print(a)\n    print(b)\n    print(a + b)\n    print(a * b)\n    print(1 / a)\n    print(b - 1)\n    a *= 2\n    print(a)\n    b /= 2\n    print(b)\n\n    {names:[i],edges:[4],blocks:[0,1,2,3]}\n    {names:[i],edges:[4],blocks:[0,10,20,30]}\n    {names:[i],edges:[4],blocks:[0,11,22,33]}\n    {names:[i],edges:[4],blocks:[0,10,40,90]}\n    {names:[i],edges:[4],blocks:[inf,1,0.5,0.333333]}\n    {names:[i],edges:[4],blocks:[-1,9,19,29]}\n    {names:[i],edges:[4],blocks:[0,2,4,6]}\n    {names:[i],edges:[4],blocks:[0,5,10,15]}\n\n\n## The tensor conjugation\n\nConjugating a tensor induces a reversal of symmetry in all segments across every edge, while simultaneously altering the values of all elements within the tensor, as illustrated below.\n\n    import TAT\n    \n    A = TAT.BoseU1.Z.Tensor([\"i\", \"j\"], [\n        [(0, 2), (2, 4), (1, 1)],\n        [(0, 3), (-2, 1), (-1, 3)],\n    ]).range_(0, 1 + 1j)\n    B = A.conjugate()\n    print(A)\n    print(B)\n\n    {names:[i,j],edges:[{0:2,2:4,1:1},{0:3,-2:1,-1:3}],blocks:{[0,0]:[0,1+1i,2+2i,3+3i,4+4i,5+5i],[2,-2]:[6+6i,7+7i,8+8i,9+9i],[1,-1]:[10+10i,11+11i,12+12i]}}\n    {names:[i,j],edges:[{0:2,-2:4,-1:1},{0:3,2:1,1:3}],blocks:{[0,0]:[0,1-1i,2-2i,3-3i,4-4i,5-5i],[-2,2]:[6-6i,7-7i,8-8i,9-9i],[-1,1]:[10-10i,11-11i,12-12i]}}\n\nPlease note that, in the case of \\(U(1)\\) symmetry, the reversal of the irreducible representation results in its negation, whereas for \\(Z(2)\\) symmetry, the reversal remains unchanged.\n\nIn the case of a fermion tensor, the conjugation of the tensor, when contracted with the original one, may result in a non-positive number.\nThis peculiar phenomenon indicates that the metric of the fermion tensor is not positive-semidefinite.\nThis unusual occurrence can disrupt the plain gradient method in high-level programming.\nTo compute the conjugation with a fixed metric, users can utilize an argument named `trivial_metric=True` when calling the conjugate function, as demonstrated below.\nHowever, it's important to note that this metric fixing will lead to a situation where \\((AB)^\\dagger \\neq A^\\dagger B^\\dagger\\) .\n\n    import TAT\n    \n    A = TAT.FermiZ2.Z.Tensor([\"i\", \"j\"], [\n        ([(False, 2), (True, 4)], False),\n        ([(False, 3), (True, 1)], True),\n    ]).range_(0, 1 + 1j)\n    B = A.conjugate()\n    C = A.conjugate(trivial_metric=True)\n    print(A)\n    print(B)\n    print(C)\n    print(A.contract(B, {(\"i\", \"i\"), (\"j\", \"j\")}))\n    print(A.contract(C, {(\"i\", \"i\"), (\"j\", \"j\")}))\n\n    {names:[i,j],edges:[{arrow:0,segment:{0:2,1:4}},{arrow:1,segment:{0:3,1:1}}],blocks:{[0,0]:[0,1+1i,2+2i,3+3i,4+4i,5+5i],[1,1]:[6+6i,7+7i,8+8i,9+9i]}}\n    {names:[i,j],edges:[{arrow:1,segment:{0:2,1:4}},{arrow:0,segment:{0:3,1:1}}],blocks:{[0,0]:[0,1-1i,2-2i,3-3i,4-4i,5-5i],[1,1]:[-6+6i,-7+7i,-8+8i,-9+9i]}}\n    {names:[i,j],edges:[{arrow:1,segment:{0:2,1:4}},{arrow:0,segment:{0:3,1:1}}],blocks:{[0,0]:[0,1-1i,2-2i,3-3i,4-4i,5-5i],[1,1]:[6-6i,7-7i,8-8i,9-9i]}}\n    {names:[],edges:[],blocks:{[]:[-350]}}\n    {names:[],edges:[],blocks:{[]:[570]}}\n\n\n## The tensor contraction\n\nTo perform the contraction of two tensors, users can provide a set of edge pairs as argument to the `contract` function.\nEach pair consists of an edge from the first tensor to be contracted and the corresponding edge from the second tensor.\nIn the following example, edge 'i' of tensor A is contracted with edge 'a' of tensor B, and edge 'j' of tensor A is contracted with edge 'c' of tensor B.\n\n    import TAT\n    \n    A = TAT.No.D.Tensor([\"i\", \"j\", \"k\"], [2, 3, 4]).range_()\n    B = TAT.No.D.Tensor([\"a\", \"b\", \"c\", \"d\"], [2, 5, 3, 6]).range_()\n    C = A.contract(B, {(\"i\", \"a\"), (\"j\", \"c\")})\n    print(C)\n\n    {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]}\n\nSince the function `clear_symmetry` solely removes symmetry information without making any other modifications,\nthe symmetry-cleared tensor resulting from the contraction is equal to the contraction of the symmetry-cleared tensors individually.\n\n    import TAT\n    \n    a = TAT.BoseU1.D.Tensor([\"A\", \"B\", \"C\", \"D\"], [\n        [(-1, 1), (0, 1), (-2, 1)],\n        [(0, 1), (1, 2)],\n        [(0, 2), (1, 2)],\n        [(-2, 2), (-1, 1), (0, 2)],\n    ]).range_()\n    b = TAT.BoseU1.D.Tensor([\"E\", \"F\", \"G\", \"H\"], [\n        [(0, 2), (1, 1)],\n        [(-2, 1), (-1, 1), (0, 2)],\n        [(0, 1), (-1, 2)],\n        [(2, 2), (1, 1), (0, 2)],\n    ]).range_()\n    c = a.contract(b, {(\"B\", \"G\"), (\"D\", \"H\")})\n    \n    A = a.clear_symmetry()\n    B = b.clear_symmetry()\n    C = A.contract(B, {(\"B\", \"G\"), (\"D\", \"H\")})\n    print((c.clear_symmetry() - C).norm_2())\n\n    0.0\n\nThe same principle applies to fermion symmetry tensors.\n\n    import TAT\n    \n    a = TAT.FermiU1.D.Tensor([\"A\", \"B\", \"C\", \"D\"], [\n        ([(-1, 1), (0, 1), (-2, 1)], False),\n        ([(0, 1), (1, 2)], True),\n        ([(0, 2), (1, 2)], False),\n        ([(-2, 2), (-1, 1), (0, 2)], True),\n    ]).range_()\n    b = TAT.FermiU1.D.Tensor([\"E\", \"F\", \"G\", \"H\"], [\n        ([(0, 2), (1, 1)], False),\n        ([(-2, 1), (-1, 1), (0, 2)], True),\n        ([(0, 1), (-1, 2)], False),\n        ([(2, 2), (1, 1), (0, 2)], False),\n    ]).range_()\n    c = a.contract(b, {(\"B\", \"G\"), (\"D\", \"H\")})\n    \n    A = a.clear_symmetry()\n    B = b.clear_symmetry()\n    C = A.contract(B, {(\"B\", \"G\"), (\"D\", \"H\")})\n    print((c.clear_symmetry() - C).norm_2())\n\n    0.0\n\nSometimes, users may wish to construct a hypergraph that connects multiple edges (more than two) together.\nThis functionality is implemented using an additional argument in the `contract` function.\nThis argument is a set of edge names that specifies which edges should be fused together while keeping them as free edges without summation.\nIt's important to note that this type of fusion operation is not well-defined for symmetry tensors and can only be applied to non-symmetry tensors.\nThe following code snippet provides an example of this functionality:\n\n    import TAT\n    \n    A = TAT.No.D.Tensor([\"i\", \"j\", \"x\"], [2, 3, 5]).range_()\n    B = TAT.No.D.Tensor([\"a\", \"x\", \"c\", \"d\"], [2, 5, 3, 6]).range_()\n    C = A.contract(B, {(\"i\", \"a\"), (\"j\", \"c\")}, {\"x\"})\n    print(C)\n\n    {names:[x,d],edges:[5,6],blocks:[5970,6045,6120,6195,6270,6345,7734,7815,7896,7977,8058,8139,9714,9801,9888,9975,10062,10149,11910,12003,12096,12189,12282,12375,14322,14421,14520,14619,14718,14817]}\n\n\n## Edge renaming\n\nTo rename the edge names of a tensor, users can utilize the `edge_rename` function with a dictionary as an argument,\nwhere the keys represent the old names and the values represent the new names.\nIn the example provided, \"i\" is renamed to \"j\" and \"j\" is renamed to \"i\".\n\n    import TAT\n    \n    A = TAT.No.D.Tensor([\"i\", \"j\", \"k\"], [2, 3, 4]).range_()\n    B = A.edge_rename({\"i\": \"j\", \"j\": \"i\"})\n    print(A)\n    print(B)\n\n    {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]}\n    {names:[j,i,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]}\n\n\n## Tensor exponential\n\nSimilar to the matrix exponential, the tensor exponential is obtained by summing the power series of tensor contractions.\nTo specify the way to contract tensors, users should define the relations between edges using a set of pairs of two edge names.\nThese pairs identify the corresponding relations, and the two edges in each pair will be contracted during the tensor contraction calculations.\n\n    import TAT\n    \n    A = TAT.No.D.Tensor(\n        [\"i\", \"j\", \"k\", \"l\"],\n        [2, 3, 3, 2],\n    ).range_()\n    \n    B = A.exponential({(\"i\", \"l\"), (\"j\", \"k\")})\n    print(B)\n\n    {names:[j,i,k,l],edges:[3,2,3,2],blocks:[5.98438e+45,6.36586e+45,6.74734e+45,7.12882e+45,7.5103e+45,7.89178e+45,3.97807e+46,4.23166e+46,4.48524e+46,4.73883e+46,4.99242e+46,5.246e+46,1.72498e+46,1.83494e+46,1.9449e+46,2.05486e+46,2.16483e+46,2.27479e+46,5.10462e+46,5.43002e+46,5.75542e+46,6.08081e+46,6.40621e+46,6.73161e+46,2.85153e+46,3.0333e+46,3.21507e+46,3.39685e+46,3.57862e+46,3.76039e+46,6.23116e+46,6.62837e+46,7.02559e+46,7.4228e+46,7.82001e+46,8.21722e+46]}\n\n\n## Setting an identity tensor\n\nThere are situations where users may want to obtain a tensor equivalent to an identity matrix.\nThis can be achieved by setting a tensor to an identity tensor using the `identity_` function.\nThis function accepts the same arguments as the exponential function to identify the corresponding relations within the edges.\nThe example provided below sets the tensor A to an identity tensor in place. After setting, we have \\(A = \\delta_{il}\\delta_{jk}\\).\n\n    import TAT\n    \n    A = TAT.BoseU1.D.Tensor([\"i\", \"j\", \"k\", \"l\"], [\n        [(-1, 1), (0, 1), (+2, 1)],\n        [(-2, 2), (+1, 2), (0, 2)],\n        [(+2, 2), (-1, 2), (0, 2)],\n        [(+1, 1), (0, 1), (-2, 1)],\n    ]).identity_({(\"i\", \"l\"), (\"j\", \"k\")})\n    print(A)\n\n    {names:[i,j,k,l],edges:[{-1:1,0:1,2:1},{-2:2,1:2,0:2},{2:2,-1:2,0:2},{1:1,0:1,-2:1}],blocks:{[-1,-2,2,1]:[1,0,0,1],[-1,1,2,-2]:[0,0,0,0],[-1,1,-1,1]:[1,0,0,1],[-1,1,0,0]:[0,0,0,0],[-1,0,0,1]:[1,0,0,1],[0,-2,2,0]:[1,0,0,1],[0,1,-1,0]:[1,0,0,1],[0,0,2,-2]:[0,0,0,0],[0,0,-1,1]:[0,0,0,0],[0,0,0,0]:[1,0,0,1],[2,-2,2,-2]:[1,0,0,1],[2,-2,-1,1]:[0,0,0,0],[2,-2,0,0]:[0,0,0,0],[2,1,-1,-2]:[1,0,0,1],[2,0,0,-2]:[1,0,0,1]}}\n\n\n## Merging and splitting edges\n\nUsers have the ability to merge or split edges within a tensor using the functions `merge_edge` and `split_edge`.\nWhen merging edges, users need to provide a dictionary that maps from the new edge name to the list of old edge names,\nspecifying which edges should be merged into a single edge and the order of the edges before merging.\nThe interface for splitting edges is similar, but due to the information loss during edge merging,\nusers also need to specify the edge segment information at this stage.\nAn edge consists of two parts: segment information and a possible fermi-arrow.\nIn this context, fermi-arrow is not needed, as TAT will automatically derive it.\nFor non-symmetry tensors, the segment information can be replaced by the edge dimension in a straightforward manner.\nUsers are free to merge zero edges into one edge or split one edge into zero edges, which simplifies handling corner cases in high-level code.\n\n    import TAT\n    \n    A = TAT.FermiU1.D.Tensor([\"i\", \"j\", \"k\", \"l\"], [\n        ([(-1, 1), (0, 1), (+2, 1)], False),\n        ([(-2, 2), (+1, 2), (0, 2)], True),\n        ([(+2, 2), (-1, 2), (0, 2)], False),\n        ([(+1, 1), (0, 1), (-2, 1)], True),\n    ]).range_()\n    print(A)\n    \n    B = A.merge_edge({\"a\": [\"i\", \"k\"], \"b\": [], \"c\": [\"l\", \"j\"]})\n    print(B)\n    \n    C = B.split_edge({\n        \"a\": [\n    \t(\"i\", [(-1, 1), (0, 1), (+2, 1)]),\n    \t(\"k\", [(+2, 2), (-1, 2), (0, 2)]),\n        ],\n        \"b\": [],\n        \"c\": [\n    \t(\"l\", [(+1, 1), (0, 1), (-2, 1)]),\n    \t(\"j\", [(-2, 2), (+1, 2), (0, 2)]),\n        ]\n    })\n    print(C)\n    print((A - C).norm_2())\n\n    {names:[i,j,k,l],edges:[{arrow:0,segment:{-1:1,0:1,2:1}},{arrow:1,segment:{-2:2,1:2,0:2}},{arrow:0,segment:{2:2,-1:2,0:2}},{arrow:1,segment:{1:1,0:1,-2:1}}],blocks:{[-1,-2,2,1]:[0,1,2,3],[-1,1,2,-2]:[4,5,6,7],[-1,1,-1,1]:[8,9,10,11],[-1,1,0,0]:[12,13,14,15],[-1,0,0,1]:[16,17,18,19],[0,-2,2,0]:[20,21,22,23],[0,1,-1,0]:[24,25,26,27],[0,0,2,-2]:[28,29,30,31],[0,0,-1,1]:[32,33,34,35],[0,0,0,0]:[36,37,38,39],[2,-2,2,-2]:[40,41,42,43],[2,-2,-1,1]:[44,45,46,47],[2,-2,0,0]:[48,49,50,51],[2,1,-1,-2]:[52,53,54,55],[2,0,0,-2]:[56,57,58,59]}}\n    {names:[b,c,a],edges:[{arrow:0,segment:{0:1}},{arrow:1,segment:{-1:4,2:2,1:4,-2:4,0:2,-4:2}},{arrow:0,segment:{1:4,-2:2,-1:4,2:4,0:2,4:2}}],blocks:{[0,-1,1]:[-0,-1,-44,-45,-2,-3,-46,-47,-4,-5,52,53,-6,-7,54,55],[0,2,-2]:[8,9,10,11],[0,1,-1]:[-16,-17,-32,-33,-18,-19,-34,-35,-12,-13,24,25,-14,-15,26,27],[0,-2,2]:[20,21,48,49,22,23,50,51,28,29,56,57,30,31,58,59],[0,0,0]:[36,37,38,39],[0,-4,4]:[40,41,42,43]}}\n    {names:[l,j,i,k],edges:[{arrow:1,segment:{1:1,0:1,-2:1}},{arrow:1,segment:{-2:2,1:2,0:2}},{arrow:0,segment:{-1:1,0:1,2:1}},{arrow:0,segment:{2:2,-1:2,0:2}}],blocks:{[1,-2,-1,2]:[-0,-1,-2,-3],[1,-2,2,-1]:[-44,-45,-46,-47],[1,1,-1,-1]:[8,9,10,11],[1,0,-1,0]:[-16,-17,-18,-19],[1,0,0,-1]:[-32,-33,-34,-35],[0,-2,0,2]:[20,21,22,23],[0,-2,2,0]:[48,49,50,51],[0,1,-1,0]:[-12,-13,-14,-15],[0,1,0,-1]:[24,25,26,27],[0,0,0,0]:[36,37,38,39],[-2,-2,2,2]:[40,41,42,43],[-2,1,-1,2]:[-4,-5,-6,-7],[-2,1,2,-1]:[52,53,54,55],[-2,0,0,2]:[28,29,30,31],[-2,0,2,0]:[56,57,58,59]}}\n    0.0\n\nIt's crucial to note that when two fermion symmetry tensors with connected edges, which will be contracted,\nundergo merging or splitting of common edges, it results in the generation of a single sign.\nSo, users needs to specify which of the two tensors should contain the generated sign using the additional two arguments provided by the corresponding functions.\nIn the examples below, we initially contract the common edges \"i\" and \"j\" from connected tensors A1 and B1 to obtain tensor C1.\nSubsequently, we merge the two common edges \"i\" and \"j\" into a single common edge \"k\" for both tensors, resulting in tensors A2 and B2.\nAfterward, tensor C2 is obtained by contracting A2 and B2, demonstrating that C1 equals C2.\nIn this example, we apply the sign to B1 but not to A1, as we should apply it only once.\nMoreover, there is a third argument in the function, which consists of a set of edge names selected from the merged edges,\nand these particular edges are expected to exhibit behavior opposite to what is determined by the second argument.\nIn the case of splitting functions, the third argument should consist of a set of names representing edges that will exhibit opposite behavior when they are split.\n\n    import TAT\n    \n    TAT.random.seed(7)\n    \n    A1 = TAT.FermiZ2.D.Tensor([\"i\", \"j\", \"a\"], [\n        ([(False, 2), (True, 2)], False),\n        ([(False, 2), (True, 2)], False),\n        ([(False, 2), (True, 2)], True),\n    ]).randn_()\n    B1 = TAT.FermiZ2.D.Tensor([\"i\", \"j\", \"b\"], [\n        ([(False, 2), (True, 2)], True),\n        ([(False, 2), (True, 2)], True),\n        ([(False, 2), (True, 2)], False),\n    ]).randn_()\n    C1 = A1.contract(B1, {(\"i\", \"i\"), (\"j\", \"j\")})\n    \n    A2 = A1.merge_edge({\"k\": [\"i\", \"j\"]}, False)\n    B2 = B1.merge_edge({\"k\": [\"i\", \"j\"]}, True)\n    C2 = A2.contract(B2, {(\"k\", \"k\")})\n    \n    print(C1 - C2)\n\n    {names:[a,b],edges:[{arrow:1,segment:{0:2,1:2}},{arrow:0,segment:{0:2,1:2}}],blocks:{[0,0]:[0,0,0,0],[1,1]:[0,0,0,0]}}\n\n\n## Reversing fermi-arrow of edges\n\nThe fermi-arrow of two edges that are connected with each other can be reversed together using the `reversed_edge` function.\nIt's important to note that when reversing a pair of edges, a single sign is generated.\nTherefore, users need to specify which tensor the generated sign should be applied to.\nThis is handled by the last two arguments of the function.\nIn the example below, we first contract tensors A1 and B1 to obtain C1.\nThen, we reverse the edges of A1 and B1 that will be contracted to create new tensors A2 and B2.\nAfter reversing, we contract A2 and B2 to obtain C2. The code demonstrates that C1 and C2 are equal.\nWhen reversing, the second argument indicates whether to apply the sign to the current tensor.\nIn this example, we apply the sign to B1 but not to A1, as we should apply it only once.\nAdditionally, there is a third argument in the function, which consists of a set of names selected from the edges that have undergone reversal,\nand these specific edges are expected to exhibit behavior opposite to what is determined by the second argument.\n\n    import TAT\n    \n    TAT.random.seed(7)\n    \n    A1 = TAT.FermiZ2.D.Tensor([\"i\", \"j\"], [\n        ([(False, 2), (True, 2)], False),\n        ([(False, 2), (True, 2)], True),\n    ]).randn_()\n    B1 = TAT.FermiZ2.D.Tensor([\"i\", \"j\"], [\n        ([(False, 2), (True, 2)], False),\n        ([(False, 2), (True, 2)], True),\n    ]).randn_()\n    C1 = A1.contract(B1, {(\"i\", \"j\")})\n    \n    A2 = A1.reverse_edge({\"i\"}, False)\n    B2 = B1.reverse_edge({\"j\"}, True)\n    C2 = A2.contract(B2, {(\"i\", \"j\")})\n    \n    print(C1 - C2)\n\n    {names:[j,i],edges:[{arrow:1,segment:{0:2,1:2}},{arrow:0,segment:{0:2,1:2}}],blocks:{[0,0]:[0,0,0,0],[1,1]:[0,0,0,0]}}\n\n\n## QR decomposition on a tensor\n\nThe `qr` function can be used to perform QR decomposition on a tensor.\nTo use this function, users should provide the set of free edges of the tensor after decomposition,\nas well as the two edge names created as a result of the decomposition.\nIn the provided example, the fermion tensor A has three edges: \"i\", \"j\" and \"k\".\nDuring the QR decomposition, we configure that the edges of the Q tensor should include \"k\" only,\nwhile the remaining edges, namely \"i\" and \"j\", should be included in the R tensor.\nThe first argument of the qr function can be either 'q' or 'r', specifying whether the second argument represents the set of free edges of the Q tensor or the R tensor.\nAfter the QR decomposition, the Q tensor will have two edges: the original \"k\" edge from the input tensor and the edge created during the decomposition, which is named \"Q\".\nFor the R tensor, it should contain three edges, with two of them coming from the original tensor (\"i\" and \"j\") and the newly created edge, named \"R\".\n\n    import TAT\n    \n    A = TAT.FermiU1.D.Tensor([\"i\", \"j\", \"k\"], [\n        ([(-1, 2), (0, 2), (-2, 2)], True),\n        ([(0, 2), (1, 2)], False),\n        ([(0, 2), (1, 2)], False),\n    ]).range_()\n    \n    Q, R = A.qr('q', {\"k\"}, \"Q\", \"R\")\n    Q_dagger = Q.conjugate().edge_rename({\"Q\": \"Q'\"})\n    print(Q_dagger.contract(Q, {(\"k\", \"k\")}))\n    print((Q.contract(R, {(\"Q\", \"R\")}) - A).norm_max())\n\n    {names:[Q',Q],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]}}\n    3.552713678800501e-15\n\n\n## Singular value decomposition (SVD) on a tensor\n\nThe `svd` function can be used to perform SVD on a tensor.\nTo use this function, users need to provide the set of free edges of the tensor after decomposition,\nas well as the four edge names created as a result of the decomposition.\nIn the provided example, the fermion tensor A has three edges: \"i\", \"j\", and \"k\".\nDuring the SVD, we configure the edges of the U tensor to include only the \"k\" edge, while the remaining edges, namely \"i\" and \"j\", should be included in the V tensor.\nThe first argument of the svd function is the set of free edges of the U tensor.\nAfter the SVD, the U tensor will have two edges: the original \"k\" edge from the input tensor and the edge created during decomposition, which is named \"U\".\nFor the V tensor, it should contain three edges, with two of them coming from the original tensor (\"i\" and \"j\") and the newly created edge, named \"V\".\nAs for the S tensor, it is indeed a diagonal matrix with two edges, named \"SU\" and \"SV,\" as specified in the later two arguments.\nThe last argument, which represents the SVD dimension cut, can be set to -1 for no cutting (default behavior),\na positive integer for absolute dimension cutting, or a real number between 0 and 1 for relative dimension cutting.\n\n    import TAT\n    \n    A = TAT.FermiU1.D.Tensor([\"i\", \"j\", \"k\"], [\n        ([(-1, 2), (0, 2), (-2, 2)], True),\n        ([(0, 2), (1, 2)], False),\n        ([(0, 2), (1, 2)], False),\n    ]).range_()\n    \n    U, S, V = A.svd({\"k\"}, \"U\", \"V\", \"SU\", \"SV\", -1)\n    U_dagger = U.conjugate().edge_rename({\"U\": \"U'\"})\n    print(U_dagger.contract(U, {(\"k\", \"k\")}))\n    USV = U.contract(S, {(\"U\", \"SU\")}).contract(V, {(\"SV\", \"V\")})\n    print((USV - A).norm_max())\n\n    {names:[U',U],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]}}\n    1.0658141036401503e-14\n\n\n## The tensor tracing\n\nTo trace a subset of edges within a tensor, users can utilize the `trace` function.\nThis involves providing a set of pairs consisting of two edge names that are intended for tracing.\nIn the provided example, we perform a trace operation on tensor A, specifically targeting edges labeled \"j\" and \"k\".\nThis tensor encompasses three edges: \"i\", \"j\", and \"k\".\nConsequently, the outcome of this operation will yield a tensor with a solitary edge labeled \"i\".\n\n    import TAT\n    \n    A = TAT.FermiZ2.C.Tensor([\"i\", \"j\", \"k\"], [\n        ([(False, 2), (True, 2)], True),\n        ([(False, 2), (True, 2)], False),\n        ([(False, 2), (True, 2)], True),\n    ]).range_()\n    print(A)\n    B = A.trace({(\"j\", \"k\")})\n    print(B)\n\n    {names:[i,j,k],edges:[{arrow:1,segment:{0:2,1:2}},{arrow:0,segment:{0:2,1:2}},{arrow:1,segment:{0:2,1:2}}],blocks:{[0,0,0]:[0,1,2,3,4,5,6,7],[0,1,1]:[8,9,10,11,12,13,14,15],[1,0,1]:[16,17,18,19,20,21,22,23],[1,1,0]:[24,25,26,27,28,29,30,31]}}\n    {names:[i],edges:[{arrow:1,segment:{0:2,1:2}}],blocks:{[0]:[-16,-16]}}\n\nSpecifically tailored for non-symmetric tensors, similar to the contract operation,\nthis interface allows users to establish a connection between two edges within the same tensor while leaving them unsummarized.\nThis functionality is realized through the utilization of the second argument,\nwhich takes the form of a dictionary mapping new edge names to pairs of two existing edge names.\nIn the provided examples, a non-symmetric tensor is created, featuring five edges: \"i\", \"j\", \"k\", \"l\", and \"m\".\nDuring the tracing process, \"j\" and \"k\" are connected and combined, resulting in the omission of these two edges in the resulting tensor.\nOn the other hand, \"l\" and \"m\" are connected but not aggregated, leading to their consolidation into a single edge labeled \"n\" within the resultant tensor.\n\n    import TAT\n    \n    A = TAT.No.Z.Tensor(\n        [\"i\", \"j\", \"k\", \"l\", \"m\"],\n        [4, 3, 3, 2, 2],\n    ).range_()\n    print(A)\n    B = A.trace({(\"j\", \"k\")}, {\"n\": (\"l\", \"m\")})\n    print(B)\n\n    {names:[i,j,k,l,m],edges:[4,3,3,2,2],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,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143]}\n    {names:[n,i],edges:[2,4],blocks:[48,156,264,372,57,165,273,381]}\n\n\n## The tensor transposition\n\nIn practical tensor operations, manual tensor transposition is typically unnecessary.\nHowever, transposition becomes valuable when preparing tensors for external operations, such as MPI operations on tensor storage.\nThe `transpose` function accommodates this need by accepting a list of edge names that specify the desired edge order for the resulting tensor.\n\n    import TAT\n    \n    A = TAT.FermiZ2.C.Tensor([\"i\", \"j\", \"k\"], [\n        ([(False, 2), (True, 2)], True),\n        ([(False, 2), (True, 2)], False),\n        ([(False, 2), (True, 2)], True),\n    ]).range_()\n    print(A)\n    B = A.transpose([\"k\", \"j\", \"i\"])\n    print(B)\n\n    {names:[i,j,k],edges:[{arrow:1,segment:{0:2,1:2}},{arrow:0,segment:{0:2,1:2}},{arrow:1,segment:{0:2,1:2}}],blocks:{[0,0,0]:[0,1,2,3,4,5,6,7],[0,1,1]:[8,9,10,11,12,13,14,15],[1,0,1]:[16,17,18,19,20,21,22,23],[1,1,0]:[24,25,26,27,28,29,30,31]}}\n    {names:[k,j,i],edges:[{arrow:1,segment:{0:2,1:2}},{arrow:0,segment:{0:2,1:2}},{arrow:1,segment:{0:2,1:2}}],blocks:{[0,0,0]:[0,4,2,6,1,5,3,7],[0,1,1]:[-24,-28,-26,-30,-25,-29,-27,-31],[1,0,1]:[-16,-20,-18,-22,-17,-21,-19,-23],[1,1,0]:[-8,-12,-10,-14,-9,-13,-11,-15]}}\n\n\n## Symmetry operations\n\nWhile all interfaces accept integers, booleans, or tuples comprised of integers and booleans to represent symmetries,\noften referred to as irreducible representations, each symmetry type has its specific class.\nFor instance, there is `TAT.FermiZ2.Symmetry`, which can be instantiated using a boolean value.\nIn practice, it's worth mentioning that all interfaces perform an implicit conversion of the input to the appropriate symmetry type internally.\nFor all symmetry types, users have the flexibility to perform various operations,\nincluding addition of two symmetries,\nsubtraction of two symmetries,\nobtaining the negation of a symmetry,\ncomparing two symmetries,\nand retrieving the parity of the symmetry.\n\n    import TAT\n    \n    r1 = TAT.BoseZ2.Symmetry(False)\n    r2 = TAT.BoseZ2.Symmetry(True)\n    print(r1, r2)\n    print(r1 + r2, r1 - r2)\n    print(-r1, -r2)\n    print(r1 > r2, r1 < r2, r1 == r2)\n    print(r1.parity, r2.parity)\n    \n    s1 = TAT.FermiZ2.Symmetry(False)\n    s2 = TAT.FermiZ2.Symmetry(True)\n    print(s1, s2)\n    print(s1 + s2, s1 - s2)\n    print(-s1, -s2)\n    print(s1 > s2, s1 < s2, s1 == s2)\n    print(s1.parity, s2.parity)\n    \n    t1 = TAT.FermiU1.Symmetry(-2)\n    t2 = TAT.FermiU1.Symmetry(+3)\n    print(t1, t2)\n    print(t1 + t2, t1 - t2)\n    print(-t1, -t2)\n    print(t1 > t2, t1 < t2, t1 == t2)\n    print(t1.parity, t2.parity)\n\n    0 1\n    1 1\n    0 1\n    False True False\n    False False\n    0 1\n    1 1\n    0 1\n    False True False\n    False True\n    -2 3\n    1 -5\n    2 -3\n    False True False\n    False True\n\n\n## Edge operations\n\nSimilarly to symmetry types, edge types are also defined, and interfaces that accept edges will automatically perform implicit type conversion for input edge types.\nFor instance, `TAT.FermiU1.Edge` is the designated edge type utilized in all tensors within the submodule `TAT.FermiU1`.\nEdge types encompass several functions and attributes, including:\n\n-   `edge.arrow`: Retrieves the fermi arrow of the edge. It is always set to False for non-fermion symmetry edges and non-symmetry edges.\n-   `edge.dimension`: Obtains the total dimension of the edge.\n-   `edge.segments`: Provides a read-only list of segment pairs comprising symmetry and its corresponding local dimension.\n-   `edge.segments_size`: Determines the length of the segments list.\n-   `edge.conjugate()`: Computes the conjugated edge.\n-   `edge.dimension_by_symmetry(symmetry)`: Retrieves the local dimension based on the given symmetry.\n-   `edge.position_by_symmetry(symmetry)`: Retrieves the position in the segments list using the specified symmetry.\n-   `edge.<x>_by_<y>(...)`: Facilitates conversion between three indexing methods, where `<x>` and `<y>` can be either `index`, `coord`, or `point`.\n    In the context of `index`, it represents the total index across the entire edge.\n    In the case of `coord`, it consists of a pair denoting the position of the local segment within the segments list and the local index within that segment.\n    Lastly, for `point`, it comprises a pair consisting of the symmetry of the current segment and the local index within that segment.\n\n\n# FAQ\n\n\n## I get error message like this when `import TAT`\n\n    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)\n    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)\n    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)\n    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)\n\nThis issue may arise due to problems with older MPI versions, such as OpenMPI 2.1.1 on Ubuntu 18.04 LTS.\nIf you have compiled MPI support into PyTAT, you may need to load the MPI dynamic shared library manually before importing TAT.\nYou can do this by using `import ctypes` and `ctypes.CDLL(\"libmpi.so\", mode=ctypes.RTLD_GLOBAL)`.\nIt is recommended to refrain from integrating MPI support into TAT while compiling PyTAT, as we have no intention of using it.\nInstead, our preference is to utilize mpi4py directly within the high-level code.\n\n\n## I get error message like this when `import TAT`\n\n    Traceback (most recent call last):\n      File \"<stdin>\", line 1, in <module>\n    ImportError: /home/hzhangxyz/.local/lib/python3.10/site-packages/TAT.cpython-310-x86_64-linux-gnu.so: undefined symbol: cgesv_\n\nThis error arises due to the omission of linking LAPACK and BLAS libraries during the library compilation process.\nTo resolve this issue, you must either recompile the library with the correct compilation flags,\nor alternatively, you can include the LAPACK/BLAS library path in the `LD_PRELOAD` environment variable.\nFor instance, you can achieve this by executing the command `export LD_PRELOAD=/lib64/liblapack.so.3` before running Python.\n\n",
    "bugtrack_url": null,
    "license": "GPLv3",
    "summary": "python binding for TAT(TAT is A Tensor library)",
    "version": "0.3.16",
    "project_urls": {
        "Changelog": "https://github.com/USTC-TNS/TNSP/blob/main/CHANGELOG.org",
        "Homepage": "https://github.com/USTC-TNS/TNSP/tree/main/PyTAT",
        "Issues": "https://github.com/USTC-TNS/TNSP/issues",
        "Repository": "https://github.com/USTC-TNS/TNSP.git"
    },
    "split_keywords": [
        "tensor",
        "tensor",
        "network",
        "tensor",
        "network",
        "state",
        "peps",
        "mps",
        "quantum",
        "many",
        "body",
        "system"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8557b8c9c5ea2bc7b8552aa15b58514a5340e18b7a7c1ffc83531787ee1140c0",
                "md5": "557d8c2ca59f6e9f667e802c47dcde7c",
                "sha256": "a074d188489fdbafe15411fe2fdf5a8b3f0b35e53b3a8f1ca9050680ed91f84e"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "has_sig": false,
            "md5_digest": "557d8c2ca59f6e9f667e802c47dcde7c",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 11597308,
            "upload_time": "2024-03-20T08:23:47",
            "upload_time_iso_8601": "2024-03-20T08:23:47.086776Z",
            "url": "https://files.pythonhosted.org/packages/85/57/b8c9c5ea2bc7b8552aa15b58514a5340e18b7a7c1ffc83531787ee1140c0/pytat-0.3.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9769f50fc64cef1c5943e3cb149905927a3a7c8eeba3d484517025e70b7ad2d1",
                "md5": "09d0f60c694097482b6cc0ae30c5ff45",
                "sha256": "f28477f5051c35074187c373b10ec166ed930d5d00b19e2d7796f53bd5414c61"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "09d0f60c694097482b6cc0ae30c5ff45",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 18445480,
            "upload_time": "2024-03-20T08:23:50",
            "upload_time_iso_8601": "2024-03-20T08:23:50.731562Z",
            "url": "https://files.pythonhosted.org/packages/97/69/f50fc64cef1c5943e3cb149905927a3a7c8eeba3d484517025e70b7ad2d1/pytat-0.3.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b46ff5dc20ef216082fc1b4d508c0e481c1e811eb3edafb7b8ac22e11a367765",
                "md5": "4886be9bc7710cb82a1fc6bbcea70b04",
                "sha256": "ead11145f42fa3d9d6bbe98edae736580f69acca6dabdb9706e6bca1fb903723"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp310-cp310-musllinux_1_1_aarch64.whl",
            "has_sig": false,
            "md5_digest": "4886be9bc7710cb82a1fc6bbcea70b04",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 11109870,
            "upload_time": "2024-03-20T08:23:54",
            "upload_time_iso_8601": "2024-03-20T08:23:54.208406Z",
            "url": "https://files.pythonhosted.org/packages/b4/6f/f5dc20ef216082fc1b4d508c0e481c1e811eb3edafb7b8ac22e11a367765/pytat-0.3.16-cp310-cp310-musllinux_1_1_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b30688b6c70dcdcfba22118378d18c1ed6c21c4441888321b2ef40c0381c1e6e",
                "md5": "230a5fe04049100721f7047ede07e19f",
                "sha256": "f53403e6bcce485ae830d643c12fa073dab1a8d8baa85c44daafea6a3a7d6f10"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp310-cp310-musllinux_1_1_x86_64.whl",
            "has_sig": false,
            "md5_digest": "230a5fe04049100721f7047ede07e19f",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 16407052,
            "upload_time": "2024-03-20T08:23:56",
            "upload_time_iso_8601": "2024-03-20T08:23:56.927759Z",
            "url": "https://files.pythonhosted.org/packages/b3/06/88b6c70dcdcfba22118378d18c1ed6c21c4441888321b2ef40c0381c1e6e/pytat-0.3.16-cp310-cp310-musllinux_1_1_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3d4c3c23298a7eff03bc3a9e1f42155caffe2c86146eb1bc5940130fecc33003",
                "md5": "2eb77e34b7e00fc880a09e5f2163d85a",
                "sha256": "87babd3432a905861e5f4825e8eb868d926dcc62313d754055e4fde7e3193075"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp310-cp310-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "2eb77e34b7e00fc880a09e5f2163d85a",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 27300812,
            "upload_time": "2024-03-20T08:24:00",
            "upload_time_iso_8601": "2024-03-20T08:24:00.190088Z",
            "url": "https://files.pythonhosted.org/packages/3d/4c/3c23298a7eff03bc3a9e1f42155caffe2c86146eb1bc5940130fecc33003/pytat-0.3.16-cp310-cp310-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "dc1a75b7f486237f84bc11292efec868eeece161fe9197b4ae2f17f8db22bdb4",
                "md5": "76537db5632e9614b83ad9ec81089182",
                "sha256": "a0ffb078bed47a1b2b0626b3f2d212455d98cd65848504e6b4d1628264aa197b"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp311-cp311-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "76537db5632e9614b83ad9ec81089182",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 14302209,
            "upload_time": "2024-03-20T08:24:03",
            "upload_time_iso_8601": "2024-03-20T08:24:03.167209Z",
            "url": "https://files.pythonhosted.org/packages/dc/1a/75b7f486237f84bc11292efec868eeece161fe9197b4ae2f17f8db22bdb4/pytat-0.3.16-cp311-cp311-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9d22d85da7016a42d8497d4a4290d76aa860d22eabb6781734300483e6b834ff",
                "md5": "8afd04fe20193e5b259fad13184190b7",
                "sha256": "5622328e5f9168a648a77ac89c12d518253e68703e26cd8e4eac44b2a5f7ff32"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp311-cp311-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "8afd04fe20193e5b259fad13184190b7",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 10797299,
            "upload_time": "2024-03-20T08:24:05",
            "upload_time_iso_8601": "2024-03-20T08:24:05.906500Z",
            "url": "https://files.pythonhosted.org/packages/9d/22/d85da7016a42d8497d4a4290d76aa860d22eabb6781734300483e6b834ff/pytat-0.3.16-cp311-cp311-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e0f16ec4fef76d63e2bfc26d51ed888845d565e09e8964ec24a779553f1e0397",
                "md5": "8889f1c586c5bf358ffea2183ceb1008",
                "sha256": "2aa6348452780d38937e4044f27a0601c1d818cc3d38a09f01242dd0fd5913f0"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "has_sig": false,
            "md5_digest": "8889f1c586c5bf358ffea2183ceb1008",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 11592674,
            "upload_time": "2024-03-20T08:24:08",
            "upload_time_iso_8601": "2024-03-20T08:24:08.673641Z",
            "url": "https://files.pythonhosted.org/packages/e0/f1/6ec4fef76d63e2bfc26d51ed888845d565e09e8964ec24a779553f1e0397/pytat-0.3.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "48deb344341306ece35e89d8a1df15e829f9dd04cc5344661a0e86838d176448",
                "md5": "0a7799ef0a215e6bd18bb8c8e637dfca",
                "sha256": "6df9fc6de74cbd8fa65f2ade514919b22e8811589298a02ee19948cbb4c6840e"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "0a7799ef0a215e6bd18bb8c8e637dfca",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 18442711,
            "upload_time": "2024-03-20T08:24:11",
            "upload_time_iso_8601": "2024-03-20T08:24:11.303720Z",
            "url": "https://files.pythonhosted.org/packages/48/de/b344341306ece35e89d8a1df15e829f9dd04cc5344661a0e86838d176448/pytat-0.3.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2dffe692967cfb31c9d0b28887729280307b382712a63d2ef2223ca72de3be10",
                "md5": "05abd2977a279a9b65e49d1f788a2a4d",
                "sha256": "8a4add116fcdd42cda8dfd65d1a1c04fdfe9929b34918f357e1c7407a9ea247a"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp311-cp311-musllinux_1_1_aarch64.whl",
            "has_sig": false,
            "md5_digest": "05abd2977a279a9b65e49d1f788a2a4d",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 11113027,
            "upload_time": "2024-03-20T08:24:13",
            "upload_time_iso_8601": "2024-03-20T08:24:13.987506Z",
            "url": "https://files.pythonhosted.org/packages/2d/ff/e692967cfb31c9d0b28887729280307b382712a63d2ef2223ca72de3be10/pytat-0.3.16-cp311-cp311-musllinux_1_1_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0d38549c0795099522d681205f229a3f92027dd10f1e6901097207fd9d11df02",
                "md5": "365cf448f2726d430349afcd144db5d1",
                "sha256": "76d05d228723baddbaf23bec2b429b3a402f2cbc5dada0fde824887a293e7261"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp311-cp311-musllinux_1_1_x86_64.whl",
            "has_sig": false,
            "md5_digest": "365cf448f2726d430349afcd144db5d1",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 16411900,
            "upload_time": "2024-03-20T08:24:17",
            "upload_time_iso_8601": "2024-03-20T08:24:17.270853Z",
            "url": "https://files.pythonhosted.org/packages/0d/38/549c0795099522d681205f229a3f92027dd10f1e6901097207fd9d11df02/pytat-0.3.16-cp311-cp311-musllinux_1_1_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "144208c3975ceeea3b617b6b78f0aaf46bf99b98dacaeccd68aad6708726e41f",
                "md5": "addce6fed5d59a596d4baef1614a8844",
                "sha256": "c2458900851ff3c75fa0bb9821573ebe12ad17429c8f00e6e65b15b26fad7d7a"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp311-cp311-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "addce6fed5d59a596d4baef1614a8844",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 27302306,
            "upload_time": "2024-03-20T08:24:20",
            "upload_time_iso_8601": "2024-03-20T08:24:20.928345Z",
            "url": "https://files.pythonhosted.org/packages/14/42/08c3975ceeea3b617b6b78f0aaf46bf99b98dacaeccd68aad6708726e41f/pytat-0.3.16-cp311-cp311-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "978cbae0d1f5a729cc5bbab165f54cb2b41cd2c4478e45139586f02abb9c68d6",
                "md5": "14bceb588582280eecf9def102825b70",
                "sha256": "9e912d50c2b7ab7b28d2d92a27d608b946fbc629435decbb2459360d4fc0a8d0"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp312-cp312-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "14bceb588582280eecf9def102825b70",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 14452574,
            "upload_time": "2024-03-20T08:24:24",
            "upload_time_iso_8601": "2024-03-20T08:24:24.597024Z",
            "url": "https://files.pythonhosted.org/packages/97/8c/bae0d1f5a729cc5bbab165f54cb2b41cd2c4478e45139586f02abb9c68d6/pytat-0.3.16-cp312-cp312-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "71ed235964148efacc8fe506a2dc6245d321cb3d60cb9dc2909b6ee02103b2dc",
                "md5": "7ef1199f10a183f58a18865796df3be8",
                "sha256": "ad93c604fa11f07ee05921bb3e446d5b9f933fb69221e2a6aa3c7ef65ed09b68"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp312-cp312-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "7ef1199f10a183f58a18865796df3be8",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 10848845,
            "upload_time": "2024-03-20T08:24:27",
            "upload_time_iso_8601": "2024-03-20T08:24:27.848694Z",
            "url": "https://files.pythonhosted.org/packages/71/ed/235964148efacc8fe506a2dc6245d321cb3d60cb9dc2909b6ee02103b2dc/pytat-0.3.16-cp312-cp312-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5bd65aac2cb01dc0679b1ba25ee24bb95cde4dd63feadfac41adbde0f794a9f6",
                "md5": "c52a2564fe0dccd9151107c8a80dd89f",
                "sha256": "e7511a7e3fbfb460b7d055f216d5bb6b08c4bebaf6d3666ef337adaeecd88e44"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "has_sig": false,
            "md5_digest": "c52a2564fe0dccd9151107c8a80dd89f",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 11588943,
            "upload_time": "2024-03-20T08:24:30",
            "upload_time_iso_8601": "2024-03-20T08:24:30.857143Z",
            "url": "https://files.pythonhosted.org/packages/5b/d6/5aac2cb01dc0679b1ba25ee24bb95cde4dd63feadfac41adbde0f794a9f6/pytat-0.3.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "19c8f3c8db6af89e512cc61dd1099c127908723d4dc328af4c64060d0331b409",
                "md5": "68a92b4c95a61dcb256a9caf45cc643f",
                "sha256": "0f3eb06586e7d70a635e162cfffd575bd53daf198873faf9d45091b9578def16"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "68a92b4c95a61dcb256a9caf45cc643f",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 18443943,
            "upload_time": "2024-03-20T08:24:34",
            "upload_time_iso_8601": "2024-03-20T08:24:34.077660Z",
            "url": "https://files.pythonhosted.org/packages/19/c8/f3c8db6af89e512cc61dd1099c127908723d4dc328af4c64060d0331b409/pytat-0.3.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "402c45b5d6a1c98f62d7e13792848150263577b6dd3206eafb89484c5be7188c",
                "md5": "b68588eae7dd5009ca4a5be3865af66b",
                "sha256": "ebfb81e51c787ea710f9534f11095d06a89c84d815c0fea0707a3b7f23407cd4"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp312-cp312-musllinux_1_1_aarch64.whl",
            "has_sig": false,
            "md5_digest": "b68588eae7dd5009ca4a5be3865af66b",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 11130597,
            "upload_time": "2024-03-20T08:24:36",
            "upload_time_iso_8601": "2024-03-20T08:24:36.870302Z",
            "url": "https://files.pythonhosted.org/packages/40/2c/45b5d6a1c98f62d7e13792848150263577b6dd3206eafb89484c5be7188c/pytat-0.3.16-cp312-cp312-musllinux_1_1_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f9b7b077dc690d2ed76821fa178f339cb11ba7912933dc592828a30987f817bb",
                "md5": "18a4600878ddcc9e7b47ccab9c3fa9f3",
                "sha256": "b358ce815613b86f1a9ca384b9dbbcd1036cada156d2bf5d9fc5bca32703c59c"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp312-cp312-musllinux_1_1_x86_64.whl",
            "has_sig": false,
            "md5_digest": "18a4600878ddcc9e7b47ccab9c3fa9f3",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 16434928,
            "upload_time": "2024-03-20T08:24:39",
            "upload_time_iso_8601": "2024-03-20T08:24:39.754662Z",
            "url": "https://files.pythonhosted.org/packages/f9/b7/b077dc690d2ed76821fa178f339cb11ba7912933dc592828a30987f817bb/pytat-0.3.16-cp312-cp312-musllinux_1_1_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "4759cc5b8bcb9f8b00b317e87016b887887ff6364d6e4e64926399fe5f775b83",
                "md5": "75ad927021d001a58bc7baadc996465a",
                "sha256": "08006957cea6873c9f1ff7bb8674eb00d5080805a35d6d54c4613271e14d6d4c"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp312-cp312-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "75ad927021d001a58bc7baadc996465a",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 27510490,
            "upload_time": "2024-03-20T08:24:42",
            "upload_time_iso_8601": "2024-03-20T08:24:42.474229Z",
            "url": "https://files.pythonhosted.org/packages/47/59/cc5b8bcb9f8b00b317e87016b887887ff6364d6e4e64926399fe5f775b83/pytat-0.3.16-cp312-cp312-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5c48587027598c9bec5e0a0c6f917f5fbdcb97ec42116c7165593b6b8fbe38ea",
                "md5": "63b2df1282e512018ee7fba35565172d",
                "sha256": "c81aa8a7fbd2cc09ec675ee8aaf31bc032c064ca5fe1bd767cdeeaa6181939cc"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "has_sig": false,
            "md5_digest": "63b2df1282e512018ee7fba35565172d",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 11600987,
            "upload_time": "2024-03-20T08:24:45",
            "upload_time_iso_8601": "2024-03-20T08:24:45.949806Z",
            "url": "https://files.pythonhosted.org/packages/5c/48/587027598c9bec5e0a0c6f917f5fbdcb97ec42116c7165593b6b8fbe38ea/pytat-0.3.16-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "74cd6aa1385a607b7ea0ff0b1a2ba7a554d476ddcfa8cf545e8d1f5ec8b4ffbc",
                "md5": "f8ae2cf4c5f3e9a925a492a4edad0e94",
                "sha256": "7350ea06ba373925ca6fd4cb9080d5cffe1e1132e2ef51ec9798fe50f1fecc56"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "f8ae2cf4c5f3e9a925a492a4edad0e94",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 18444310,
            "upload_time": "2024-03-20T08:24:48",
            "upload_time_iso_8601": "2024-03-20T08:24:48.682279Z",
            "url": "https://files.pythonhosted.org/packages/74/cd/6aa1385a607b7ea0ff0b1a2ba7a554d476ddcfa8cf545e8d1f5ec8b4ffbc/pytat-0.3.16-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0abdad8886cdd0ff7c221aa106e291850d30f979e07288e01adf48712a43cd22",
                "md5": "4487c604fb7e7e317333d2f7125e263c",
                "sha256": "c978d89d1d7aace603675ddcf79606d311ea094aaf8550bc4920552e51851397"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp38-cp38-musllinux_1_1_aarch64.whl",
            "has_sig": false,
            "md5_digest": "4487c604fb7e7e317333d2f7125e263c",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 11105989,
            "upload_time": "2024-03-20T08:24:51",
            "upload_time_iso_8601": "2024-03-20T08:24:51.659205Z",
            "url": "https://files.pythonhosted.org/packages/0a/bd/ad8886cdd0ff7c221aa106e291850d30f979e07288e01adf48712a43cd22/pytat-0.3.16-cp38-cp38-musllinux_1_1_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "dfff55b314619140eed0e84a2647132f8f9082a84bfbd882d3b4ffeb8c7c7570",
                "md5": "3a579a127a7b723f1a7a6c66e7961f3d",
                "sha256": "7628bcdc1fa1324c1948b491f773b3433a3230a2743768e5b0d97811a60d59f9"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp38-cp38-musllinux_1_1_x86_64.whl",
            "has_sig": false,
            "md5_digest": "3a579a127a7b723f1a7a6c66e7961f3d",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 16413048,
            "upload_time": "2024-03-20T08:24:54",
            "upload_time_iso_8601": "2024-03-20T08:24:54.145960Z",
            "url": "https://files.pythonhosted.org/packages/df/ff/55b314619140eed0e84a2647132f8f9082a84bfbd882d3b4ffeb8c7c7570/pytat-0.3.16-cp38-cp38-musllinux_1_1_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d13ccde3191b4d1386bf60948382bc65a3dc872ede24360aed05ddfa1aaaf478",
                "md5": "23c08c878dd5a82d06c968df3e8fd6d0",
                "sha256": "8b899a11f2775c1dd72e30fc84db36429a3db5e1d4481b92cdabe8e8fab9b702"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "has_sig": false,
            "md5_digest": "23c08c878dd5a82d06c968df3e8fd6d0",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 11589919,
            "upload_time": "2024-03-20T08:24:56",
            "upload_time_iso_8601": "2024-03-20T08:24:56.855597Z",
            "url": "https://files.pythonhosted.org/packages/d1/3c/cde3191b4d1386bf60948382bc65a3dc872ede24360aed05ddfa1aaaf478/pytat-0.3.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6ce279413f2b3aa10a13f73483a0ff1616e3361cd527f477ad5596033f66c015",
                "md5": "a7c41aa6e1814adf4731d45f02f45e00",
                "sha256": "faabcb6ea8bfedfd784705e9cc9e97dc57a90bb84ae65edcf515be9a1f6a6f67"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "a7c41aa6e1814adf4731d45f02f45e00",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 18441220,
            "upload_time": "2024-03-20T08:24:59",
            "upload_time_iso_8601": "2024-03-20T08:24:59.857162Z",
            "url": "https://files.pythonhosted.org/packages/6c/e2/79413f2b3aa10a13f73483a0ff1616e3361cd527f477ad5596033f66c015/pytat-0.3.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "add035ef4de2abb4223f1245eaf0ae2395c6d5a760178835d5c51c1da698fb32",
                "md5": "ca54af6ec4293bd0b26113a338eef96c",
                "sha256": "a24b21f4c455f6936385dbc3197e18b8f0461b0d26c40d4eb8e7c62e0dc155c4"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp39-cp39-musllinux_1_1_aarch64.whl",
            "has_sig": false,
            "md5_digest": "ca54af6ec4293bd0b26113a338eef96c",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 11104674,
            "upload_time": "2024-03-20T08:25:03",
            "upload_time_iso_8601": "2024-03-20T08:25:03.251099Z",
            "url": "https://files.pythonhosted.org/packages/ad/d0/35ef4de2abb4223f1245eaf0ae2395c6d5a760178835d5c51c1da698fb32/pytat-0.3.16-cp39-cp39-musllinux_1_1_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1419123e31c0523d9a4f2d1836ee1bce6f8df9facf3c1ddfe540a7440cb347ee",
                "md5": "25b1a3577b74255ecc84949e41484033",
                "sha256": "bb1291b403045da86b43973d3570511c45b508a9e7155eba2b5a0d52a5f95b0b"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp39-cp39-musllinux_1_1_x86_64.whl",
            "has_sig": false,
            "md5_digest": "25b1a3577b74255ecc84949e41484033",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 16407797,
            "upload_time": "2024-03-20T08:25:05",
            "upload_time_iso_8601": "2024-03-20T08:25:05.892351Z",
            "url": "https://files.pythonhosted.org/packages/14/19/123e31c0523d9a4f2d1836ee1bce6f8df9facf3c1ddfe540a7440cb347ee/pytat-0.3.16-cp39-cp39-musllinux_1_1_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "589568de622391321758f133944290416ae6b1acb7a9a6f7508d3fc0d7b51d36",
                "md5": "7e972151d36b0bf87a30df4f6b04d291",
                "sha256": "c34cb398825568ecd34e7869944af7572b2d87fcae78d9f52fcf5b66dee684b4"
            },
            "downloads": -1,
            "filename": "pytat-0.3.16-cp39-cp39-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "7e972151d36b0bf87a30df4f6b04d291",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 27301186,
            "upload_time": "2024-03-20T08:25:09",
            "upload_time_iso_8601": "2024-03-20T08:25:09.745621Z",
            "url": "https://files.pythonhosted.org/packages/58/95/68de622391321758f133944290416ae6b1acb7a9a6f7508d3fc0d7b51d36/pytat-0.3.16-cp39-cp39-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-20 08:23:47",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "USTC-TNS",
    "github_project": "TNSP",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "pytat"
}
        
Elapsed time: 0.21429s