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)