Skip to content

USTC-TNS/TNSP

Repository files navigation

TAT · https://img.shields.io/github/v/release/USTC-TNS/TAT.svg?style=flat-square https://img.shields.io/github/license/USTC-TNS/TAT.svg?style=flat-square https://img.shields.io/github/actions/workflow/status/USTC-TNS/TAT/build.yml.svg?style=flat-square https://img.shields.io/github/actions/workflow/status/USTC-TNS/TAT/doxygen.yml.svg?color=%237f7fff&label=reference&style=flat-square

TAT is a header-only c++ tensor library with support for Abelian symmetry tensor and fermi tensor

The name “TAT” is a recursive acronym for “TAT is A Tensor library!”, and it should be all uppercase

Prerequisites

  • c++ compiler with c++17 support(such as GCC 7.3+, Clang 6.0+)
  • LAPACK/BLAS or MKL
  • MPI(optional for parallel computing)
  • pybind11(optional for python binding)
  • numpy(optional in python binding to export data into numpy array)

Usage

Just include the file include/TAT/TAT.hpp and link LAPACK/BLAS or MKL at link time

For good practice, pass argument -I$path_to_TAT_root/include to compiler and use #include <TAT/TAT.hpp> in your source file

For MPI support, you need to define macro TAT_USE_MPI and use MPI compiler such as mpic++ (recommend) or pass correct flag to normal compiler(for expert)

Please check comment in file common_variable.hpp for some other macro options

Please notice that this library need proper compiler optimization option(-O2, -O3, -Ofast) for good performace

You can also use TAT as a cmake subdirectory, just use add_subdirectory(path_to_TAT_root) or find_package(TAT) and then target_link_libraries(your_target TAT) in your CMakeLists.txt

Python binding

Python binding is configured in CMakeLists.txt , use cmake and build target PyTAT

For other customed python module name, define TAT_PYTHON_MODULE as cmake variable

Refer to this for more about python binding

Use with emscripten

If you want to run a program using TAT in browser, which is very useful for demonstration

You can simply compile TAT with em++ (no mpi support, no doubt), and link liblapack.a, libblas.a, libf2c.a compiled from clapack-3.2.1

You can download them from here or compile by yourself

If you are using cmake, you need to put these three files into directory emscripten, then run emcmake cmake $path_to_TAT_root which will configure it automatically

Please notice that -s EMULATE_FUNCTION_POINTER_CASTS=1 is needed for em++ to compile TAT.

If clapack is compiled by yourself, several patch should be applied, including missing #include <stdio.h> in BLAS/SRC/xerbla.c, return type of s_copy, s_cat declaration in many lapack function.

Documents

Create tensor

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

auto A = TAT::Tensor<double, TAT::NoSymmetry>({"i", "j"}, {3, 4}).zero();
std::cout << A << "\n";
{names:[i,j],edges:[3,4],blocks:[0,0,0,0,0,0,0,0,0,0,0,0]}

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

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

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

auto A = TAT::Tensor<double, TAT::Z2Symmetry>({"i", "j"}, {{{0, 2}, {1, 4}}, {{0, 3}, {1, 1}}}).range();
std::cout << A << "\n";
{names:[i,j],edges:[{0:2,1:4},{0:3,1:1}],blocks:{[0,0]:[0,1,2,3,4,5],[1,1]:[6,7,8,9]}}

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

auto A = TAT::Tensor<double, TAT::Z2Symmetry>({"i", "j"}, {{{0, 2}, {1, 4}}, {{0, 3}, {1, 1}}}).range();
auto B = A.clear_symmetry();
std::cout << B << "\n";
{names:[i,j],edges:[6,4],blocks:[0,1,2,0,3,4,5,0,0,0,0,6,0,0,0,7,0,0,0,8,0,0,0,9]}

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

the U1 symmety edge can be more complex

auto A = TAT::Tensor<double, TAT::U1Symmetry>({"i", "j"}, {{{0, 2}, {2, 4}, {1, 1}}, {{0, 3}, {-2, 1}, {-1, 3}}}).range();
auto B = A.clear_symmetry();
std::cout << A << "\n";
std::cout << B << "\n";
{names:[i,j],edges:[{0:2,2:4,1:1},{0:3,-2:1,-1:3}],blocks:{[0,0]:[0,1,2,3,4,5],[2,-2]:[6,7,8,9],[1,-1]:[10,11,12]}}
{names:[i,j],edges:[7,7],blocks:[0,1,2,0,0,0,0,3,4,5,0,0,0,0,0,0,0,6,0,0,0,0,0,0,7,0,0,0,0,0,0,8,0,0,0,0,0,0,9,0,0,0,0,0,0,0,10,11,12]}

Please notice that the order of symmetry segment is important.

Access element of tensor

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

// Create a tensor and initialize it to zero
auto A = TAT::Tensor<double, TAT::NoSymmetry>({"i", "j"}, {3, 4}).zero();
// Set an element of tensor A to 3
A.at({{"i", 2}, {"j", 2}}) = 3;
// print tensor A
std::cout << A << "\n";
// print the element set as 3
std::cout << A.at({{"j", 2}, {"i", 2}}) << "\n";
{names:[i,j],edges:[3,4],blocks:[0,0,0,0,0,0,0,0,0,0,3,0]}
3

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

auto A = TAT::Tensor<double, TAT::U1Symmetry, std::string>({"i", "j"}, {{{0, 2}, {2, 4}, {1, 1}}, {{0, 3}, {-2, 1}, {-1, 3}}}).zero();
A.at({{"i", 1}, {"j", 2}}) = 233;
A.at({{"i", {2, 2}}, {"j", {-2, 0}}}) = 42;
// print tensor A
std::cout << A << "\n";
// print the element set
std::cout << A.at({{"i", {0, 1}}, {"j", {0, 2}}}) << "\n";
std::cout << A.at({{"j", 3}, {"i", 4}}) << "\n";
auto B = A.clear_symmetry();
std::cout << B.at({{"j", 3}, {"i", 4}}) << "\n";
{names:[i,j],edges:[{0:2,2:4,1:1},{0:3,-2:1,-1:3}],blocks:{[0,0]:[0,0,0,0,0,233],[2,-2]:[0,0,42,0],[1,-1]:[0,0,0]}}
233
42
42

Scalar operators

You can do scalar operators directly

// Create two rank-1 tensors
auto A = TAT::Tensor<double, TAT::NoSymmetry>({"i"}, {4});
auto B = TAT::Tensor<double, TAT::NoSymmetry>({"i"}, {4});
A.at({{"i", 0}}) = 1;
A.at({{"i", 1}}) = 2;
A.at({{"i", 2}}) = 3;
A.at({{"i", 3}}) = 4;
B.at({{"i", 0}}) = 10;
B.at({{"i", 1}}) = 20;
B.at({{"i", 2}}) = 30;
B.at({{"i", 3}}) = 40;

// Add two tensor
std::cout << A + B << "\n";

// A number over a tensor
std::cout << 1 / A << "\n";
{names:[i],edges:[4],blocks:[11,22,33,44]}
{names:[i],edges:[4],blocks:[1,0.5,0.333333,0.25]}

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

auto A = TAT::Tensor<double, TAT::U1Symmetry, std::string>({"i", "j"}, {{{0, 2}, {2, 4}, {1, 1}}, {{0, 3}, {-2, 1}, {-1, 3}}}).range();
auto B = TAT::Tensor<double, TAT::U1Symmetry, std::string>({"j", "i"}, {{{0, 3}, {-2, 1}, {-1, 3}}, {{0, 2}, {2, 4}, {1, 1}}}).range();
std::cout << A + B << "\n";
{names:[i,j],edges:[{0:2,2:4,1:1},{0:3,-2:1,-1:3}],blocks:{[0,0]:[0,3,6,4,7,10],[2,-2]:[12,14,16,18],[1,-1]:[20,22,24]}}

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

Rank-0 tensor and number

You can convert between rank-0 tensor and number directly

// Directly initialize a tensor with a number
auto A = TAT::Tensor<double, TAT::NoSymmetry>(233);
// Convert rank-0 tensor to number
double a = double(A);
std::cout << a << "\n";

auto B = TAT::Tensor<double, TAT::U1Symmetry>(233);
std::cout << B << "\n";
double b = double(B);
std::cout << b << "\n";

auto C = TAT::Tensor<std::complex<double>, TAT::U1Symmetry>({233, 666}, {"i", "j"}, {2, -2});
std::cout << C << "\n";
std::complex<double> c = std::complex<double>(C);
std::cout << c << "\n";
233
{names:[],edges:[],blocks:{[]:[233]}}
233
{names:[i,j],edges:[{2:1},{-2:1}],blocks:{[2,-2]:[233+666i]}}
(233,666)

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

Explicitly copy

auto A = TAT::Tensor<double, TAT::NoSymmetry>(233);
// By default, assigning a tensor to another tensor
// will let two tensor share the same data blocks
auto B = A;
// data of B is not changed when execute =A.at() = 1=
// but data copy happened implicitly and a warning will
// be thrown.
A.at() = 1;

auto C = TAT::Tensor<double, TAT::NoSymmetry>(233);
// Explicitly copy of tensor C
auto D = C.copy();
// No warning will be thrown
C.at() = 1;

Create same shape tensor and some elementwise operator

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

auto A = TAT::Tensor<double, TAT::NoSymmetry>({"i", "j"}, {2, 2});
A.at({{"i", 0}, {"j", 0}}) = 1;
A.at({{"i", 0}, {"j", 1}}) = 2;
A.at({{"i", 1}, {"j", 0}}) = 3;
A.at({{"i", 1}, {"j", 1}}) = 4;
// tensor B copy the shape of A but not content of A
auto B = A.same_shape<float>().zero();
std::cout << B << "\n";
{names:[i,j],edges:[2,2],blocks:[0,0,0,0]}

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

using Tensor = TAT::Tensor<double, TAT::NoSymmetry>;
auto A = Tensor({"i", "j"}, {2, 2});
// Another easy test data setter for tensor
// which will fill meanless test data into tensor
A.range();
// Every element is transformed by a function inplacely
A.transform([](auto x) {
   return x * x;
});
std::cout << A << "\n";

// Every element is transformed by a function outplacely
auto B = A.map([](auto x) {
   return x + 1;
});
std::cout << B << "\n";
std::cout << A << "\n";
{names:[i,j],edges:[2,2],blocks:[0,1,4,9]}
{names:[i,j],edges:[2,2],blocks:[1,2,5,10]}
{names:[i,j],edges:[2,2],blocks:[0,1,4,9]}

method to is used for type conversion.

// decltype(A) is TAT::Tensor<double, TAT::NoSymmetry>
auto A = TAT::Tensor<double, TAT::NoSymmetry>(233);
// Convert A to an complex tensor
// decltype(B) is  TAT::Tensor<std::complex<double>, TAT::NoSymmetry>
auto B = A.to<std::complex<double>>();

Norm

auto A = TAT::Tensor<double, TAT::NoSymmetry>({"i"}, {10}).range();
// Get maximum norm
std::cout << A.norm<-1>() << "\n";
// Get 0 norm
std::cout << A.norm<0>() << "\n";
// Get 1 norm
std::cout << A.norm<1>() << "\n";
// Get 2 norm
std::cout << A.norm<2>() << "\n";
9
10
45
16.8819

Contract

using Tensor = TAT::Tensor<double, TAT::NoSymmetry>;
auto A = Tensor({"i", "j", "k"}, {2, 3, 4}).range();
auto B = Tensor({"a", "b", "c", "d"}, {2, 5, 3, 6}).range();
// Contract edge i of A and edge a of B, edge j of A and edge c of B
auto C = A.contract(B, {{"i", "a"}, {"j", "c"}});
std::cout << C << "\n";
{names:[k,b,d],edges:[4,5,6],blocks:[4776,4836,4896,4956,5016,5076,5856,5916,5976,6036,6096,6156,6936,6996,7056,7116,7176,7236,8016,8076,8136,8196,8256,8316,9096,9156,9216,9276,9336,9396,5082,5148,5214,5280,5346,5412,6270,6336,6402,6468,6534,6600,7458,7524,7590,7656,7722,7788,8646,8712,8778,8844,8910,8976,9834,9900,9966,10032,10098,10164,5388,5460,5532,5604,5676,5748,6684,6756,6828,6900,6972,7044,7980,8052,8124,8196,8268,8340,9276,9348,9420,9492,9564,9636,10572,10644,10716,10788,10860,10932,5694,5772,5850,5928,6006,6084,7098,7176,7254,7332,7410,7488,8502,8580,8658,8736,8814,8892,9906,9984,10062,10140,10218,10296,11310,11388,11466,11544,11622,11700]}
#define edge(...) \
   { __VA_ARGS__ }
using Tensor = TAT::Tensor<double, TAT::U1Symmetry>;
auto a = Tensor{{"A", "B", "C", "D"}, {edge({-1, 1}, {0, 1}, {-2, 1}), edge({0, 1}, {1, 2}), edge({0, 2}, {1, 2}), edge({-2, 2}, {-1, 1}, {0, 2})}}
               .range();
auto b = Tensor{{"E", "F", "G", "H"}, {edge({0, 2}, {1, 1}), edge({-2, 1}, {-1, 1}, {0, 2}), edge({0, 1}, {-1, 2}), edge({2, 2}, {1, 1}, {0, 2})}}
               .range();
std::cout << a << "\n";
std::cout << b << "\n";
std::cout << TAT::contract(a, b, {{"B", "G"}, {"D", "H"}}) << "\n";
std::cout << TAT::Tensor<double, TAT::U1Symmetry>::contract(
                   a.transpose({"A", "C", "B", "D"}),
                   b.transpose({"G", "H", "E", "F"}),
                   {{"B", "G"}, {"D", "H"}})
          << "\n";
auto c = a.clear_symmetry();
auto d = b.clear_symmetry();
auto e = TAT::contract(a, b, {{"B", "G"}, {"D", "H"}}).clear_symmetry();
auto f = TAT::contract(c, d, {{"B", "G"}, {"D", "H"}});
std::cout << e << "\n";
std::cout << f << "\n";
{names:[A,B,C,D],edges:[{-1:1,0:1,-2:1},{0:1,1:2},{0:2,1:2},{-2:2,-1:1,0:2}],blocks:{[-1,0,1,0]:[0,1,2,3],[-1,1,0,0]:[4,5,6,7,8,9,10,11],[-1,1,1,-1]:[12,13,14,15],[0,0,0,0]:[16,17,18,19],[0,0,1,-1]:[20,21],[0,1,0,-1]:[22,23,24,25],[0,1,1,-2]:[26,27,28,29,30,31,32,33],[-2,1,1,0]:[34,35,36,37,38,39,40,41]}}
{names:[E,F,G,H],edges:[{0:2,1:1},{-2:1,-1:1,0:2},{0:1,-1:2},{2:2,1:1,0:2}],blocks:{[0,-2,0,2]:[0,1,2,3],[0,-1,0,1]:[4,5],[0,-1,-1,2]:[6,7,8,9,10,11,12,13],[0,0,0,0]:[14,15,16,17,18,19,20,21],[0,0,-1,1]:[22,23,24,25,26,27,28,29],[1,-2,0,1]:[30],[1,-2,-1,2]:[31,32,33,34],[1,-1,0,0]:[35,36],[1,-1,-1,1]:[37,38],[1,0,-1,0]:[39,40,41,42,43,44,45,46]}}
{names:[A,C,E,F],edges:[{-1:1,0:1,-2:1},{0:2,1:2},{0:2,1:1},{-2:1,-1:1,0:2}],blocks:{[-1,0,1,0]:[1062,1166,1386,1522],[-1,1,0,0]:[601,655,709,763,704,770,836,902],[-1,1,1,-1]:[1012,1229],[0,0,0,0]:[1515,1673,1831,1989,1618,1788,1958,2128],[0,0,1,-1]:[2898,3115],[0,1,0,-1]:[944,1420,1008,1517],[0,1,1,-2]:[4314,4604],[-2,1,1,0]:[5922,6506,6246,6862]}}
{names:[A,C,E,F],edges:[{-1:1,0:1,-2:1},{0:2,1:2},{0:2,1:1},{-2:1,-1:1,0:2}],blocks:{[-1,0,1,0]:[1062,1166,1386,1522],[-1,1,0,0]:[601,655,709,763,704,770,836,902],[-1,1,1,-1]:[1012,1229],[0,0,0,0]:[1515,1673,1831,1989,1618,1788,1958,2128],[0,0,1,-1]:[2898,3115],[0,1,0,-1]:[944,1420,1008,1517],[0,1,1,-2]:[4314,4604],[-2,1,1,0]:[5922,6506,6246,6862]}}
{names:[A,C,E,F],edges:[3,4,3,4],blocks:[0,0,0,0,0,0,0,0,0,0,1062,1166,0,0,0,0,0,0,0,0,0,0,1386,1522,0,0,601,655,0,0,709,763,0,1012,0,0,0,0,704,770,0,0,836,902,0,1229,0,0,0,0,1515,1673,0,0,1831,1989,0,2898,0,0,0,0,1618,1788,0,0,1958,2128,0,3115,0,0,0,944,0,0,0,1420,0,0,4314,0,0,0,0,1008,0,0,0,1517,0,0,4604,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5922,6506,0,0,0,0,0,0,0,0,0,0,6246,6862]}
{names:[A,C,E,F],edges:[3,4,3,4],blocks:[0,0,0,0,0,0,0,0,0,0,1062,1166,0,0,0,0,0,0,0,0,0,0,1386,1522,0,0,601,655,0,0,709,763,0,1012,0,0,0,0,704,770,0,0,836,902,0,1229,0,0,0,0,1515,1673,0,0,1831,1989,0,2898,0,0,0,0,1618,1788,0,0,1958,2128,0,3115,0,0,0,944,0,0,0,1420,0,0,4314,0,0,0,0,1008,0,0,0,1517,0,0,4604,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5922,6506,0,0,0,0,0,0,0,0,0,0,6246,6862]}

Since edge “B” and edge “G”, edge “D” and edge “H” have the compatible order, the contract result of clear_symmetry equals to clear_symmetry of contract result

Merge and split edge

using Tensor = TAT::Tensor<double, TAT::NoSymmetry>;
auto A = Tensor({"i", "j", "k"}, {2, 3, 4}).range();
// Merge edge i and edge j into a single edge a,
// and Merge no edge to get a trivial edge b
auto B = A.merge_edge({{"a", {"i", "j"}}, {"b", {}}});
std::cout << B << "\n";

// Split edge a back to edge i and edge j, and split
// trivial edge b to no edge
auto C = B.split_edge({{"b", {}}, {"a", {{"i", 2}, {"j", 3}}}});
std::cout << C << "\n";
{names:[b,a,k],edges:[1,6,4],blocks:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]}
{names:[i,j,k],edges:[2,3,4],blocks:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]}

Edge rename and transpose

using Tensor = TAT::Tensor<double, TAT::NoSymmetry>;
auto A = Tensor({"i", "j", "k"}, {2, 3, 4}).range();
// Rename edge i to edge x
auto B = A.edge_rename({{"i", "x"}});
std::cout << B << "\n";
// =edge_rename= is an outplace operator
std::cout << A << "\n";

// Transpose tensor A with specific order
auto C = A.transpose({"k", "j", "i"});
std::cout << C << "\n";
{names:[x,j,k],edges:[2,3,4],blocks:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]}
{names:[i,j,k],edges:[2,3,4],blocks:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]}
{names:[k,j,i],edges:[4,3,2],blocks:[0,12,4,16,8,20,1,13,5,17,9,21,2,14,6,18,10,22,3,15,7,19,11,23]}

SVD and QR decomposition

QR decomposition

#define f_edge(...) \
   { {__VA_ARGS__}, false }
#define t_edge(...) \
   { {__VA_ARGS__}, true }
using Tensor = TAT::Tensor<double, TAT::FermiSymmetry>;
auto A = Tensor({"i", "j", "k"}, {t_edge({-1, 2}, {0, 2}, {-2, 2}), f_edge({0, 2}, {1, 2}), f_edge({0, 2}, {1, 2})}).range();
// Do QR decomposition, specify Q matrix edge is edge k
// You can also write is as =Q, R = A.qr('r', {"i", "j"}, "Q", "R")=
// The last two argument is the name of new edges generated
// by QR decomposition
auto [Q, R] = A.qr('q', {"k"}, "Q", "R");
// Q is an unitary matrix, which edge name is Q and k
std::cout << Q.conjugate().edge_rename({{"Q", "Q1"}}).contract(Q.edge_rename({{"Q", "Q2"}}), {{"k", "k"}}) << "\n";
// Q R - A is 0
std::cout << (Q.contract(R, {{"Q", "R"}}) - A).norm<-1>() << "\n";
{names:[Q1,Q2],edges:[{arrow:0,segment:{1:2,0:2}},{arrow:1,segment:{-1:2,0:2}}],blocks:{[1,-1]:[1,0,0,1],[0,0]:[1,6.34378e-17,6.34378e-17,1]}}
3.55271e-15

SVD decomposition

#define f_edge(...) \
   { {__VA_ARGS__}, false }
#define t_edge(...) \
   { {__VA_ARGS__}, true }
using Tensor = TAT::Tensor<double, TAT::FermiSymmetry>;
auto A = Tensor{{"i", "j", "k"}, {t_edge({-1, 2}, {0, 2}, {-2, 2}), f_edge({0, 2}, {1, 2}), f_edge({0, 2}, {1, 2})}}.range();
// Do SVD decomposition with cut=3, if cut not specified,
// svd will not cut the edge.
// The first argument is edge set of matrix U, SVD does not
// supply function to specify edge set of matrix V like what
// is done in QR since SVD is symmetric between U and V.
// The later two argument is new edges generated in tensor U
// and tensor V. The later two argument is new edges of tensor
// S. and the last argument is dimension cut.
auto [U, S, V] = A.svd({"k"}, "U", "V", "SU", "SV", TAT::NoCut());
// U is an rank-3 unitary matrix
std::cout << U.conjugate().edge_rename({{"U", "U1"}}).contract(U.edge_rename({{"U", "U2"}}), {{"k", "k"}}) << "\n";
// U S V - A is a small value
std::cout << (U.contract(S, {{"U", "SU"}}).contract(V, {{"SV", "V"}}) - A).norm<-1>() << "\n";
{names:[U1,U2],edges:[{arrow:0,segment:{1:2,0:2}},{arrow:1,segment:{-1:2,0:2}}],blocks:{[1,-1]:[1,1.26068e-17,1.26068e-17,1],[0,0]:[1,2.22085e-17,2.22085e-17,1]}}
1.06581e-14

Identity, exponential and trace

using Tensor = TAT::Tensor<double, TAT::NoSymmetry>;
// Please notice that identity is INPLACE operator
// For any i, j, k, l, we have
// =A[{"i":i, "j":j, "k":k, "l":l}] = delta(i,l) * delta(j,k)=
auto A = Tensor({"i", "j", "k", "l"}, {2, 3, 3, 2}).identity({{"i", "l"}, {"j", "k"}});

// calculate matrix exponential B = exp(A)
// second argument is iteration steps, with default value 2
auto B = A.exponential({{"i", "l"}, {"j", "k"}}, 4);
std::cout << B << "\n";

// Calculate trace or partial trace of a tenso
// Here it calculate =A[{"i":i, "j":j, "k":k, "l":l}] * delta(i,l) * delta(j,k)=
auto C = A.trace({{"i", "l"}, {"j", "k"}});
std::cout << C << "\n";
{names:[j,i,k,l],edges:[3,2,3,2],blocks:[2.71828,0,0,0,0,0,0,2.71828,0,0,0,0,0,0,2.71828,0,0,0,0,0,0,2.71828,0,0,0,0,0,0,2.71828,0,0,0,0,0,0,2.71828]}
{names:[],edges:[],blocks:[6]}
#define edge(...) \
   { __VA_ARGS__ }
using Tensor = TAT::Tensor<double, TAT::U1Symmetry>;
auto A = Tensor(
               {"i", "j", "k", "l", "m"},
               {edge({-1, 2}, {0, 2}, {+1, 2}), edge({0, 2}, {1, 2}), edge({0, 2}, {-1, 2}), edge({0, 2}, {2, 3}), edge({0, 2}, {-2, 3})})
               .range();
auto id = Tensor({"k", "j", "m", "l"}, {edge({0, 2}, {1, 2}), edge({0, 2}, {-1, 2}), edge({0, 2}, {2, 3}), edge({0, 2}, {-2, 3})})
                .identity({{"j", "k"}, {"m", "l"}});
std::cout << A.trace({{"j", "k"}, {"l", "m"}}) << "\n";
std::cout << A.contract(id, {{"j", "j"}, {"k", "k"}, {"l", "l"}, {"m", "m"}}) << "\n";
{names:[i],edges:[{-1:2,0:2,1:2}],blocks:{[0]:[4734,5294]}}
{names:[i],edges:[{-1:2,0:2,1:2}],blocks:{[0]:[4734,5294]}}

IO

You can direclty read/write/load/dump tensor from/to a stream.

using Tensor = TAT::Tensor<double, TAT::NoSymmetry>;
auto A = Tensor({"i", "j", "k", "l"}, {2, 3, 3, 2}).identity({{"i", "l"}, {"j", "k"}});
std::stringstream text_stream;
// use operator<< to write to a stream
text_stream << A;
std::cout << text_stream.str() << "\n";
Tensor B;
// use operatoor>> to read from a stream
text_stream >> B;

std::stringstream binary_stream;
// use operator< to dump to a stream
binary_stream < A;
Tensor C;
// use operator> to load from a stream
binary_stream > C;
{names:[i,j,k,l],edges:[2,3,3,2],blocks:[1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1]}

Fill random number into tensor

c++ have its own way to generate random number, see this. So TAT will use this to generate random tensor.

Tensor::set is an inplace operator with one function as its argument, its will call this function to get every element of the tensor. It will be used to get random tensor with help of c++ own random library.

using Tensor = TAT::Tensor<double, TAT::NoSymmetry>;
std::random_device rd;
auto seed = rd();
std::default_random_engine engine(seed);
std::normal_distribution<double> dist{0, 1};
auto A = Tensor({"i", "j", "k"}, {2, 3, 4}).set([&]() {
   return dist(engine);
});
std::cout << A << "\n";
{names:[i,j,k],edges:[2,3,4],blocks:[0.311096,0.263491,0.39513,0.192521,0.959267,0.625805,0.714625,-1.64844,-0.0565222,-0.426022,-0.2058,0.855997,-2.64814,0.348301,-0.0801771,2.1666,0.304713,-0.917179,-0.539063,-0.13536,0.822066,-2.0096,1.22646,1.96164]}

Alternatives