Skip to main content

python binding for TAT(TAT is A Tensor library)

Project description

PyTAT is a Python wrapper for the C++ tensor library called 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 (with command python -m pip wheel .) or build (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. Remember to add --exports pyinit to the pyodide build arguments. We can't upload our pre-built emscripten platform distribution to pypi.org as they don't allow it. Instead, users can download a pre-built emscripten platform wheel from the release page.

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.

`` scalar type in C++ equivalent in Fortran
`S`, `float32` `float` `real(kind=4)`
`D`, `float64`, `float` `double` `real(kind=8)`
`C`, `complex64` `std::complex` `complex(kind=4)`
`Z`, `complex128`, `complex` `std::complex` `complex(kind=8)`

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

`` symmetry type in C++ conservation example
`No`, `Normal` `Symmetry<>` nothing
`BoseZ2`, `Z2` `Symmetry>` parity of spin z
`BoseU1`, `U1` `Symmetry>` spin z
`FermiU1` `Symmetry>` fermion number
`FermiU1BoseZ2` `Symmetry, bose>` fermion number & parity of spin z
`FermiU1BoseU1` `Symmetry, bose>` fermion number & spin z
`FermiZ2` `Symmetry>` parity of fermion number
`FermiU1FermiU1` `Symmetry, fermi>` numbers of two kinds of fermions

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 ∞-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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

pytat-0.3.17-cp312-cp312-win_amd64.whl (26.2 MB view details)

Uploaded CPython 3.12 Windows x86-64

pytat-0.3.17-cp312-cp312-musllinux_1_2_x86_64.whl (20.1 MB view details)

Uploaded CPython 3.12 musllinux: musl 1.2+ x86-64

pytat-0.3.17-cp312-cp312-musllinux_1_2_aarch64.whl (13.0 MB view details)

Uploaded CPython 3.12 musllinux: musl 1.2+ ARM64

pytat-0.3.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (18.4 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.17+ x86-64

pytat-0.3.17-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (11.6 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.17+ ARM64

pytat-0.3.17-cp312-cp312-macosx_11_0_arm64.whl (11.0 MB view details)

Uploaded CPython 3.12 macOS 11.0+ ARM64

pytat-0.3.17-cp312-cp312-macosx_10_14_x86_64.whl (13.7 MB view details)

Uploaded CPython 3.12 macOS 10.14+ x86-64

pytat-0.3.17-cp311-cp311-win_amd64.whl (26.2 MB view details)

Uploaded CPython 3.11 Windows x86-64

pytat-0.3.17-cp311-cp311-musllinux_1_2_x86_64.whl (20.1 MB view details)

Uploaded CPython 3.11 musllinux: musl 1.2+ x86-64

pytat-0.3.17-cp311-cp311-musllinux_1_2_aarch64.whl (13.0 MB view details)

Uploaded CPython 3.11 musllinux: musl 1.2+ ARM64

pytat-0.3.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (18.4 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.17+ x86-64

pytat-0.3.17-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (11.6 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.17+ ARM64

pytat-0.3.17-cp311-cp311-macosx_11_0_arm64.whl (10.9 MB view details)

Uploaded CPython 3.11 macOS 11.0+ ARM64

pytat-0.3.17-cp311-cp311-macosx_10_14_x86_64.whl (13.6 MB view details)

Uploaded CPython 3.11 macOS 10.14+ x86-64

pytat-0.3.17-cp310-cp310-win_amd64.whl (26.2 MB view details)

Uploaded CPython 3.10 Windows x86-64

pytat-0.3.17-cp310-cp310-musllinux_1_2_x86_64.whl (20.1 MB view details)

Uploaded CPython 3.10 musllinux: musl 1.2+ x86-64

pytat-0.3.17-cp310-cp310-musllinux_1_2_aarch64.whl (13.0 MB view details)

Uploaded CPython 3.10 musllinux: musl 1.2+ ARM64

pytat-0.3.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (18.4 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ x86-64

pytat-0.3.17-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (11.6 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ ARM64

pytat-0.3.17-cp39-cp39-win_amd64.whl (26.2 MB view details)

Uploaded CPython 3.9 Windows x86-64

pytat-0.3.17-cp39-cp39-musllinux_1_2_x86_64.whl (20.1 MB view details)

Uploaded CPython 3.9 musllinux: musl 1.2+ x86-64

pytat-0.3.17-cp39-cp39-musllinux_1_2_aarch64.whl (13.0 MB view details)

Uploaded CPython 3.9 musllinux: musl 1.2+ ARM64

pytat-0.3.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (18.4 MB view details)

Uploaded CPython 3.9 manylinux: glibc 2.17+ x86-64

pytat-0.3.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (11.6 MB view details)

Uploaded CPython 3.9 manylinux: glibc 2.17+ ARM64

pytat-0.3.17-cp38-cp38-musllinux_1_2_x86_64.whl (20.1 MB view details)

Uploaded CPython 3.8 musllinux: musl 1.2+ x86-64

pytat-0.3.17-cp38-cp38-musllinux_1_2_aarch64.whl (13.0 MB view details)

Uploaded CPython 3.8 musllinux: musl 1.2+ ARM64

pytat-0.3.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (18.4 MB view details)

Uploaded CPython 3.8 manylinux: glibc 2.17+ x86-64

pytat-0.3.17-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (11.6 MB view details)

Uploaded CPython 3.8 manylinux: glibc 2.17+ ARM64

File details

Details for the file pytat-0.3.17-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: pytat-0.3.17-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 26.2 MB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.5

File hashes

Hashes for pytat-0.3.17-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 e37fc908047f27da9040d414a30b4cdcffef4cc6192e182cf5e37dbbbf7549a4
MD5 0d6b67835d735fa345ccfdd236c61dbc
BLAKE2b-256 eecb543b893cf64b3d50941ac9d8751b3227d470b357850687eb7c3a490d6e6b

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp312-cp312-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 d6fdab2099bb9ce9796b754f5c78eab9f9f69c6839b2fb8b6a19150d771f6c6d
MD5 97820d3e3452ad6e179a368b1b9829c0
BLAKE2b-256 19f2ab3380e6cf6607a3b71f7abd1e7f2908896c877bd4da3b39ba8e71da33b7

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp312-cp312-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp312-cp312-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 f5369cc28bc7408900aa40d158db10123c5c32b496caaa8f74b7c9ec6560b5d5
MD5 057d1c95c45fb6c8bad4a2b15494d3b8
BLAKE2b-256 af2929c5932647468a49d05a051bcfc04881350e98dd5148c51f674e82d2bad4

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 fa4ac2d2b8b7ee3ad126ae25f7f14ea204d7e09fc6f7ec511647d6998260e150
MD5 a5906ae4d416d2f1305048f0b49ae09d
BLAKE2b-256 78386e30472de0851f190c093cbbacb669f2e0590f57091e5ddad37be5506a63

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 fbfc047cb0b3ac45c426e880bc3361a4aada4065496f22bff5f84bf0e9f8bd82
MD5 c1fc6a26e086d6c03304646f8f598f03
BLAKE2b-256 d478059705f4e74fe355c4be2b0c2034c4d65f48f9347069043f592a0819c199

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 5712e19b3ebf25b7055986c2dc3a17522b4d2a8363b35de7054a8246e59341d7
MD5 c07fddeaee008e26917ef6eca24abdce
BLAKE2b-256 e39944a02f7a6011558bf073b93b27d1a8d49c08b340b4537dd98325a3bf5680

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp312-cp312-macosx_10_14_x86_64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp312-cp312-macosx_10_14_x86_64.whl
Algorithm Hash digest
SHA256 66eb3251940fe34f260bc4691e96c373be7260e7967e540110d6c9aaa163811d
MD5 5fcd51208aa90e612327a08ab06b6c41
BLAKE2b-256 fb5b10bfc45b2352c40abca651c0d47791e546559d926161c49cd181c51ed4f9

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: pytat-0.3.17-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 26.2 MB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.5

File hashes

Hashes for pytat-0.3.17-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 4c53577ebc80a37e44da746f20e5a636011262c680a007d2c7b5ea9a7079d3c5
MD5 28bad067cb74c22fc6488d779592ee7d
BLAKE2b-256 091c5f54cef88f493578c399bc57098c3f7dbc4ef11c082a0e82d8b2ef9bb18e

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp311-cp311-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 714c209e38bd65f4960c391bac3b4e5b834a993bd5a251b4329c65caf8353569
MD5 47d681d93c695f70e069b41839f1b8fc
BLAKE2b-256 316773d4692fae95b7870aadcec0ec913bd75e3aa2ece2509809e584eb9e225f

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp311-cp311-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp311-cp311-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 1c7713effcadd666c8822fc2e7ec700d62badcf190927cb20f203e4cddc3ef6f
MD5 3a37c95cb23de8bff26d53998845bdba
BLAKE2b-256 3080379ff0f20c5277a9110bb27f8551b36fe9bffed89188c27e984a64941d5a

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 58e8219b55807f022660cf494d4a5e157914d9ebd93baf7fabae027968fb93c1
MD5 9acc0e80bd03d7bbb6f616d6215cf7e6
BLAKE2b-256 35a0c1225f652f09649d3f382295b74fb2f17b76baeb8929e1ef035afcbdc6f4

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 f487a4441f641373e3291347d4aa4bddf7b09b9ade9916c78b282299d1a3ba25
MD5 4a845f2501405fd078850d96b5216635
BLAKE2b-256 4e0f4b431c258b056d9e92e020daab07ec3f43c073533f83dda772c09cc19e47

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 5449c819e293fa3482898c2addc0a9fada225c9719cd15cb78acb384b225d656
MD5 e20be7cf240e23f4c39f128c07965e2c
BLAKE2b-256 fa641a033d27e76d48efa7a929c07f9854529bf0e040799b7f16b4bbf4db50f6

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp311-cp311-macosx_10_14_x86_64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp311-cp311-macosx_10_14_x86_64.whl
Algorithm Hash digest
SHA256 df366b3ef644331b103a7c32eed58e6e5713243d5f3eb49daa662c0993686c06
MD5 1f2b973589b9a0b3fd92ca90442fcfb2
BLAKE2b-256 c3f8e2f84449265348cf29477cabb5aeede28429cf505eb42796a4b5405026e3

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: pytat-0.3.17-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 26.2 MB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.5

File hashes

Hashes for pytat-0.3.17-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 4f11118b7e861d91b64af1daa3e5816b55d3a9574697a090d9840e7518fd1deb
MD5 113d119d6184a1da4d4db2f03e8e0326
BLAKE2b-256 867038afac2e9b5e4b93b543a7031c2bfbe30d22d6ad196107d5cc198d6d4645

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp310-cp310-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 e40e70219c75b85b3d831b95563ce9c453c54d7a7225e2ca62b017a242f604e3
MD5 27bfddef10aeab38a0e2f1fb1c06991e
BLAKE2b-256 1159d4ffa4185aa17729cd9de530d6d54499b60ea71ac598a959b90498ba0db9

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp310-cp310-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp310-cp310-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 a668f28771a4091444d4abfae0e15c38e59e25bd70a85fc4539effac93e539a0
MD5 bce0ef86a7ae1a12617d7a16c9528438
BLAKE2b-256 42965067d0d47ae66b99a5ba02e53d5a73bc9f6f6d625617720afe1c2654f237

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 6f73303a19fe3c0b1b6b7832dcb3d01626da02f8188b6a108f986ff57d930199
MD5 70d3ec26ba8069c699b4ad0881655841
BLAKE2b-256 1bb5da17980a4ad15c5e995f7638c4c1395cbb1d79d444085e0ad9d7b9761310

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 41a38d2d3814fdb52390b69f670204225a8e655fef41c4592770a6f1f248af82
MD5 47be83e34bd7806db52ee5cc613bb34d
BLAKE2b-256 c902847a6c40f56a22b257db86042afe199c13b92d6184ef1c3325c08927f9be

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp39-cp39-win_amd64.whl.

File metadata

  • Download URL: pytat-0.3.17-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 26.2 MB
  • Tags: CPython 3.9, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.5

File hashes

Hashes for pytat-0.3.17-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 41ae237c3a0fcff7397c4d7b45b96d3bdc5cd68d4ff051f05239575aeb3cf3ef
MD5 49ac0e49de079fcf8aefdbb927d87cab
BLAKE2b-256 9515b5ab07ac4272ba7c37e2cfc72df300315f42cf1f1d9b0c55a6d1430dc391

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp39-cp39-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp39-cp39-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 853e0b5014b330958d77b81862ccbad059ca856d997c1050df48fcd3255eec84
MD5 a34c128f5049107d325baab570a9ec31
BLAKE2b-256 59f08bb391fa3d2c68eab2b9ac86eb1d2b53310d02fb2112e8d5e514d1461c12

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp39-cp39-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp39-cp39-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 a8373899f49341c8f4e0cb4366509b86ecd614acffbc9af379b8fb2d3172e97c
MD5 33eff166170859c63db667cb171a79dc
BLAKE2b-256 5b9dfdc41dc067a672a545038fb5879b72b91b6ef43ca6662e5d73b56bb23ac6

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9adfd6ad2082e24fa4a8a2890ee99010e0d10d44530383e38364aea9f7d3f2de
MD5 84c18d510eed51c9b22573d61dd461f4
BLAKE2b-256 271922cc6a752ed5ae62e7ffbf1f4c885af871217346299e799e877399aa92f7

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 0f0afe8bb7c9141cb5ec72e6c9b8d61f46fb7226c0f85859e7182197c16f9ebd
MD5 d33c42f4f04d04a263cd1a6b36e25380
BLAKE2b-256 d8fa5436ef1c5c454885b739f520a520d873b5e6de529149f5afcd5cc2fc0aa2

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp38-cp38-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp38-cp38-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 fb90876b73a2148e94f3f56c410efd608dd0fff62cf83bbdc5abd644d4c11f22
MD5 25dc0738e150c75f91433cb6e09e3caf
BLAKE2b-256 6cfafb245f1529c36b6b878cd8cf3ae9f1a5fcbea925b4a18e73dcf896e3ec71

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp38-cp38-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp38-cp38-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 eb54c4f555228d88f1ad00c33be050448e0610ddfe112d492d0518b8f69e5a36
MD5 4f860128fe1fe058f562f59398733585
BLAKE2b-256 e9fcfd3499791531e83569423557d85608941ae2baeb6d7c5888132268f326e6

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 344dfdcb90c6e5a50f15e746808bc48e0b4d15472ac73db4545f5a5954c8eda4
MD5 2510293d260291b052661ef8a6b1695a
BLAKE2b-256 7472ddb9cbcd9b642cc58726e48849cc6c61c62d9fcf32aca9fad1ecf9250788

See more details on using hashes here.

File details

Details for the file pytat-0.3.17-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pytat-0.3.17-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 66acd426db3eb5b1e101b6a1184ac36453168cd2efbab854cd131c49f09a3e3e
MD5 d3b0641347e09966a85be4d74594c1f4
BLAKE2b-256 1272bad90c1992ab7e23321ea6e63c7b8d761fcb915b76337dfbb3a0c33eab90

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page