Пример #1
0
def coleman_decomposition(two_two_tensor):
    """
    Perform unitary decomposition of a (2, 2)-tensor

    For more detail references and derivations see references:

    1. Phys. Rev. E. 65 026704
    2. Int. J. Quant. Chem. XVII 127901307 (1980)

    :param two_two_tensor: 4-tensor representing a (2, 2)-tensor
    :return:
    """
    if not np.isclose(np.ndim(two_two_tensor), 4):
        raise TypeError("coleman decomposition requires a (2,2) tensor")

    dim = two_two_tensor.shape[0]
    trace_value = np.einsum('ijij', two_two_tensor)

    one_one_tensor = np.einsum('ikjk', two_two_tensor)
    one_one_eye = wedge_product(one_one_tensor, np.eye(dim)).astype(complex)
    eye_wedge_eye = wedge_product(np.eye(dim), np.eye(dim)).astype(complex)

    zero_carrier = ((2 * trace_value) / (dim * (dim - 1))) * eye_wedge_eye

    one_carrier = (4 / (dim - 2)) * one_one_eye
    one_carrier -= ((4 * trace_value) / (dim * (dim - 2))) * eye_wedge_eye

    two_carrier = two_two_tensor - (4 / (dim - 2)) * one_one_eye
    two_carrier += ((2 * trace_value) / ((dim - 1) * (dim - 2))) * eye_wedge_eye

    return zero_carrier.real, one_carrier.real, two_carrier.real
def test_map_d2_q2_with_wedge():
    n_density, rdm_gen, transform, molecule = heh_system()
    density = SpinOrbitalDensity(n_density, molecule.n_qubits)
    tpdm = density.construct_tpdm()
    tqdm = density.construct_thdm()
    opdm = density.construct_opdm()
    dim = opdm.shape[0]
    eye_wedge_eye = wedge_product(np.eye(dim), np.eye(dim))
    one_wedge_eye = wedge_product(opdm, np.eye(dim))
    tqdm_test = 2 * eye_wedge_eye - 4 * one_wedge_eye + tpdm
    for p, q, r, s in product(range(dim), repeat=4):
        np.testing.assert_allclose(tqdm_test[p, q, r, s],
                                   tqdm[p, q, r, s],
                                   atol=1.0E-10)
def coleman_projection_dq(tpdm, N, error=1.0E-6, disp=False):
    """
    Fixe up the tpdm trace and 1-particle piece and then iterate over the

    Coleman decomp of exposed operators

    :param tpdm:
    :param N:
    :param float error:
    :param Bool disp: optional argument for displaying updates
    :return:
    """
    dim = tpdm.shape[0]
    _, _, d2_2 = coleman_decomposition(tpdm)
    # check if this thing I grabbed is actually an antisymmetric operator

    eye_wedge_eye = wedge_product(np.eye(dim), np.eye(dim)).astype(complex)
    eye_wedge_eye_mat = four_tensor2matrix(eye_wedge_eye)

    # we know d2_0 by fixed trace from particle number
    trace_value = N * (N - 1)
    d2_0 = ((2 * trace_value) / (dim * (dim - 1))) * eye_wedge_eye

    # we know d2_1 should be (N - 1) * opdm
    # so grab the opdm first from the noisy tpdm and then purify
    opdm = map_tpdm_to_opdm(tpdm, N)
    # opdm_new = mazziotti_opdm_purification(opdm, N)
    opdm_new = fixed_trace_positive_projection(opdm, N)

    # first multiply by N - 1 get back to just contracted.  Then another
    # for adjusting
    one_one_tensor = ((N - 1)**2) * opdm_new
    one_one_eye = wedge_product(one_one_tensor, np.eye(dim))
    one_one_eye_mat = four_tensor2matrix(one_one_eye)

    d2_1 = (4 / (dim - 2)) * one_one_eye
    d2_1 -= ((4 * trace_value) / (dim * (dim - 2))) * eye_wedge_eye

    # we need to check a bunch of stuff about the reconstruct tpdm
    # a) does it have all the right symmetry
    # b) does it have the right trace and does it contract appropriately
    # c) does the d2_0 + d2_1 look like a contracted tpdm_partial fix

    tpdm_partial_fix = d2_0 + d2_1 + d2_2
    d2_matrix = four_tensor2matrix(tpdm_partial_fix)

    # grabbing the 2-Q-RDM
    tqdm = map_tpdm_to_tqdm(tpdm_partial_fix, opdm_new)
    q2_matrix = four_tensor2matrix(tqdm)

    residual = 10
    iter_max = 3000
    iter = 0
    while residual > error and iter < iter_max:
        tpdm_current_iter = matrix2four_tensor(d2_matrix)
        opdm = map_tpdm_to_opdm(tpdm_current_iter, N)
        one_wedge_eye_mat = four_tensor2matrix(wedge_product(opdm, np.eye(dim)))
        tqdm = map_tpdm_to_tqdm(tpdm_current_iter, opdm)
        q2_matrix = four_tensor2matrix(tqdm)

        # diagonalize both d2 and q2
        w, v = np.linalg.eigh(d2_matrix)
        wq, vq = np.linalg.eigh(q2_matrix)

        # grab the exposing operators for both matrices
        exposed_operator = []
        for ii in range(w.shape[0]):
            if w[ii] < float(-1.0E-13):
                # get the exposing operator antisymmeterize
                eo = v[:, [ii]].dot(np.conj(v[:, [ii]]).T)
                eo_22_tensor = matrix2four_tensor(eo)
                eo_22_tensor_antiy = antisymmeterizer(eo_22_tensor)

                # add the operator to my list
                exposed_operator.append(four_tensor2matrix(eo_22_tensor_antiy))

        exposed_operator_q = []
        for ii in range(wq.shape[0]):
            if wq[ii] < float(-1.0E-13):
                # get the exposing operator antisymmeterize
                eo = vq[:, [ii]].dot(np.conj(vq[:, [ii]]).T)
                eo_22_tensor = matrix2four_tensor(eo)
                eo_22_tensor_antiy = antisymmeterizer(eo_22_tensor)

                # add the operator to my list
                exposed_operator_q.append(four_tensor2matrix(eo_22_tensor_antiy))


        # Now we want to find the coleman decomp of each of the exposing operators
        num_eo = len(exposed_operator)
        zero_contraction_eo = []
        for ii in range(num_eo):
            eo = matrix2four_tensor(exposed_operator[ii])
            eo_0, eo_1, eo_2 = coleman_decomposition(eo)
            zero_contraction_eo.append(four_tensor2matrix(eo_2))

        num_eo_q = len(exposed_operator_q)
        zero_contraction_eo_q = []
        for ii in range(num_eo_q):
            eo = matrix2four_tensor(exposed_operator_q[ii])
            eo_0, eo_1, eo_2 = coleman_decomposition(eo)
            zero_contraction_eo_q.append(four_tensor2matrix(eo_2))

        # set up system of equations to solve for alpha terms
        Amatrix = np.zeros((num_eo + num_eo_q, num_eo + num_eo_q))
        bvector = np.zeros((num_eo + num_eo_q, 1))
        for i, j in product(range(num_eo), repeat=2):
            # alpha coeffs
            Amatrix[i, j] = np.trace(
                exposed_operator[i].dot(zero_contraction_eo[j])).real
            # beta vector component for i-term
            if i == j:
                bvector[i, 0] = np.trace(
                    d2_matrix.dot(exposed_operator[i])).real

        for i, j in product(range(num_eo), range(num_eo_q)):
            # beta coeffs
            Amatrix[i, j + num_eo] = np.trace(
                exposed_operator[i].dot(zero_contraction_eo_q[j])).real

        # now fill in the rows of the Amatrix for the hole exposed operators
        for i, j in product(range(num_eo_q), range(num_eo)):
            # alpha coeffs with q-exposed operator
            Amatrix[i + num_eo, j] = np.trace(
                exposed_operator_q[i].dot(zero_contraction_eo[j]).real
            )
        for i, j in product(range(num_eo_q), repeat=2):
            # beta coeffs with q-exposed operator
            Amatrix[i + num_eo, j + num_eo] = np.trace(
                exposed_operator_q[i].dot(zero_contraction_eo_q[j]).real
            )
            if i == j:
                bvector[i + num_eo, 0] = np.trace(
                    d2_matrix.dot(exposed_operator_q[i])).real
                bvector[i + num_eo, 0] += 2 * np.trace(
                                              exposed_operator_q[i].dot(
                                                  eye_wedge_eye_mat)).real
                bvector[i + num_eo, 0] -= 4 * np.trace(
                                              exposed_operator_q[i].dot(
                                                  one_wedge_eye_mat)).real

        # alpha_beta = np.linalg.solve(Amatrix, -bvector)
        [alpha_beta, _, _, _] = np.linalg.lstsq(Amatrix, -bvector)
        # update the 2-RDM matrix
        d2_new = d2_matrix.copy()
        for ii in range(num_eo):
            d2_new += alpha_beta[ii] * zero_contraction_eo[ii]
        for ii in range(num_eo_q):
            d2_new += alpha_beta[ii + num_eo] * zero_contraction_eo_q[ii]

        # check for conversion of the iterative method
        w, v = np.linalg.eigh(d2_new)
        residual = np.linalg.norm(w[w < 0])

        if disp:
            print("iter {:5.0f}\tdiff {:3.10e}\t".format(iter, np.linalg.norm(d2_new - d2_matrix)), end='')
            print("trace new {:3.10f}\terror {:3.10e}".format(np.trace(d2_new).real, residual))

        d2_matrix = d2_new
        iter += 1

    tpdm = matrix2four_tensor(d2_matrix)
    return tpdm
def unitary_subspace_purification_fixed_initial_trace(tpdm,
                                                      N,
                                                      error=1.0E-6,
                                                      disp=False):
    """
    Fixe up the tpdm trace and 1-particle piece and then iterate over the

    Coleman decomp of exposed operators

    :param tpdm:
    :param N:
    :param float error:
    :param Bool disp: optional argument for displaying updates
    :return:
    """
    if __debug__:
        check_antisymmetric_d2(tpdm)

    dim = tpdm.shape[0]
    _, _, d2_2 = coleman_decomposition(tpdm)
    # check if this thing I grabbed is actually an antisymmetric operator
    if __debug__:
        check_antisymmetric_d2(d2_2)
        assert np.isclose(np.einsum('ijij', d2_2), 0.0)

    eye_wedge_eye = wedge_product(np.eye(dim), np.eye(dim)).astype(complex)

    # we know d2_0 by fixed trace from particle number
    trace_value = N * (N - 1)
    d2_0 = ((2 * trace_value) / (dim * (dim - 1))) * eye_wedge_eye

    # we know d2_1 should be (N - 1) * opdm
    # so grab the opdm first from the noisy tpdm and then purify
    opdm = map_tpdm_to_opdm(tpdm, N)
    # opdm_new = mazziotti_opdm_purification(opdm, N)
    opdm_new = fixed_trace_positive_projection(opdm, N)

    # first multiply by N - 1 get back to just contracted.  Then another
    # for adjusting
    one_one_tensor = ((N - 1)**2) * opdm_new
    one_one_eye = wedge_product(one_one_tensor, np.eye(dim))

    d2_1 = (4 / (dim - 2)) * one_one_eye
    d2_1 -= ((4 * trace_value) / (dim * (dim - 2))) * eye_wedge_eye

    if __debug__:
        d2_1_trial = (4 / (dim - 2)) * wedge_product(
            opdm_new - (trace_value / dim) * np.eye(dim), np.eye(dim))
        assert np.allclose(d2_1_trial, d2_1)

    # we need to check a bunch of stuff about the reconstruct tpdm
    # a) does it have all the right symmetry
    # b) does it have the right trace and does it contract appropriately
    # c) does the d2_0 + d2_1 look like a contracted tpdm_partial fix

    tpdm_partial_fix = d2_0 + d2_1 + d2_2
    d2_matrix = four_tensor2matrix(tpdm_partial_fix)
    if __debug__:
        check_antisymmetric_d2(tpdm_partial_fix)
        # check conversion back to the tpdm_partial_fix
        np.testing.assert_allclose(matrix2four_tensor(d2_matrix),
                                   tpdm_partial_fix)

    residual = 10
    iter_max = 3000
    iter = 0
    while residual > error and iter < iter_max:
        w, v = np.linalg.eigh(d2_matrix)
        exposed_operator = []
        for ii in range(w.shape[0]):
            if w[ii] < float(-1.0E-13):
                if __debug__:
                    # checks to see if I'm sane?
                    assert v[:, [ii]].shape == (d2_matrix.shape[0], 1)
                    assert v[:, [ii]].dot(np.conj(
                        v[:, [ii]]).T).shape == d2_matrix.shape
                    # check_antisymmetric_index(v[:, [ii]])

                # get the exposing operator antisymmeterize
                eo = v[:, [ii]].dot(np.conj(v[:, [ii]]).T)
                eo_22_tensor = matrix2four_tensor(eo)
                eo_22_tensor_antiy = antisymmeterizer(eo_22_tensor)

                if __debug__:
                    check_antisymmetric_d2(
                        eo_22_tensor_antiy)  # redundant sanity check
                    # check if operator is hermetian
                    np.testing.assert_allclose(eo, np.conj(eo).T)

                # add the operator to my list
                exposed_operator.append(four_tensor2matrix(eo_22_tensor_antiy))

        # Now we want to find the coleman decomp of each of the exposing operators
        num_eo = len(exposed_operator)
        zero_contraction_eo = []
        for ii in range(num_eo):
            eo = matrix2four_tensor(exposed_operator[ii])
            eo_0, eo_1, eo_2 = coleman_decomposition(eo)
            zero_contraction_eo.append(four_tensor2matrix(eo_2))

            if __debug__:
                # check result is sane
                assert np.isclose(np.einsum('ijij', eo_0),
                                  np.einsum('ijij', eo))
                # check if it contracts to zero
                assert np.allclose(np.einsum('ikjk', eo_2),
                                   np.zeros_like(eo_2))
                # check if the einsum is correct
                assert np.allclose(np.einsum('ikjk', eo),
                                   np.einsum('ikjk', eo_0 + eo_1))
                # check if it reconstructs
                assert np.allclose(eo_0 + eo_1 + eo_2, eo)
                # check if it's hermetian
                assert np.allclose(zero_contraction_eo[-1],
                                   np.conj(zero_contraction_eo[-1]).T)
                # check if eo_2 is antisymmetric
                check_antisymmetric_d2(eo_2)

        # set up system of equations to solve for alpha terms
        Amatrix = np.zeros((num_eo, num_eo))
        bvector = np.zeros((num_eo, 1))
        for i, j in product(range(num_eo), repeat=2):
            Amatrix[i, j] = np.trace(exposed_operator[i].dot(
                zero_contraction_eo[j])).real
            if i == j:
                bvector[i,
                        0] = np.trace(d2_matrix.dot(exposed_operator[i])).real

        # alpha = np.linalg.solve(Amatrix, -bvector)
        [alpha, _, _, _] = np.linalg.lstsq(Amatrix, -bvector)
        if __debug__:
            assert np.allclose(np.dot(Amatrix, alpha), -bvector)

        # update the 2-RDM matrix
        d2_new = d2_matrix.copy()
        for ii in range(num_eo):
            d2_new += alpha[ii] * zero_contraction_eo[ii]

        if __debug__:
            # check if the new d2_new is orthogonal to zero_contraction
            for ii in range(num_eo):
                assert np.isclose(np.trace(d2_new.dot(exposed_operator[ii])),
                                  0.0)

        # check for conversion of the iterative method
        w, v = np.linalg.eigh(d2_new)
        residual = np.linalg.norm(w[w < 0])

        if disp:
            print("iter {:5.0f}\tdiff {:3.10e}\t".format(
                iter, np.linalg.norm(d2_new - d2_matrix)),
                  end='')
            print("trace new {:3.10f}\terror {:3.10e}".format(
                np.trace(d2_new).real, residual))

        d2_matrix = d2_new
        iter += 1

    tpdm = matrix2four_tensor(d2_matrix)
    return tpdm