Basic tensor initialization and creation operations#
import yastn
Create tensors from scratch#
def test_syntax_tensor_creation_operations(self):
#
# Initialize several rank-4 tensors, with the following signature
# ___
# (-) 0--<--| a |--<--1 (+)
# (+) 2-->--|___|-->--3 (-)
#
# The signatures can be interpreted as tensor legs being directed: ingoing for (+)
# or outgoing for (-).
#
# The symmetry, U1, is specified in config_U1. We specify the charge
# sectors on all legs by tuple t, with its first member, t[0], defining charge
# sectors on the first leg, t[1] on second leg and so on.
# The corresponding dimensions of each charge sector are specified by tuple D
# with analogous structure as t.
#
# Then, upon creation, all blocks which respect charge conservation will be
# initialized and filled with either random numbers, ones, or zeros in the examples
# below.
#
# The dtype of the tensor elements as well as the device on which its data
# reside is given in config_U1.
#
leg1 = yastn.Leg(config_U1, s=-1, t=(-1, 0, 1), D=(1, 2, 3))
leg2 = yastn.Leg(config_U1, s=1, t=(-1, 1, 2), D=(4, 5, 6))
leg3 = yastn.Leg(config_U1, s=1, t=(-1, 1, 2), D=(7, 8, 9))
leg4 = yastn.Leg(config_U1, s=-1, t=(-1, 1, 2), D=(10, 11, 12))
a = yastn.rand(config=config_U1, legs=[leg1, leg2, leg3, leg4])
b = yastn.ones(config=config_U1, legs=[leg1, leg2, leg3, leg4])
c = yastn.zeros(config=config_U1, legs=[leg1, leg2, leg3, leg4])
#
# The identity tensor behaves as rank-2 tensor with automatic signature (1, -1)
# or (-1, 1). It is enough to provide charge sectors and their dimensions
# for single leg, the data for other leg is inferred automatically.
#
e = yastn.eye(config=config_U1,legs=leg1)
Create empty tensor and fill it block by block#
def test_syntax_create_empty_tensor_and_fill(self):
#
# Initialize empty rank-4 tensor, with the following signature
# ___
# (-) 0--<--| a |--<--1 (+)
# (+) 2-->--|___|-->--3 (-)
#
# The symmetry, U1, is specified in config_U1.
#
# Then, initialize some blocks with random values. The charges
# of the block, ts, are given as a tuple with the length identical to
# the rank of the tensor. Similarly, the dimensions of the block Ds.
#
d = yastn.Tensor(config=config_U1, s=(-1, 1, 1, -1))
d.set_block(ts=(1, -1, 2, 0), Ds=(2, 4, 9, 2), val='rand')
d.set_block(ts=(2, 0, 2, 0), Ds=(3, 3, 9, 2), val='rand')
#
# Once the dimension is assigned to charge sector on a leg of the tensor
# attempt to create block with different dimension will raise an error.
# In the example above sector with charge 2 on 3rd leg has dimension 9.
#
# Attempting to create new block with different dimension for the same
# sector 2 on 3rd leg throws an error
#
with self.assertRaises(yastn.YastnError):
d.set_block(ts=(2, 1, 2, 1), Ds=(3, 3, 10, 2), val='rand')
Clone, detach or copy tensors#
def test_clone_copy(self):
#
# create random U1 symmetric tensor and flag it for autograd
#
leg1 = yastn.Leg(config_U1, s=1, t=(-1, 0, 1), D=(2, 3, 4))
leg2 = yastn.Leg(config_U1, s=1, t=(-1, 1, 2), D=(2, 4, 5))
a = yastn.rand(config=config_U1, legs=[leg1, leg1.conj(), leg2.conj(), leg2])
a.requires_grad_(True)
#
# Clone the tensor a resulting in a new, numerically identical, tensor b.
# However, tensors a and b do not share data - their blocks are independent.
# Further operations on b would be correctly differentiated when computing gradients
# with respect to a.
b = a.clone()
assert b.requires_grad
assert yastn.are_independent(a, b)
#
# Tensor tracked by autograd can be "detached" from the computational
# graph. This might be useful, if one wishes to perform some computations
# with the tensor outside of autograd.
# The original and detached tensor still share data (blocks).
c = a.detach()
assert not c.requires_grad
assert not yastn.are_independent(a, c)
#
# Copy of tensor is both detached from the computational graph
# and does not share data with the original
d = a.copy()
assert not d.requires_grad
assert yastn.are_independent(a,d)
Serialization of symmetric tensors#
def test_syntax_tensor_export_import_operations(self):
#
# First, we crate a random U1 symmetric tensor
# Such tensor is stored as dict of non-zero blocks, indexed by charges
#
legs = [yastn.Leg(config_U1, s=-1, t=(-1, 0, 1), D=(1, 2, 3)),
yastn.Leg(config_U1, s=1, t=(-1, 1, 2), D=(4, 5, 6)),
yastn.Leg(config_U1, s=-1, t=(-1, 1, 2), D=(7, 8, 9))]
a= yastn.rand(config=config_U1, legs=legs)
#
# We can serialize symmetric tensors into 1-D vector, holding
# reshaped raw-data of blocks and dictionary, meta, which holds
# the symmetric structure of the tensors. Each entry of meta represents
# non-zero block indexed by charges and it points to location of 1-D vector
# where the raw data of that block is stored
#
vector, meta = yastn.compress_to_1d(a)
vector, meta = a.compress_to_1d(meta=meta)
tensor = yastn.decompress_from_1d(vector, meta)
#
# Tensors can be also serialized directly into basic Python dictionary
#
dictionary = yastn.save_to_dict(a)
dictionary = a.save_to_dict()
tensor = yastn.load_from_dict(config=config_U1, d=dictionary)