Example #1
0
def construct_comm_opchain(opchain):
    """Construct operator chain on enlarged physical space to realize commutator."""
    # -opchain acting from the left
    oplist = [np.kron(op, np.identity(len(op))) for op in opchain.oplist]
    oplist[0] *= -1
    opcL = ptn.OpChain(oplist, opchain.qD)
    # opchain acting from the right
    oplist = [np.kron(np.identity(len(op)), op.T) for op in opchain.oplist]
    opcR = ptn.OpChain(oplist, opchain.qD)
    return [opcL, opcR]
Example #2
0
    def test_single_site(self):

        # number of lattice sites
        L = 10

        # time step can have both real and imaginary parts;
        # for real-time evolution use purely imaginary dt!
        dt = 0.02 - 0.05j
        # number of steps
        numsteps = 12

        # construct matrix product operator representation of Heisenberg Hamiltonian
        J =  4.0/3
        D =  5.0/13
        h = -2.0/7
        mpoH = ptn.heisenberg_XXZ_MPO(L, J, D, h)

        # fix total spin quantum number of wavefunction (trailing virtual bond)
        spin_tot = 2

        # enumerate all possible virtual bond quantum numbers (including multiplicities);
        # will be implicitly reduced by orthonormalization steps below
        qD = [np.array([0])]
        for i in range(L-1):
            qD.append(np.sort(np.array([q + mpoH.qd for q in qD[-1]]).reshape(-1)))
        qD.append(np.array([2*spin_tot]))

        # initial wavefunction as MPS with random entries
        psi = ptn.MPS(mpoH.qd, qD, fill='random')
        psi.orthonormalize(mode='left')
        psi.orthonormalize(mode='right')
        # effectively clamp virtual bond dimension of initial state
        Dinit = 8
        for i in range(L):
            psi.A[i][:, Dinit:, :] = 0
            psi.A[i][:, :, Dinit:] = 0
        # orthonormalize again
        psi.orthonormalize(mode='left')

        self.assertEqual(psi.qD[-1][0], 2*spin_tot,
            msg='trailing bond quantum number must not change during orthonormalization')

        # total spin operator as MPO
        Sztot = ptn.local_opchains_to_MPO(mpoH.qd, L, [ptn.OpChain([np.diag([0.5, -0.5])], [])])

        # explicitly compute average spin
        spin_avr = ptn.operator_average(psi, Sztot)
        self.assertAlmostEqual(abs(spin_avr - spin_tot), 0, delta=1e-14,
            msg='average spin must be equal to prescribed value')

        # reference time evolution
        psi_ref = np.dot(expm(-dt*numsteps * mpoH.as_matrix()), psi.as_vector())

        # run TDVP time evolution
        ptn.integrate_local_singlesite(mpoH, psi, dt, numsteps, numiter_lanczos=5)

        # compare time-evolved wavefunctions
        self.assertAlmostEqual(np.linalg.norm(psi.as_vector() - psi_ref), 0, delta=2e-5,
            msg='time-evolved wavefunction obtained by single-site MPS time evolution must match reference')
Example #3
0
def heisenberg_XXZ_comm_MPO(L, J, D, h):
    """Construct commutator with XXZ Heisenberg Hamiltonian
    'sum J X X + J Y Y + D Z Z - h Z' on a 1D lattice as MPO."""
    # physical quantum numbers (multiplied by 2)
    qd = np.array([1, -1])
    # spin operators
    Sup = np.array([[0., 1.], [0., 0.]])
    Sdn = np.array([[0., 0.], [1., 0.]])
    Sz = np.array([[0.5, 0.], [0., -0.5]])
    # local two-site and single-site terms
    lopchains = [
        ptn.OpChain([0.5 * J * Sup, Sdn], [2]),
        ptn.OpChain([0.5 * J * Sdn, Sup], [-2]),
        ptn.OpChain([D * Sz, Sz], [0]),
        ptn.OpChain([-h * Sz], [])
    ]
    # convert to MPO form, with local terms acting either on first or second physical dimension
    locopchains = []
    for opchain in lopchains:
        locopchains += construct_comm_opchain(opchain)
    return ptn.local_opchains_to_MPO(ptn.qnumber_flatten([qd, -qd]), L,
                                     locopchains)
Example #4
0
    def test_from_opchains(self):

        # dimensions
        d = 4
        L = 5

        # physical quantum numbers
        qd = np.random.randint(-2, 3, size=d)

        # fictitious operator chains
        opchains = []
        n = np.random.randint(20)
        for _ in range(n):
            istart = np.random.randint(L)
            length = np.random.randint(1, L - istart + 1)
            oplist = [randn_complex((d, d)) for _ in range(length)]
            qD = np.random.randint(-2, 3, size=length - 1)
            # enforce sparsity structure dictated by quantum numbers
            qDpad = np.pad(qD, 1, mode='constant')
            for i in range(length):
                mask = ptn.qnumber_outer_sum(
                    [qd + qDpad[i], -(qd + qDpad[i + 1])])
                oplist[i] = np.where(mask == 0, oplist[i], 0)
            opchains.append(ptn.OpChain(oplist, qD, istart))

        # construct MPO representation corresponding to operator chains
        mpo0 = ptn.MPO.from_opchains(qd, L, opchains)

        for i in range(mpo0.nsites):
            self.assertTrue(
                ptn.is_qsparse(
                    mpo0.A[i],
                    [mpo0.qd, -mpo0.qd, mpo0.qD[i], -mpo0.qD[i + 1]]),
                msg='sparsity pattern of MPO tensors must match quantum numbers'
            )

        # construct full Hamiltonian from operator chains, as reference
        Href = sum([opc.as_matrix(d, L) for opc in opchains])

        # compare
        self.assertAlmostEqual(
            np.linalg.norm(mpo0.as_matrix() - Href),
            0.,
            delta=1e-10,
            msg=
            'full merging of MPO must be equal to matrix representation of operator chains'
        )
Example #5
0
    def test_as_matrix(self):

        # dimensions
        d = 3
        L = 6

        # create random operator chain
        oplist = [
            np.random.normal(size=(d, d)) + 1j * np.random.normal(size=(d, d))
            for _ in range(2)
        ]
        opchain = ptn.OpChain(oplist=oplist, qD=[0], istart=3)

        # matrix representation on full Hilbert space
        A = opchain.as_matrix(d, L)

        # check dimensions
        self.assertEqual(A.shape, (d**L, d**L), msg='matrix dimensions')

        # pad identities on the left and check if matrix representation still matches
        opchain.pad_identities_left(d)
        A1 = opchain.as_matrix(d, L)
        self.assertEqual(
            np.linalg.norm(A1 - A),
            0,
            msg=
            'matrix representation after padding identities on the left must remain unchanged'
        )

        # pad identities on the right and check if matrix representation still matches
        opchain.pad_identities_right(d, L)
        self.assertEqual(opchain.length, L, msg='operator chain length')
        A2 = opchain.as_matrix(d, L)
        self.assertEqual(
            np.linalg.norm(A2 - A),
            0,
            msg=
            'matrix representation after padding identities on the right must remain unchanged'
        )