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