예제 #1
0
    def test_multiply(self):

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

        # create random matrix product operators
        op0 = ptn.MPO(qd, [
            np.random.randint(-2, 3, size=Di)
            for Di in [1, 10, 13, 24, 17, 9, 1]
        ],
                      fill='random')
        op1 = ptn.MPO(qd, [
            np.random.randint(-2, 3, size=Di)
            for Di in [1, 8, 17, 11, 23, 13, 1]
        ],
                      fill='random')

        # MPO multiplication (composition)
        op = op0 * op1

        # reference calculation
        op_ref = np.dot(op0.as_matrix(), op1.as_matrix())

        # relative error
        err = np.linalg.norm(op.as_matrix() - op_ref) / max(
            np.linalg.norm(op_ref), 1e-12)
        self.assertAlmostEqual(
            err,
            0.,
            delta=1e-14,
            msg='composition of two MPOs must agree with matrix representation'
        )
예제 #2
0
    def test_add_singlesite(self):

        # separate test for a single site since implementation is a special case

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

        # create random matrix product operators acting on a single site
        # leading and trailing (dummy) virtual bond quantum numbers
        qD = [np.array([-1]), np.array([-2])]
        op0 = ptn.MPO(qd, qD, fill='random')
        op1 = ptn.MPO(qd, qD, fill='random')

        # MPO addition
        op = op0 + op1

        # reference calculation
        op_ref = op0.as_matrix() + op1.as_matrix()

        # relative error
        err = np.linalg.norm(op.as_matrix() - op_ref) / max(
            np.linalg.norm(op_ref), 1e-12)
        self.assertAlmostEqual(
            err,
            0.,
            delta=1e-14,
            msg='addition two MPOs must agree with matrix representation')
예제 #3
0
    def test_add(self):

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

        # create random matrix product operators
        qD0 = [
            np.random.randint(-2, 3, size=Di)
            for Di in [1, 11, 15, 23, 18, 9, 1]
        ]
        qD1 = [
            np.random.randint(-2, 3, size=Di)
            for Di in [1, 7, 23, 11, 17, 13, 1]
        ]
        # leading and trailing (dummy) virtual bond quantum numbers must agree
        qD1[0] = qD0[0].copy()
        qD1[-1] = qD0[-1].copy()
        op0 = ptn.MPO(qd, qD0, fill='random')
        op1 = ptn.MPO(qd, qD1, fill='random')

        # MPO addition
        op = op0 + op1

        # reference calculation
        op_ref = op0.as_matrix() + op1.as_matrix()

        # relative error
        err = np.linalg.norm(op.as_matrix() - op_ref) / max(
            np.linalg.norm(op_ref), 1e-12)
        self.assertAlmostEqual(
            err,
            0.,
            delta=1e-14,
            msg='addition two MPOs must agree with matrix representation')
예제 #4
0
def cast_to_MPO(mps, qd):
    """
    Cast a matrix product state into MPO form by interpreting the physical
    dimension as Kronecker product of a pair of dimensions.
    """
    assert np.array_equal(mps.qd, ptn.qnumber_flatten([qd, -qd]))

    mpo = ptn.MPO(qd, mps.qD, fill=0.0)
    for i in range(mps.nsites):
        s = mps.A[i].shape
        mpo.A[i] = mps.A[i].reshape((len(qd), len(qd), s[1], s[2])).copy()
        assert ptn.is_qsparse(mpo.A[i],
                              [mpo.qd, -mpo.qd, mpo.qD[i], -mpo.qD[i + 1]])
    return mpo
예제 #5
0
    def test_operator_average(self):

        # physical dimension
        d = 3

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

        # create random matrix product state
        D = [1, 7, 26, 19, 25, 8, 1]
        psi = ptn.MPS(qd, [np.random.randint(-1, 2, size=Di) for Di in D],
                      fill='random')
        # rescale to achieve norm of order 1
        for i in range(psi.nsites):
            psi.A[i] *= 5

        # create random matrix product operator
        D = [1, 5, 16, 14, 17, 4, 1]
        # set bond quantum numbers to zero since otherwise,
        # sparsity pattern often leads to <psi | op | psi> = 0
        op = ptn.MPO(qd, [np.zeros(Di, dtype=int) for Di in D], fill='random')

        # calculate average (expectation value) <psi | op | psi>
        avr = ptn.operator_average(psi, op)

        # reference value based on full Fock space representation
        x = psi.as_vector()
        avr_ref = np.vdot(x, np.dot(op.as_matrix(), x))

        # relative error
        err = abs(avr - avr_ref) / max(abs(avr_ref), 1e-12)
        self.assertAlmostEqual(
            err,
            0.,
            delta=1e-12,
            msg='operator average must match reference value')
예제 #6
0
def main():

    # physical local Hilbert space dimension
    d = 2

    # number of lattice sites
    L = 6

    # construct matrix product operator representation of Heisenberg Hamiltonian
    J = 1.0
    DH = 1.2
    h = -0.2
    mpoH = ptn.heisenberg_XXZ_MPO(L, J, DH, h)
    mpoH.zero_qnumbers()
    # realize commutator [H, .] as matrix product operator
    mpoHcomm = heisenberg_XXZ_comm_MPO(L, J, DH, h)
    mpoHcomm.zero_qnumbers()
    print('2-norm of [H, .] operator:', np.linalg.norm(mpoHcomm.as_matrix(),
                                                       2))

    mpsH = cast_to_MPS(mpoH)
    print('mpsH.bond_dims:', mpsH.bond_dims)
    print('ptn.norm(mpsH):', ptn.norm(mpsH))

    print('[H, .] applied to H as vector (should be zero): {:g}'.format(
        np.linalg.norm(np.dot(mpoHcomm.as_matrix(), mpsH.as_vector()))))

    # initial MPO with random entries (not necessarily Hermitian)
    Dmax = 40
    D = np.minimum(
        np.minimum(d**(2 * np.arange(L + 1)), d**(2 * (L - np.arange(L + 1)))),
        Dmax)
    print('D:', D)
    np.random.seed(42)
    op = ptn.MPO(mpoH.qd, [np.zeros(Di, dtype=int) for Di in D], fill='random')
    # effectively clamp virtual bond dimension
    for i in range(L):
        op.A[i][:, :, 2:, :] = 0
        op.A[i][:, :, :, 2:] = 0

    op.orthonormalize(mode='right')
    op.orthonormalize(mode='left')

    # matrix representation
    op_mat = op.as_matrix()

    # cast into MPS form
    psi = cast_to_MPS(op)
    print('norm of psi:', np.linalg.norm(psi.as_vector()))
    print('psi.bond_dims:', psi.bond_dims)

    # check: commutator
    comm_ref = np.dot(op_mat, mpoH.as_matrix()) - np.dot(
        mpoH.as_matrix(), op_mat)
    comm = np.dot(mpoHcomm.as_matrix(), psi.as_vector()).reshape(
        (2 * L) * [d]).transpose(
            np.concatenate((2 * np.arange(L, dtype=int),
                            2 * np.arange(L, dtype=int) + 1))).reshape(
                                (d**L, d**L))
    print('commutator reference check error: {:g}'.format(
        np.linalg.norm(comm - comm_ref)))

    # initial average energy (should be conserved)
    en_avr_0 = np.trace(np.dot(mpoH.as_matrix(), op_mat))
    en_avr_0_alt = ptn.vdot(mpsH, psi)
    print('en_avr_0:', en_avr_0)
    print('abs(en_avr_0_alt - en_avr_0):', abs(en_avr_0_alt - en_avr_0))

    # exact Schmidt coefficients (singular values) of initial state
    sigma_0 = schmidt_coefficients_operator(d, L, op_mat)
    plt.semilogy(np.arange(len(sigma_0)) + 1, sigma_0, '.')
    plt.xlabel('i')
    plt.ylabel('$\sigma_i$')
    plt.title('Schmidt coefficients of initial state')
    plt.savefig('evolution_operator_schmidt_0.pdf')
    plt.show()

    print(
        'Schmidt coefficients consistency check:',
        np.linalg.norm(
            sigma_0 -
            schmidt_coefficients_wavefunction(d**2, L, psi.as_vector())))

    print('entropy of initial state:',
          entropy((sigma_0 / np.linalg.norm(sigma_0))**2))

    # mixed real and imaginary time evolution
    t = 0.1 + 0.5j

    # reference calculation: exp(t H) op exp(-t H)
    op_t_ref = np.dot(np.dot(expm(t * mpoH.as_matrix()), op_mat),
                      expm(-t * mpoH.as_matrix()))

    # Frobenius norm not preserved by mixed real and imaginary time evolution
    print('Frobenius norm of initial op: {:g}'.format(
        np.linalg.norm(op_mat, 'fro')))
    print('Frobenius norm of op(t):      {:g}'.format(
        np.linalg.norm(op_t_ref, 'fro')))

    # energy should be conserved
    en_avr_t_ref = np.trace(np.dot(mpoH.as_matrix(), op_t_ref))
    print('en_avr_t_ref:', en_avr_t_ref)
    print('abs(en_avr_t_ref - en_avr_0):', abs(en_avr_t_ref - en_avr_0))

    # exact Schmidt coefficients (singular values) of time-evolved state
    sigma_t = schmidt_coefficients_operator(d, L, op_t_ref)
    plt.semilogy(np.arange(len(sigma_t)) + 1, sigma_t, '.')
    plt.xlabel('i')
    plt.ylabel(r'$\sigma_i$')
    plt.title(
        'Schmidt coefficients of time-evolved state (t = {:g})\n(based on exact time evolution)'
        .format(-1j * t))
    plt.savefig('evolution_operator_schmidt_t.pdf')
    plt.show()

    S = entropy((sigma_t / np.linalg.norm(sigma_t))**2)
    print('entropy of time-evolved state:', S)

    P = tangent_space_projector(psi)
    print(
        'np.linalg.norm(np.dot(P, mpsH.as_vector()) - mpsH.as_vector()) (in general non-zero):',
        np.linalg.norm(np.dot(P, mpsH.as_vector()) - mpsH.as_vector()))

    # number of time steps
    numsteps = 2**(np.arange(5))
    err_op = np.zeros(len(numsteps))
    # relative energy error
    err_en = np.zeros(len(numsteps))

    for i, n in enumerate(numsteps):
        print('n:', n)

        # time step
        dt = t / n

        psi_t = copy.deepcopy(psi)
        ptn.integrate_local_singlesite(mpoHcomm,
                                       psi_t,
                                       dt,
                                       n,
                                       numiter_lanczos=20)
        op_t = cast_to_MPO(psi_t, op.qd)

        err_op[i] = np.linalg.norm(op_t.as_matrix() - op_t_ref, ord=1)

        en_avr_t = np.trace(np.dot(mpoH.as_matrix(), op_t.as_matrix()))
        # relative energy error
        err_en[i] = abs(en_avr_t - en_avr_0) / abs(en_avr_0)

    dtinv = numsteps / abs(t)
    plt.loglog(dtinv, err_op, '.-')
    # show quadratic scaling for comparison
    plt.loglog(dtinv, 1.75e-2 / dtinv**2, '--')
    plt.xlabel('1/dt')
    plt.ylabel(
        r'$\Vert\mathcal{O}[A](t) - \mathcal{O}_\mathrm{ref}(t)\Vert_1$')
    plt.title(
        'TDVP time evolution (applied to operator) rate of convergence for\nHeisenberg XXZ model (J={:g}, D={:g}, h={:g}), L={}, t={:g}'
        .format(J, DH, h, L, -1j * t))
    plt.savefig('evolution_operator_convergence.pdf')
    plt.show()

    plt.loglog(dtinv, err_en, '.-')
    # show quadratic scaling for comparison
    plt.loglog(dtinv, 1e-2 / dtinv**2, '--')
    plt.xlabel('1/dt')
    plt.ylabel(
        r'$\frac{\vert\epsilon(t) - \epsilon(0)\vert}{\vert\epsilon(0)\vert}, \quad \epsilon(t) = \mathrm{tr}[H \mathcal{O}[A](t)]$'
    )
    plt.title(
        'TDVP time evolution (applied to operator) relative energy error for\nHeisenberg XXZ model (J={:g}, D={:g}, h={:g}), L={}, t={:g}'
        .format(J, DH, h, L, -1j * t))
    plt.savefig('evolution_operator_energy_conv.pdf')
    plt.show()
예제 #7
0
    def test_orthonormalization(self):

        # create random matrix product operator
        d = 4
        D = [1, 10, 13, 14, 7, 1]
        mpo0 = ptn.MPO(np.random.randint(-2, 3, size=d),
                       [np.random.randint(-2, 3, size=Di) for Di in D],
                       fill='random')

        self.assertEqual(mpo0.bond_dims, D, msg='virtual bond dimensions')

        # density matrix on full Hilbert space
        rho = mpo0.as_matrix()

        # performing left-orthonormalization...
        cL = mpo0.orthonormalize(mode='left')

        self.assertLessEqual(
            mpo0.bond_dims[1],
            d**2,
            msg=
            'virtual bond dimension can only increase by factor of "d^2" per site'
        )

        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'
            )

        rhoL = mpo0.as_matrix()
        # density matrix should now be normalized
        self.assertAlmostEqual(np.linalg.norm(rhoL, 'fro'),
                               1.,
                               delta=1e-12,
                               msg='density matrix normalization')

        # density matrices before and after left-normalization must match
        # (up to normalization factor)
        self.assertAlmostEqual(
            np.linalg.norm(cL * rhoL - rho),
            0.,
            delta=1e-10,
            msg=
            'density matrices before and after left-normalization must match')

        # check left-orthonormalization
        for i in range(mpo0.nsites):
            s = mpo0.A[i].shape
            assert s[0] == d and s[1] == d
            Q = mpo0.A[i].reshape((s[0] * s[1] * s[2], s[3]))
            QH_Q = np.dot(Q.conj().T, Q)
            self.assertAlmostEqual(np.linalg.norm(QH_Q - np.identity(s[3])),
                                   0.,
                                   delta=1e-12,
                                   msg='left-orthonormalization')

        # performing right-orthonormalization...
        cR = mpo0.orthonormalize(mode='right')

        self.assertLessEqual(
            mpo0.bond_dims[-2],
            d**2,
            msg=
            'virtual bond dimension can only increase by factor of "d^2" per site'
        )

        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'
            )

        self.assertAlmostEqual(
            abs(cR),
            1.,
            delta=1e-12,
            msg=
            'normalization factor must have magnitude 1 due to previous left-orthonormalization'
        )

        rhoR = mpo0.as_matrix()
        # density matrices must match
        self.assertAlmostEqual(
            np.linalg.norm(rhoL - cR * rhoR),
            0.,
            delta=1e-10,
            msg=
            'density matrices after left- and right-orthonormalization must match'
        )

        # check right-orthonormalization
        for i in range(mpo0.nsites):
            s = mpo0.A[i].shape
            assert s[0] == d and s[1] == d
            Q = mpo0.A[i].transpose((0, 1, 3, 2)).reshape(
                (s[0] * s[1] * s[3], s[2]))
            QH_Q = np.dot(Q.conj().T, Q)
            self.assertAlmostEqual(np.linalg.norm(QH_Q - np.identity(s[2])),
                                   0.,
                                   delta=1e-12,
                                   msg='right-orthonormalization')