TAT ·

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
- 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)
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 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.
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.
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
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.
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.
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 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>>();
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
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
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]}
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]}
#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
#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
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]}}
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]}
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]}