def test_apply_spinful_fermionop(self): """ Make sure the spin-orbital reordering is working by comparing apply operation """ wfn = Wavefunction([[2, 0, 2]]) wfn.set_wfn(strategy='random') wfn.normalize() cirq_wf = to_cirq(wfn).reshape((-1, 1)) op_to_apply = FermionOperator() test_state = copy.deepcopy(wfn) test_state.set_wfn('zero') for p, q, r, s in product(range(2), repeat=4): op = FermionOperator( ((2 * p, 1), (2 * q + 1, 1), (2 * r + 1, 0), (2 * s, 0)), coefficient=numpy.random.randn()) op_to_apply += op + hermitian_conjugated(op) test_state += wfn.apply(op + hermitian_conjugated(op)) opmat = get_sparse_operator(op_to_apply, n_qubits=4).toarray() new_state_cirq = opmat @ cirq_wf # this part is because we need to pass a normalized wavefunction norm_constant = new_state_cirq.conj().T @ new_state_cirq new_state_cirq /= numpy.sqrt(norm_constant) new_state_wfn = from_cirq(new_state_cirq.flatten(), thresh=1.0E-12) new_state_wfn.scale(numpy.sqrt(norm_constant)) self.assertTrue( numpy.allclose(test_state.get_coeff((2, 0)), new_state_wfn.get_coeff((2, 0))))
def test_apply_number(self): norb = 4 test = numpy.random.rand(norb, norb) diag = numpy.random.rand(norb * 2) diag2 = copy.deepcopy(diag) e_0 = 0 for i in range(norb): e_0 += diag[i + norb] diag2[i + norb] = -diag[i + norb] hamil = diagonal_hamiltonian.Diagonal(diag2, e_0=e_0) hamil._conserve_number = False wfn = Wavefunction([[4, 2, norb]], broken=['number']) wfn.set_wfn(strategy='from_data', raw_data={(4, 2): test}) out1 = wfn.apply(hamil) hamil = diagonal_hamiltonian.Diagonal(diag) wfn = Wavefunction([[4, 2, norb]]) wfn.set_wfn(strategy='from_data', raw_data={(4, 2): test}) out2 = wfn.apply(hamil) self.assertTrue( numpy.allclose(out1._civec[(4, 2)].coeff, out2._civec[(4, 2)].coeff))
def get_acse_residual_fqe(fqe_wf: Wavefunction, fqe_ham: RestrictedHamiltonian, norbs: int) -> np.ndarray: """Get the ACSE block by using reduced density operators that are Sz spin adapted R^{ij}_{lk} = <psi | [i^ j^ k l, A] | psi> alpha-alpha, beta-beta, alpha-beta, and beta-alpha blocks we do not compression over alpha-alpha or beta-beta so these are still norbs**2 in linear dimension. In other words, we do computation on elements we know should be zero. This is for simplicity in the code. Args: fqe_wf: fqe.Wavefunction object to calculate expectation value with fqe_ham: fqe.RestrictedHamiltonian operator corresponding to a chemical Hamiltonian norbs: Number of orbitals. Number of spatial orbitals Returns: Gradient of the i^ j^ k l operator """ acse_aa = np.zeros((norbs, norbs, norbs, norbs), dtype=np.complex128) acse_bb = np.zeros((norbs, norbs, norbs, norbs), dtype=np.complex128) acse_ab = np.zeros((norbs, norbs, norbs, norbs), dtype=np.complex128) fqe_appA = fqe_wf.apply(fqe_ham) for p, q, r, s in product(range(norbs), repeat=4): # alpha-alpha block real if p != q and r != s: rdo = ((2 * p, 1), (2 * q, 1), (2 * r, 0), (2 * s, 0)) rdo = 1j * (of.FermionOperator(rdo) - of.hermitian_conjugated(of.FermionOperator(rdo))) val1 = fqe.util.vdot(fqe_appA, fqe_wf.apply(rdo)) val2 = np.conjugate(val1) acse_aa[p, q, r, s] = (val2 - val1) / 2j # alpha-alpha block imag rdo = ((2 * p, 1), (2 * q, 1), (2 * r, 0), (2 * s, 0)) rdo = of.FermionOperator(rdo) + of.hermitian_conjugated( of.FermionOperator(rdo)) val1 = fqe.util.vdot(fqe_appA, fqe_wf.apply(rdo)) val2 = np.conjugate(val1) acse_aa[p, q, r, s] += (val2 - val1) / 2 # beta-beta block real rdo = ( (2 * p + 1, 1), (2 * q + 1, 1), (2 * r + 1, 0), (2 * s + 1, 0), ) rdo = 1j * (of.FermionOperator(rdo) - of.hermitian_conjugated(of.FermionOperator(rdo))) val1 = fqe.util.vdot(fqe_appA, fqe_wf.apply(rdo)) val2 = np.conjugate(val1) acse_bb[p, q, r, s] += (val2 - val1) / 2j # beta-beta block imag rdo = ( (2 * p + 1, 1), (2 * q + 1, 1), (2 * r + 1, 0), (2 * s + 1, 0), ) rdo = of.FermionOperator(rdo) + of.hermitian_conjugated( of.FermionOperator(rdo)) val1 = fqe.util.vdot(fqe_appA, fqe_wf.apply(rdo)) val2 = np.conjugate(val1) acse_bb[p, q, r, s] += (val2 - val1) / 2 # alpha-beta block real rdo = ((2 * p, 1), (2 * q + 1, 1), (2 * r + 1, 0), (2 * s, 0)) rdo = 1j * (of.FermionOperator(rdo) - of.hermitian_conjugated(of.FermionOperator(rdo))) val1 = fqe.util.vdot(fqe_appA, fqe_wf.apply(rdo)) val2 = np.conjugate(val1) acse_ab[p, q, r, s] += (val2 - val1) / 2j # alpha-beta block imag rdo = ((2 * p, 1), (2 * q + 1, 1), (2 * r + 1, 0), (2 * s, 0)) rdo = of.FermionOperator(rdo) + of.hermitian_conjugated( of.FermionOperator(rdo)) val1 = fqe.util.vdot(fqe_appA, fqe_wf.apply(rdo)) val2 = np.conjugate(val1) # fqe.util.vdot(fqe_wf.apply(rdo), fqe_appA) acse_ab[p, q, r, s] += (val2 - val1) / 2 # unroll residual blocks into full matrix acse_residual = np.zeros((2 * norbs, 2 * norbs, 2 * norbs, 2 * norbs), dtype=np.complex128) acse_residual[::2, ::2, ::2, ::2] = acse_aa acse_residual[1::2, 1::2, 1::2, 1::2] = acse_bb acse_residual[::2, 1::2, 1::2, ::2] = acse_ab acse_residual[::2, 1::2, ::2, 1::2] = np.einsum("ijkl->ijlk", -acse_ab) acse_residual[1::2, ::2, ::2, 1::2] = np.einsum("ijkl->jilk", acse_ab) acse_residual[1::2, ::2, 1::2, ::2] = np.einsum("ijkl->ijlk", -acse_residual[1::2, ::2, ::2, 1::2]) return acse_residual