Skip to main content

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.

Metadata

Release files for PyTAT 0.3.17

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for PyTAT 0.3.17
File
pytat-0.3.17-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details
pytat-0.3.17-cp312-cp312-musllinux_1_2_x86_64.whl CPython 3.12 CPython 3.12 Linux musl 1.2+ x86-64 Details
pytat-0.3.17-cp312-cp312-musllinux_1_2_aarch64.whl CPython 3.12 CPython 3.12 Linux musl 1.2+ ARM64 Details
pytat-0.3.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.17+ x86-64 Details
pytat-0.3.17-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.12 CPython 3.12 Linux glibc 2.17+ ARM64 Details
pytat-0.3.17-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details
pytat-0.3.17-cp312-cp312-macosx_10_14_x86_64.whl CPython 3.12 CPython 3.12 macOS 10.14+ x86-64 Details
pytat-0.3.17-cp311-cp311-win_amd64.whl CPython 3.11 CPython 3.11 Windows x86-64 Details
pytat-0.3.17-cp311-cp311-musllinux_1_2_x86_64.whl CPython 3.11 CPython 3.11 Linux musl 1.2+ x86-64 Details
pytat-0.3.17-cp311-cp311-musllinux_1_2_aarch64.whl CPython 3.11 CPython 3.11 Linux musl 1.2+ ARM64 Details
pytat-0.3.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.17+ x86-64 Details
pytat-0.3.17-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.11 CPython 3.11 Linux glibc 2.17+ ARM64 Details
pytat-0.3.17-cp311-cp311-macosx_11_0_arm64.whl CPython 3.11 CPython 3.11 macOS 11.0+ ARM64 Details
pytat-0.3.17-cp311-cp311-macosx_10_14_x86_64.whl CPython 3.11 CPython 3.11 macOS 10.14+ x86-64 Details
pytat-0.3.17-cp310-cp310-win_amd64.whl CPython 3.10 CPython 3.10 Windows x86-64 Details
pytat-0.3.17-cp310-cp310-musllinux_1_2_x86_64.whl CPython 3.10 CPython 3.10 Linux musl 1.2+ x86-64 Details
pytat-0.3.17-cp310-cp310-musllinux_1_2_aarch64.whl CPython 3.10 CPython 3.10 Linux musl 1.2+ ARM64 Details
pytat-0.3.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 CPython 3.10 Linux glibc 2.17+ x86-64 Details
pytat-0.3.17-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 CPython 3.10 Linux glibc 2.17+ ARM64 Details
pytat-0.3.17-cp39-cp39-win_amd64.whl CPython 3.9 CPython 3.9 Windows x86-64 Details
pytat-0.3.17-cp39-cp39-musllinux_1_2_x86_64.whl CPython 3.9 CPython 3.9 Linux musl 1.2+ x86-64 Details
pytat-0.3.17-cp39-cp39-musllinux_1_2_aarch64.whl CPython 3.9 CPython 3.9 Linux musl 1.2+ ARM64 Details
pytat-0.3.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.9 CPython 3.9 Linux glibc 2.17+ x86-64 Details
pytat-0.3.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.9 CPython 3.9 Linux glibc 2.17+ ARM64 Details
pytat-0.3.17-cp38-cp38-musllinux_1_2_x86_64.whl CPython 3.8 CPython 3.8 Linux musl 1.2+ x86-64 Details
pytat-0.3.17-cp38-cp38-musllinux_1_2_aarch64.whl CPython 3.8 CPython 3.8 Linux musl 1.2+ ARM64 Details
pytat-0.3.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.8 CPython 3.8 Linux glibc 2.17+ x86-64 Details
pytat-0.3.17-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.8 CPython 3.8 Linux glibc 2.17+ ARM64 Details

Total release size: 469.5 MB

Release files / pytat-0.3.17-cp312-cp312-win_amd64.whl

Download URL pytat-0.3.17-cp312-cp312-win_amd64.whl
Size 26.2 MB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
e37fc908047f27da9040d414a30b4cdcffef4cc6192e182cf5e37dbbbf7549a4
BLAKE2b-256 checksum
How to use checksums
eecb543b893cf64b3d50941ac9d8751b3227d470b357850687eb7c3a490d6e6b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp312-cp312-musllinux_1_2_x86_64.whl

Download URL pytat-0.3.17-cp312-cp312-musllinux_1_2_x86_64.whl
Size 20.1 MB
Tags CPython 3.12 Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
d6fdab2099bb9ce9796b754f5c78eab9f9f69c6839b2fb8b6a19150d771f6c6d
BLAKE2b-256 checksum
How to use checksums
19f2ab3380e6cf6607a3b71f7abd1e7f2908896c877bd4da3b39ba8e71da33b7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp312-cp312-musllinux_1_2_aarch64.whl

Download URL pytat-0.3.17-cp312-cp312-musllinux_1_2_aarch64.whl
Size 13.0 MB
Tags CPython 3.12 Linux musl 1.2+ ARM64
SHA-256 checksum
How to use checksums
f5369cc28bc7408900aa40d158db10123c5c32b496caaa8f74b7c9ec6560b5d5
BLAKE2b-256 checksum
How to use checksums
af2929c5932647468a49d05a051bcfc04881350e98dd5148c51f674e82d2bad4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL pytat-0.3.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 18.4 MB
Tags CPython 3.12 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
fa4ac2d2b8b7ee3ad126ae25f7f14ea204d7e09fc6f7ec511647d6998260e150
BLAKE2b-256 checksum
How to use checksums
78386e30472de0851f190c093cbbacb669f2e0590f57091e5ddad37be5506a63
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL pytat-0.3.17-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 11.6 MB
Tags CPython 3.12 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
fbfc047cb0b3ac45c426e880bc3361a4aada4065496f22bff5f84bf0e9f8bd82
BLAKE2b-256 checksum
How to use checksums
d478059705f4e74fe355c4be2b0c2034c4d65f48f9347069043f592a0819c199
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp312-cp312-macosx_11_0_arm64.whl

Download URL pytat-0.3.17-cp312-cp312-macosx_11_0_arm64.whl
Size 11.0 MB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
5712e19b3ebf25b7055986c2dc3a17522b4d2a8363b35de7054a8246e59341d7
BLAKE2b-256 checksum
How to use checksums
e39944a02f7a6011558bf073b93b27d1a8d49c08b340b4537dd98325a3bf5680
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp312-cp312-macosx_10_14_x86_64.whl

Download URL pytat-0.3.17-cp312-cp312-macosx_10_14_x86_64.whl
Size 13.7 MB
Tags CPython 3.12 macOS 10.14+ x86-64
SHA-256 checksum
How to use checksums
66eb3251940fe34f260bc4691e96c373be7260e7967e540110d6c9aaa163811d
BLAKE2b-256 checksum
How to use checksums
fb5b10bfc45b2352c40abca651c0d47791e546559d926161c49cd181c51ed4f9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp311-cp311-win_amd64.whl

Download URL pytat-0.3.17-cp311-cp311-win_amd64.whl
Size 26.2 MB
Tags CPython 3.11 Windows x86-64
SHA-256 checksum
How to use checksums
4c53577ebc80a37e44da746f20e5a636011262c680a007d2c7b5ea9a7079d3c5
BLAKE2b-256 checksum
How to use checksums
091c5f54cef88f493578c399bc57098c3f7dbc4ef11c082a0e82d8b2ef9bb18e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp311-cp311-musllinux_1_2_x86_64.whl

Download URL pytat-0.3.17-cp311-cp311-musllinux_1_2_x86_64.whl
Size 20.1 MB
Tags CPython 3.11 Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
714c209e38bd65f4960c391bac3b4e5b834a993bd5a251b4329c65caf8353569
BLAKE2b-256 checksum
How to use checksums
316773d4692fae95b7870aadcec0ec913bd75e3aa2ece2509809e584eb9e225f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp311-cp311-musllinux_1_2_aarch64.whl

Download URL pytat-0.3.17-cp311-cp311-musllinux_1_2_aarch64.whl
Size 13.0 MB
Tags CPython 3.11 Linux musl 1.2+ ARM64
SHA-256 checksum
How to use checksums
1c7713effcadd666c8822fc2e7ec700d62badcf190927cb20f203e4cddc3ef6f
BLAKE2b-256 checksum
How to use checksums
3080379ff0f20c5277a9110bb27f8551b36fe9bffed89188c27e984a64941d5a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL pytat-0.3.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 18.4 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
58e8219b55807f022660cf494d4a5e157914d9ebd93baf7fabae027968fb93c1
BLAKE2b-256 checksum
How to use checksums
35a0c1225f652f09649d3f382295b74fb2f17b76baeb8929e1ef035afcbdc6f4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL pytat-0.3.17-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 11.6 MB
Tags CPython 3.11 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
f487a4441f641373e3291347d4aa4bddf7b09b9ade9916c78b282299d1a3ba25
BLAKE2b-256 checksum
How to use checksums
4e0f4b431c258b056d9e92e020daab07ec3f43c073533f83dda772c09cc19e47
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp311-cp311-macosx_11_0_arm64.whl

Download URL pytat-0.3.17-cp311-cp311-macosx_11_0_arm64.whl
Size 10.9 MB
Tags CPython 3.11 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
5449c819e293fa3482898c2addc0a9fada225c9719cd15cb78acb384b225d656
BLAKE2b-256 checksum
How to use checksums
fa641a033d27e76d48efa7a929c07f9854529bf0e040799b7f16b4bbf4db50f6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp311-cp311-macosx_10_14_x86_64.whl

Download URL pytat-0.3.17-cp311-cp311-macosx_10_14_x86_64.whl
Size 13.6 MB
Tags CPython 3.11 macOS 10.14+ x86-64
SHA-256 checksum
How to use checksums
df366b3ef644331b103a7c32eed58e6e5713243d5f3eb49daa662c0993686c06
BLAKE2b-256 checksum
How to use checksums
c3f8e2f84449265348cf29477cabb5aeede28429cf505eb42796a4b5405026e3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp310-cp310-win_amd64.whl

Download URL pytat-0.3.17-cp310-cp310-win_amd64.whl
Size 26.2 MB
Tags CPython 3.10 Windows x86-64
SHA-256 checksum
How to use checksums
4f11118b7e861d91b64af1daa3e5816b55d3a9574697a090d9840e7518fd1deb
BLAKE2b-256 checksum
How to use checksums
867038afac2e9b5e4b93b543a7031c2bfbe30d22d6ad196107d5cc198d6d4645
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp310-cp310-musllinux_1_2_x86_64.whl

Download URL pytat-0.3.17-cp310-cp310-musllinux_1_2_x86_64.whl
Size 20.1 MB
Tags CPython 3.10 Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
e40e70219c75b85b3d831b95563ce9c453c54d7a7225e2ca62b017a242f604e3
BLAKE2b-256 checksum
How to use checksums
1159d4ffa4185aa17729cd9de530d6d54499b60ea71ac598a959b90498ba0db9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp310-cp310-musllinux_1_2_aarch64.whl

Download URL pytat-0.3.17-cp310-cp310-musllinux_1_2_aarch64.whl
Size 13.0 MB
Tags CPython 3.10 Linux musl 1.2+ ARM64
SHA-256 checksum
How to use checksums
a668f28771a4091444d4abfae0e15c38e59e25bd70a85fc4539effac93e539a0
BLAKE2b-256 checksum
How to use checksums
42965067d0d47ae66b99a5ba02e53d5a73bc9f6f6d625617720afe1c2654f237
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL pytat-0.3.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 18.4 MB
Tags CPython 3.10 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
6f73303a19fe3c0b1b6b7832dcb3d01626da02f8188b6a108f986ff57d930199
BLAKE2b-256 checksum
How to use checksums
1bb5da17980a4ad15c5e995f7638c4c1395cbb1d79d444085e0ad9d7b9761310
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL pytat-0.3.17-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 11.6 MB
Tags CPython 3.10 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
41a38d2d3814fdb52390b69f670204225a8e655fef41c4592770a6f1f248af82
BLAKE2b-256 checksum
How to use checksums
c902847a6c40f56a22b257db86042afe199c13b92d6184ef1c3325c08927f9be
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp39-cp39-win_amd64.whl

Download URL pytat-0.3.17-cp39-cp39-win_amd64.whl
Size 26.2 MB
Tags CPython 3.9 Windows x86-64
SHA-256 checksum
How to use checksums
41ae237c3a0fcff7397c4d7b45b96d3bdc5cd68d4ff051f05239575aeb3cf3ef
BLAKE2b-256 checksum
How to use checksums
9515b5ab07ac4272ba7c37e2cfc72df300315f42cf1f1d9b0c55a6d1430dc391
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp39-cp39-musllinux_1_2_x86_64.whl

Download URL pytat-0.3.17-cp39-cp39-musllinux_1_2_x86_64.whl
Size 20.1 MB
Tags CPython 3.9 Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
853e0b5014b330958d77b81862ccbad059ca856d997c1050df48fcd3255eec84
BLAKE2b-256 checksum
How to use checksums
59f08bb391fa3d2c68eab2b9ac86eb1d2b53310d02fb2112e8d5e514d1461c12
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp39-cp39-musllinux_1_2_aarch64.whl

Download URL pytat-0.3.17-cp39-cp39-musllinux_1_2_aarch64.whl
Size 13.0 MB
Tags CPython 3.9 Linux musl 1.2+ ARM64
SHA-256 checksum
How to use checksums
a8373899f49341c8f4e0cb4366509b86ecd614acffbc9af379b8fb2d3172e97c
BLAKE2b-256 checksum
How to use checksums
5b9dfdc41dc067a672a545038fb5879b72b91b6ef43ca6662e5d73b56bb23ac6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL pytat-0.3.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 18.4 MB
Tags CPython 3.9 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
9adfd6ad2082e24fa4a8a2890ee99010e0d10d44530383e38364aea9f7d3f2de
BLAKE2b-256 checksum
How to use checksums
271922cc6a752ed5ae62e7ffbf1f4c885af871217346299e799e877399aa92f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL pytat-0.3.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 11.6 MB
Tags CPython 3.9 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
0f0afe8bb7c9141cb5ec72e6c9b8d61f46fb7226c0f85859e7182197c16f9ebd
BLAKE2b-256 checksum
How to use checksums
d8fa5436ef1c5c454885b739f520a520d873b5e6de529149f5afcd5cc2fc0aa2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp38-cp38-musllinux_1_2_x86_64.whl

Download URL pytat-0.3.17-cp38-cp38-musllinux_1_2_x86_64.whl
Size 20.1 MB
Tags CPython 3.8 Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
fb90876b73a2148e94f3f56c410efd608dd0fff62cf83bbdc5abd644d4c11f22
BLAKE2b-256 checksum
How to use checksums
6cfafb245f1529c36b6b878cd8cf3ae9f1a5fcbea925b4a18e73dcf896e3ec71
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp38-cp38-musllinux_1_2_aarch64.whl

Download URL pytat-0.3.17-cp38-cp38-musllinux_1_2_aarch64.whl
Size 13.0 MB
Tags CPython 3.8 Linux musl 1.2+ ARM64
SHA-256 checksum
How to use checksums
eb54c4f555228d88f1ad00c33be050448e0610ddfe112d492d0518b8f69e5a36
BLAKE2b-256 checksum
How to use checksums
e9fcfd3499791531e83569423557d85608941ae2baeb6d7c5888132268f326e6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL pytat-0.3.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 18.4 MB
Tags CPython 3.8 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
344dfdcb90c6e5a50f15e746808bc48e0b4d15472ac73db4545f5a5954c8eda4
BLAKE2b-256 checksum
How to use checksums
7472ddb9cbcd9b642cc58726e48849cc6c61c62d9fcf32aca9fad1ecf9250788
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5

Release files / pytat-0.3.17-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL pytat-0.3.17-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 11.6 MB
Tags CPython 3.8 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
66acd426db3eb5b1e101b6a1184ac36453168cd2efbab854cd131c49f09a3e3e
BLAKE2b-256 checksum
How to use checksums
1272bad90c1992ab7e23321ea6e63c7b8d761fcb915b76337dfbb3a0c33eab90
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.1 CPython/3.12.5
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page