コード例 #1
0
ファイル: test_operation.py プロジェクト: ml-lab/pytenet
    def test_vdot(self):

        # physical dimension
        d = 4

        # create random matrix product states
        psi = ptn.MPS(np.zeros(d, dtype=int),
                      [np.zeros(Di, dtype=int) for Di in [1, 3, 9, 13, 4, 1]],
                      fill='random')
        chi = ptn.MPS(np.zeros(d, dtype=int),
                      [np.zeros(Di, dtype=int) for Di in [1, 4, 7, 8, 2, 1]],
                      fill='random')

        # calculate dot product <chi | psi>
        s = ptn.vdot(chi, psi)

        # reference value
        s_ref = np.vdot(chi.as_vector(), psi.as_vector())

        # relative error
        err = abs(s - s_ref) / max(abs(s_ref), 1e-12)
        self.assertAlmostEqual(err,
                               0.,
                               delta=1e-12,
                               msg='dot product must match reference value')
コード例 #2
0
    def test_sub_singlesite(self):

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

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

        # create random matrix product states for a single site
        # leading and trailing (dummy) virtual bond quantum numbers
        qD = [np.array([-1]), np.array([-2])]
        mps0 = ptn.MPS(qd, qD, fill='random')
        mps1 = ptn.MPS(qd, qD, fill='random')

        # MPS subtraction
        mps = mps0 - mps1

        # reference calculation
        mps_ref = mps0.as_vector() - mps1.as_vector()

        # relative error
        err = np.linalg.norm(mps.as_vector() - mps_ref) / max(
            np.linalg.norm(mps_ref), 1e-12)
        self.assertAlmostEqual(
            err,
            0.,
            delta=1e-14,
            msg=
            'subtraction of two matrix product states must agree with vector representation'
        )
コード例 #3
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')
コード例 #4
0
    def test_single_site(self):

        # number of lattice sites
        L = 10

        # number of left and right sweeps
        numsweeps = 4

        # minimization seems to work better when disabling quantum numbers
        # (for a given maximal bond dimension)

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

        # initial wavefunction as MPS with random entries
        D = [1] + (L - 1) * [28] + [1]
        psi = ptn.MPS(mpoH.qd, [np.zeros(Di, dtype=int) for Di in D],
                      fill='random')

        en_min = ptn.calculate_ground_state_local_singlesite(
            mpoH, psi, numsweeps)
        # value after last iteration
        E0 = en_min[-1]

        # reference spectrum and wavefunctions
        en_ref, V_ref = np.linalg.eigh(mpoH.as_matrix())

        # compare ground state energy
        self.assertAlmostEqual(
            E0,
            en_ref[0],
            delta=1e-13,
            msg=
            'ground state energy obtained by single-site optimization must match reference'
        )

        # compare ground state wavefunction
        psi_vec = psi.as_vector()
        # multiply by phase factor to match (real-valued) reference wavefunction
        i = np.argmax(abs(psi_vec))
        z = psi_vec[i]
        psi_vec *= z.conj() / abs(z)
        if V_ref[i, 0] < 0:
            psi_vec = -psi_vec
        self.assertAlmostEqual(
            np.linalg.norm(psi_vec - V_ref[:, 0]),
            0,
            delta=1e-7,
            msg=
            'ground state wavefunction obtained by single-site optimization must match reference'
        )
コード例 #5
0
ファイル: evolution_operator.py プロジェクト: ml-lab/pytenet
def cast_to_MPS(mpo):
    """
    Cast a matrix product operator into MPS form by combining the pair of local
    physical dimensions into one dimension.
    """
    mps = ptn.MPS(ptn.qnumber_flatten([mpo.qd, -mpo.qd]), mpo.qD, fill=0.0)
    for i in range(mpo.nsites):
        s = mpo.A[i].shape
        mps.A[i] = mpo.A[i].reshape((s[0] * s[1], s[2], s[3])).copy()
        assert ptn.is_qsparse(mps.A[i], [mps.qd, mps.qD[i], -mps.qD[i + 1]])
    return mps
コード例 #6
0
    def test_add(self):

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

        # create random matrix product states
        qD0 = [np.random.randint(-2, 3, size=Di) for Di in [1, 8, 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()
        mps0 = ptn.MPS(qd, qD0, fill='random')
        mps1 = ptn.MPS(qd, qD1, fill='random')

        # MPS addition
        mps = mps0 + mps1

        # reference calculation
        mps_ref = mps0.as_vector() + mps1.as_vector()

        # relative error
        err = np.linalg.norm(mps.as_vector() - mps_ref) / max(np.linalg.norm(mps_ref), 1e-12)
        self.assertAlmostEqual(err, 0., delta=1e-14,
            msg='addition of two matrix product states must agree with vector representation')
コード例 #7
0
ファイル: test_operation.py プロジェクト: ml-lab/pytenet
    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')
コード例 #8
0
ファイル: test_operation.py プロジェクト: ml-lab/pytenet
    def test_norm(self):

        # physical dimension
        d = 3

        # create random matrix product state
        psi = ptn.MPS(
            np.zeros(d, dtype=int),
            [np.zeros(Di, dtype=int) for Di in [1, 3, 5, 8, 7, 2, 1]],
            fill='random')

        # calculate the norm of psi using the MPS representation
        n = ptn.norm(psi)

        # reference value
        n_ref = np.linalg.norm(psi.as_vector())

        # relative error
        err = abs(n - n_ref) / max(abs(n_ref), 1e-12)
        self.assertAlmostEqual(
            err,
            0.,
            delta=1e-12,
            msg='wavefunction norm must match reference value')
コード例 #9
0
    def test_orthonormalization(self):

        # create random matrix product state
        d = 7
        D = [1, 4, 15, 13, 7, 1]
        mps0 = ptn.MPS(np.random.randint(-2, 3, size=d), [np.random.randint(-2, 3, size=Di) for Di in D], fill='random')

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

        # wavefunction on full Hilbert space
        psi = mps0.as_vector()

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

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

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

        psiL = mps0.as_vector()
        # wavefunction should now be normalized
        self.assertAlmostEqual(np.linalg.norm(psiL), 1., delta=1e-12, msg='wavefunction normalization')

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

        # check left-orthonormalization
        for i in range(mps0.nsites):
            s = mps0.A[i].shape
            assert s[0] == d
            Q = mps0.A[i].reshape((s[0]*s[1], 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='left-orthonormalization')

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

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

        for i in range(mps0.nsites):
            self.assertTrue(ptn.is_qsparse(mps0.A[i], [mps0.qd, mps0.qD[i], -mps0.qD[i+1]]),
                            msg='sparsity pattern of MPS 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')

        psiR = mps0.as_vector()
        # wavefunctions must match
        self.assertAlmostEqual(np.linalg.norm(psiL - cR*psiR), 0., delta=1e-10,
                               msg='wavefunctions after left- and right-orthonormalization must match')

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

    # physical local Hilbert space dimension
    d = 2

    # number of lattice sites
    L = 10

    # 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()

    # initial wavefunction as MPS with random entries
    Dmax = 20
    D = np.minimum(np.minimum(d**np.arange(L + 1), d**(L - np.arange(L + 1))), Dmax)
    print('D:', D)
    np.random.seed(42)
    psi = ptn.MPS(mpoH.qd, [np.zeros(Di, dtype=int) for Di in D], fill='random')
    # effectively clamp virtual bond dimension
    for i in range(L):
        psi.A[i][:, 3:, :] = 0
        psi.A[i][:, :, 3:] = 0
    psi.orthonormalize(mode='right')
    psi.orthonormalize(mode='left')

    # initial average energy (should be conserved)
    e_avr_0 = ptn.operator_average(psi, mpoH).real
    print('e_avr_0:', e_avr_0)

    # exact singular values (Schmidt coefficients) initial state
    sigma_0 = schmidt_coefficients(d, L, psi.as_vector())
    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_schmidt_0.pdf')
    plt.show()
    print('entropy of initial state:', entropy((sigma_0 / np.linalg.norm(sigma_0))**2))

    # purely real time evolution
    t = 0.5j

    # reference calculation
    psi_ref = np.dot(expm(-t*mpoH.as_matrix()), psi.as_vector())

    # exact Schmidt coefficients (singular values) of time-evolved state
    sigma_t = schmidt_coefficients(d, L, psi_ref)
    plt.semilogy(np.arange(len(sigma_t)) + 1, sigma_t, '.')
    plt.xlabel('i')
    plt.ylabel('$\sigma_i$')
    plt.title('Schmidt coefficients of time-evolved state (t = {:g})\n(based on exact time evolution)'.format(t.imag))
    plt.savefig('evolution_schmidt_t.pdf')
    plt.show()
    print('entropy of time-evolved state:', entropy((sigma_t / np.linalg.norm(sigma_t))**2))

    # number of time steps
    numsteps = 2**(np.arange(5))
    err = 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(mpoH, psi_t, dt, n, numiter_lanczos=10)

        err[i] = np.linalg.norm(psi_t.as_vector() - psi_ref)

        # expecting numerically exact energy conservation
        # (for real time evolution)
        e_avr_t = ptn.operator_average(psi_t, mpoH).real
        print('e_avr_t:', e_avr_t)
        print('abs(e_avr_t - e_avr_0):', abs(e_avr_t - e_avr_0))

    dtinv = numsteps / abs(t)
    plt.loglog(dtinv, err, '.-')
    # show quadratic scaling for comparison
    plt.loglog(dtinv, 1.75e-4/dtinv**2, '--')
    plt.xlabel('1/dt')
    plt.ylabel(r'$\Vert\psi[A](t) - \psi_\mathrm{ref}(t)\Vert$')
    plt.title('TDVP time evolution rate of convergence (t = {:g}) for\nHeisenberg XXZ model (J={:g}, D={:g}, h={:g})'.format(t.imag, J, DH, h))
    plt.savefig('evolution_convergence.pdf')
    plt.show()
コード例 #11
0
ファイル: tangent_space.py プロジェクト: ml-lab/pytenet
def main():

    # physical dimension
    d = 3
    # fictitious bond dimensions (should be bounded by d^i and d^(L-i))
    D = [1, 2, 5, 7, 3, 1]

    # number of lattice sites
    L = len(D) - 1
    print('L:', L)

    psi = ptn.MPS(np.zeros(d, dtype=int), [np.zeros(Di, dtype=int) for Di in D], fill='random')

    # construct MPS derivatives with respect to entries of the A tensors
    T = []
    for i in range(L):
        s = psi.A[i].shape
        print('s:', s)
        for j in range(s[0]):
            for a in range(s[1]):
                for b in range(s[2]):
                    # derivative in direction (i, j, a, b)
                    B = np.zeros_like(psi.A[i])
                    B[j, a, b] = 1
                    # backup original A[i] tensor
                    Ai = psi.A[i]
                    psi.A[i] = B
                    T.append(psi.as_vector())
                    # restore A[i]
                    psi.A[i] = Ai

    T = np.array(T)
    num_entries = np.sum([Ai.size for Ai in psi.A])
    print('T.shape: ', T.shape)
    print('expected:', (num_entries, d**L))
    print('rank of T:', np.linalg.matrix_rank(T))
    # number of degrees of freedom based on sandwiching "X" matrices between bonds,
    # -2 for omitting the leading and trailing entry 1 in D
    rank = num_entries - ((np.array(D)**2).sum() - 2)
    print('expected: ', rank)

    # realization of random X matrices
    X = [np.identity(1, dtype=complex)]
    for i in range(L - 1):
        X.append(np.random.normal(size=(D[i+1], D[i+1])) + 1j*np.random.normal(size=(D[i+1], D[i+1])))
    X.append(np.identity(1, dtype=complex))
    N = []
    for i in range(L):
        B = np.tensordot(X[i], psi.A[i], axes=(1, 1)).transpose((1, 0, 2)) - \
            np.tensordot(psi.A[i], X[i+1], axes=(2, 0))
        # backup original A[i] tensor
        Ai = psi.A[i]
        psi.A[i] = B
        N.append(psi.as_vector())
        # restore A[i]
        psi.A[i] = Ai
    N = np.array(N)
    print('N.shape:', N.shape)
    # N should be contained in range of T
    print('rank of [T, N]:', np.linalg.matrix_rank(np.concatenate((T, N), axis=0)),
          '(should agree with rank of T)')
    # should be numerically zero by construction
    z = np.dot(N.transpose(), np.ones(L))
    print('|z|:', np.linalg.norm(z), '(should be numerically zero)')

    # reference tangent space projector based on T
    # rank-revealing QR decomposition
    Q, R, _ = scipy.linalg.qr(T.T, mode='economic', pivoting=True)
    P_ref = np.dot(Q[:, :rank], Q[:, :rank].conj().T)

    # construct tangent space projector based on MPS formalism
    P = tangent_space_projector(psi)
    # compare
    print('|P - P_ref|:', np.linalg.norm(P - P_ref), '(should be numerically zero)')

    # apply projector to psi (psi should remain unaffected)
    x = psi.as_vector()
    print('|P psi - psi|:', np.linalg.norm(np.dot(P, x) - x), '(should be numerically zero)')

    # define another state
    # fictitious bond dimensions (should be bounded by d^i and d^(L-i))
    D = [1, 4, 7, 5, 3, 1]
    chi = ptn.MPS(np.zeros(d, dtype=int), [np.zeros(Di, dtype=int) for Di in D], fill='random')

    # tangent space projector corresponding to the sum of two states
    Psum = tangent_space_projector(psi + chi)
    # apply projector to psi (should remain unaffected)
    x = psi.as_vector()
    print('|Psum psi - psi|:', np.linalg.norm(np.dot(Psum, x) - x), '(should be numerically zero)')
    # apply projector to chi (should remain unaffected)
    x = chi.as_vector()
    print('|Psum chi - chi|:', np.linalg.norm(np.dot(Psum, x) - x), '(should be numerically zero)')
    # apply projector to psi + chi (should remain unaffected)
    x = psi.as_vector() + chi.as_vector()
    print('|Psum (psi + chi) - (psi + chi)|:', np.linalg.norm(np.dot(Psum, x) - x), '(should be numerically zero)')