def test_reorder_fermionic_modes(): ref_op = get_interaction_operator( FermionOperator( """ 0.0 [] + 1.0 [0^ 0] + 1.0 [1^ 1] + 1.0 [0^ 1^ 2 3] + 1.0 [1^ 1^ 2 2] """ ) ) reordered_op = get_interaction_operator( FermionOperator( """ 0.0 [] + 1.0 [0^ 0] + 1.0 [2^ 2] + 1.0 [0^ 2^ 1 3] + 1.0 [2^ 2^ 1 1] """ ) ) spin_block_op = reorder_fermionic_modes(ref_op, [0, 2, 1, 3]) assert reordered_op == spin_block_op spin_block_qubit_op = jordan_wigner(spin_block_op) interleaved_qubit_op = jordan_wigner(ref_op) spin_block_spectrum = eigenspectrum(spin_block_qubit_op) interleaved_spectrum = eigenspectrum(interleaved_qubit_op) assert np.allclose(spin_block_spectrum, interleaved_spectrum)
def test_apply_generated_unitary(self): """APply the generated unitary transformation from the fqe namespace """ norb = 4 nele = 3 time = 0.001 ops = FermionOperator('1^ 3^ 5 0', 2.0 - 2.j) + FermionOperator( '0^ 5^ 3 1', 2.0 + 2.j) wfn = fqe.get_number_conserving_wavefunction(nele, norb) wfn.set_wfn(strategy='random') wfn.normalize() reference = fqe.apply_generated_unitary(wfn, time, 'taylor', ops) h1e = numpy.zeros((2 * norb, 2 * norb), dtype=numpy.complex128) h2e = hamiltonian_utils.nbody_matrix(ops, norb) h2e = hamiltonian_utils.antisymm_two_body(h2e) hamil = general_hamiltonian.General(tuple([h1e, h2e])) compute = wfn.apply_generated_unitary(time, 'taylor', hamil) for key in wfn.sectors(): with self.subTest(key=key): diff = reference._civec[key].coeff - compute._civec[key].coeff err = linalg.norm(diff) self.assertTrue(err < 1.e-8)
def op1e(const, Tmat): fop = FermionOperator('', const) for p in range(n_orb): for q in range(n_orb): fop += FermionOperator( ((2*p , 1),(2*q , 0)), Tmat[p,q] ) fop += FermionOperator( ((2*p+1, 1),(2*q+1, 0)), Tmat[p,q] ) return fop
def __init__(self, qubit_pair_1, qubit_pair_2, system_n_qubits=None): self.spin_complement = True assert len(qubit_pair_1) == 2 assert len(qubit_pair_2) == 2 self.qubits = [qubit_pair_1, qubit_pair_2] self.complement_qubits = [ self.spin_complement_orbitals(qubit_pair_1), self.spin_complement_orbitals(qubit_pair_2) ] fermi_operator_1 = FermionOperator( '[{2}^ {3}^ {0} {1}] - [{0}^ {1}^ {2} {3}]'.format( *self.qubits[0], *self.qubits[1])) excitations_generators = [jordan_wigner(fermi_operator_1)] if [set(self.qubits[0]), set(self.qubits[1])] != [set(self.complement_qubits[0]), set(self.complement_qubits[1])] and \ [set(self.qubits[0]), set(self.qubits[1])] != [set(self.complement_qubits[1]), set(self.complement_qubits[0])]: fermi_operator_2 = FermionOperator( '[{2}^ {3}^ {0} {1}] - [{0}^ {1}^ {2} {3}]'.format( *self.complement_qubits[0], *self.complement_qubits[1])) excitations_generators.append(jordan_wigner(fermi_operator_2)) super(SpinCompEffDFExc, self).\ __init__(element='spin_d_f_exc_{}_{}'.format(qubit_pair_1, qubit_pair_2), order=2, n_var_parameters=1, system_n_qubits=system_n_qubits, excitations_generators=excitations_generators)
def screen_out_operator_terms_below_threshold( threshold: float, fermion_generator: FermionOperator, ignore_singles=False ) -> Tuple[np.ndarray, FermionOperator]: """Screen single and double excitation operators based on a guess for the amplitudes Args: threshold (float): threshold to select excitations. Only those with absolute amplitudes above the threshold are kept. fermion_generator (openfermion.FermionOperator): Fermion Operator containing the generators for the UCC ansatz Returns: amplitudes (np.array): screened amplitudes new_fermion_generator (openfermion.FermionOperator): screened Fermion Operator """ new_fermion_generator = FermionOperator() amplitudes = [] for op in fermion_generator.terms: if abs(fermion_generator.terms[op]) > threshold or ( len(op) == 2 and ignore_singles == True ): new_fermion_generator += FermionOperator( op, fermion_generator.terms[op] ) amplitudes.append(fermion_generator.terms[op]) amplitudes = np.array(amplitudes) return amplitudes, new_fermion_generator
def fci_fermion_operator_representation(norb: int, nele: int, m_s: int) -> 'FermionOperator': """Generate the Full Configuration interaction wavefunction in the openfermion FermionOperator representation with coefficients of 1.0. Args: norb (int) - number of spatial orbitals nele (int) - number of electrons m_s (int) - s_z spin quantum number Returns: FermionOperator """ nalpha, nbeta = alpha_beta_electrons(nele, m_s) gsstr = init_bitstring_groundstate(nalpha) alphadets = lexicographic_bitstring_generator(gsstr, norb) gsstr = init_bitstring_groundstate(nbeta) betadets = lexicographic_bitstring_generator(gsstr, norb) ops = FermionOperator('', 1.0) for bstr in betadets: for astr in alphadets: ops += determinant_to_ops(astr, bstr) return ops - FermionOperator('', 1.0)
def test_basic_split(self): """Test spliting the fermion operators for a simple case. """ test_ops = FermionOperator('1 1^', 1.0) test_ops = normal_ordered(test_ops) terms, _ = split_openfermion_tensor(test_ops) self.assertEqual(FermionOperator('1^ 1', -1.0), terms[2])
def __init__(self, qubit_1, qubit_2, system_n_qubits=None, encoding='jw'): self.spin_complement = True self.qubits = [[qubit_1], [qubit_2]] self.complement_qubits = [ self.spin_complement_orbitals([qubit_1]), self.spin_complement_orbitals([qubit_2]) ] fermi_operator_1 = FermionOperator('[{1}^ {0}] - [{0}^ {1}]'.format( qubit_2, qubit_1)) if encoding == 'jw': excitations_generators = [jordan_wigner(fermi_operator_1)] else: assert encoding == 'bk' excitations_generators = [bravyi_kitaev(fermi_operator_1)] if {*self.qubits[0], *self.qubits[1]} != {*self.complement_qubits[0], *self.complement_qubits[1]} and \ {*self.qubits[0], *self.qubits[1]} != {*self.complement_qubits[1], *self.complement_qubits[0]}: fermi_operator_2 = FermionOperator( '[{1}^ {0}] - [{0}^ {1}]'.format(self.complement_qubits[1][0], self.complement_qubits[0][0])) if encoding == 'jw': excitations_generators = [jordan_wigner(fermi_operator_2)] else: assert encoding == 'bk' excitations_generators = [bravyi_kitaev(fermi_operator_2)] super(SpinCompSFExc, self).\ __init__(element='spin_s_f_exc_{}_{}'.format(qubit_2, qubit_1), order=1, n_var_parameters=1, excitations_generators=excitations_generators, system_n_qubits=system_n_qubits)
def transform_to_spin_broken(ops: 'FermionOperator') -> 'FermionOperator': """Convert a Fermion Operator string from number broken to spin broken operators. Args: ops (FermionOperator) - input FermionOperator Returns: (FermionOperator) - transformed FermionOperator to spin broken indexing """ newstr = FermionOperator() for term in ops.terms: opstr = '' for element in term: if element[0] % 2: if element[1]: opstr += str(element[0]) + ' ' else: opstr += str(element[0]) + '^ ' else: if element[1]: opstr += str(element[0]) + '^ ' else: opstr += str(element[0]) + ' ' newstr += FermionOperator(opstr, ops.terms[term]) return newstr
def test_mutate_config_vacuum(self): """Adding a single electron to the vavuum should return a parity of one and the set bit. """ ref0 = [1, 0, 1] ref0a = [1, 0, 0] ops0 = FermionOperator('0^', 1.0) ref1 = [0, 8, 1] ref1a = [0, 8, 0] ops1 = FermionOperator('7^', 1.0) ops2 = FermionOperator('0', 1.0) ops3 = FermionOperator('7', 1.0) ref23 = [0, 0, 0] for term in ops0.terms: self.assertListEqual( ref0, list(openfermion_utils.mutate_config(0, 0, term))) self.assertListEqual( ref0a, list(openfermion_utils.mutate_config(1, 0, term))) for term in ops1.terms: self.assertListEqual( ref1, list(openfermion_utils.mutate_config(0, 0, term))) self.assertListEqual( ref1a, list(openfermion_utils.mutate_config(0, 8, term))) for term in ops2.terms: self.assertListEqual( ref23, list(openfermion_utils.mutate_config(0, 0, term))) for term in ops3.terms: self.assertListEqual( ref23, list(openfermion_utils.mutate_config(0, 0, term)))
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_evolve_spinful_fermionop(self): """ Make sure the spin-orbital reordering is working by comparing time evolution """ wfn = Wavefunction([[2, 0, 2]]) wfn.set_wfn(strategy='random') wfn.normalize() cirq_wf = to_cirq(wfn).reshape((-1, 1)) op_to_apply = FermionOperator() 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) opmat = get_sparse_operator(op_to_apply, n_qubits=4).toarray() dt = 0.765 new_state_cirq = scipy.linalg.expm(-1j * dt * opmat) @ cirq_wf new_state_wfn = from_cirq(new_state_cirq.flatten(), thresh=1.0E-12) test_state = wfn.time_evolve(dt, op_to_apply) self.assertTrue( numpy.allclose(test_state.get_coeff((2, 0)), new_state_wfn.get_coeff((2, 0))))
def test_fqe_to_fermion_operator(self): """Convert the fqe representation to openfermion operators. Note: Due to the unique OpenFermion data structure and the conversion of the data internally, test requires an iterative stucture rather than assertDictEqual """ def _calc_coeff(val): return round(val / numpy.sqrt(30.0), 7) + .0j coeff = [ _calc_coeff(1.0), _calc_coeff(2.0), _calc_coeff(3.0), _calc_coeff(4.0) ] data = numpy.array([[coeff[0]], [coeff[1]], [coeff[2]], [coeff[3]]], dtype=numpy.complex128) test = FermionOperator('0^ 1^', coeff[0]) test += FermionOperator('0^ 3^', coeff[1]) test += FermionOperator('2^ 1^', coeff[2]) test += FermionOperator('2^ 3^', coeff[3]) wfn = wavefunction.Wavefunction([[2, 0, 2]]) data = numpy.reshape(data, (2, 2)) passed_data = {(2, 0): data} wfn.set_wfn(strategy='from_data', raw_data=passed_data) ops = openfermion_utils.fqe_to_fermion_operator(wfn) self.assertListEqual(list(ops.terms.keys()), list(test.terms.keys())) for term in ops.terms: self.assertAlmostEqual(ops.terms[term], test.terms[term])
def test_integration_jellium_hamiltonian_with_negation(self): hamiltonian = normal_ordered( jellium_model(Grid(2, 3, 1.), plane_wave=False)) part_a = FermionOperator.zero() part_b = FermionOperator.zero() add_to_a_or_b = 0 # add to a if 0; add to b if 1 for term, coeff in hamiltonian.terms.items(): # Partition terms in the Hamiltonian into part_a or part_b if add_to_a_or_b: part_a += FermionOperator(term, coeff) else: part_b += FermionOperator(term, coeff) add_to_a_or_b ^= 1 reference = normal_ordered(commutator(part_a, part_b)) result = commutator_ordered_diagonal_coulomb_with_two_body_operator( part_a, part_b) self.assertTrue(result.isclose(reference)) negative = commutator_ordered_diagonal_coulomb_with_two_body_operator( part_b, part_a) result += negative self.assertTrue(result.isclose(FermionOperator.zero()))
def test_identity_recognized_as_potential_term(self): potential_terms, kinetic_terms = ( diagonal_coulomb_potential_and_kinetic_terms_as_arrays( FermionOperator.identity())) self.assertListEqual(list(potential_terms), [FermionOperator.identity()]) self.assertListEqual(list(kinetic_terms), [])
def test_ladder_op(self): """Make sure that our qubit ladder operators are the same as the jw transformation """ create = jordan_wigner(FermionOperator('10^', 1. + .0j)) annihilate = jordan_wigner(FermionOperator('10', 1. + .0j)) self.assertEqual(create, openfermion_utils.ladder_op(10, 1)) self.assertEqual(annihilate, openfermion_utils.ladder_op(10, 0))
def test_sparse(): """Test some of the functions in SparseHamiltonian.""" oper = FermionOperator('0 0^') oper += FermionOperator('1 1^') test = sparse_hamiltonian.SparseHamiltonian(oper) assert test.rank() == 2 test = sparse_hamiltonian.SparseHamiltonian(oper, False) assert not test.is_individual()
def construct_ham_1body(hint, orb_qubit_map=None): if orb_qubit_map is None: orb_qubit_map = list(range(hint.shape[0])) ham_1body = FermionOperator() for (p, p_qubit), (q, q_qubit) in product(enumerate(orb_qubit_map), repeat=2): ham_1body += FermionOperator(((p_qubit, 1), (q_qubit, 0)), hint[p, q]) return ham_1body
def benchmark_fermion_math_and_normal_order(n_qubits, term_length, power): """Benchmark both arithmetic operators and normal ordering on fermions. The idea is we generate two random FermionTerms, A and B, each acting on n_qubits with term_length operators. We then compute (A + B) ** power. This is costly that is the first benchmark. The second benchmark is in normal ordering whatever comes out. Args: n_qubits: The number of qubits on which these terms act. term_length: The number of operators in each term. power: Int, the exponent to which to raise sum of the two terms. Returns: runtime_math: The time it takes to perform (A + B) ** power runtime_normal_order: The time it takes to perform FermionOperator.normal_order() """ # Generate random operator strings. operators_a = [(numpy.random.randint(n_qubits), numpy.random.randint(2))] operators_b = [(numpy.random.randint(n_qubits), numpy.random.randint(2))] for _ in range(term_length): # Make sure the operator is not trivially zero. operator_a = (numpy.random.randint(n_qubits), numpy.random.randint(2)) while operator_a == operators_a[-1]: operator_a = (numpy.random.randint(n_qubits), numpy.random.randint(2)) operators_a += [operator_a] # Do the same for the other operator. operator_b = (numpy.random.randint(n_qubits), numpy.random.randint(2)) while operator_b == operators_b[-1]: operator_b = (numpy.random.randint(n_qubits), numpy.random.randint(2)) operators_b += [operator_b] # Initialize FermionTerms and then sum them together. fermion_term_a = FermionOperator(tuple(operators_a), float(numpy.random.randn())) fermion_term_b = FermionOperator(tuple(operators_b), float(numpy.random.randn())) fermion_operator = fermion_term_a + fermion_term_b # Exponentiate. start_time = time.time() fermion_operator **= power runtime_math = time.time() - start_time # Normal order. start_time = time.time() normal_ordered(fermion_operator) runtime_normal_order = time.time() - start_time # Return. return runtime_math, runtime_normal_order
def test_odd_rank_error(self): """Check that odd rank operators are not processed """ ops = [] ops.append(FermionOperator('10^ 1 2', 1.0)) ops.append(FermionOperator('5^ 3^ 7^ 8 7', 1.0)) for rank in range(2): with self.subTest(rank=(2 * rank + 1)): self.assertRaises(ValueError, split_openfermion_tensor, ops[rank])
def test_ascending_index_order(self): """Transformations in openfermion return FermionOperators from highest index to the lowest index. Occationally we would like to reverse this. """ ops = FermionOperator('5^ 4^ 3^ 2^ 1^ 0^', 1.0) test = FermionOperator('0^ 2^ 4^ 1^ 3^ 5^', 1.0) for key in ops.terms: f_ops = openfermion_utils.ascending_index_order( key, ops.terms[key]) self.assertEqual(f_ops, test)
def test_mask_hermitian_terms_results_in_duplicated_row(self): mask = bit_mask_of_modes_acted_on_by_fermionic_terms( [FermionOperator('2^ 3'), FermionOperator('3^ 2')], n_qubits=5) expected_mask = numpy.array([[False, False], [False, False], [True, True], [True, True], [False, False]]) self.assertTrue(numpy.array_equal(mask, expected_mask))
def test_fermion_opstring_to_bitstring(self): """Interoperability between OpenFermion and the FQE necessitates that we can convert between the representations. """ ops = FermionOperator('0^ 1^', 1. + .0j) + \ FermionOperator('2^ 5^', 2. + .0j) + \ FermionOperator('8^ 9^', 3. + .0j) test = [[1, 1, 1. + .0j], [2, 4, 2. + .0j], [16, 16, 3. + .0j]] result = openfermion_utils.fermion_opstring_to_bitstring(ops) for val in result: self.assertTrue(val in test)
def test_update_operator_coeff(self): """Update the coefficients of an operator string """ coeff = numpy.ones(6, dtype=numpy.complex64) test = FermionOperator('1^', 1. + .0j) ops = FermionOperator('1^', 1. + .0j) for i in range(2, 7): ops += FermionOperator(str(i) + '^', i * (1. + .0j)) test += FermionOperator(str(i) + '^', 1. + .0j) openfermion_utils.update_operator_coeff(ops, coeff) self.assertEqual(ops, test)
def test_fermoinops_tomatrix(self): norb = 4 ops = FermionOperator('9^ 1') self.assertRaises(ValueError, fermionops_tomatrix, ops, norb) ops = FermionOperator('10^ 4') self.assertRaises(ValueError, fermionops_tomatrix, ops, norb) ops = FermionOperator('3^ 1 4') self.assertRaises(ValueError, fermionops_tomatrix, ops, norb) ops = FermionOperator('3 1') self.assertRaises(ValueError, fermionops_tomatrix, ops, norb) ops = FermionOperator('3^ 1^') self.assertRaises(ValueError, fermionops_tomatrix, ops, norb)
def construct_ham_2body(gint, orb_qubit_map=None): if orb_qubit_map is None: orb_qubit_map = list(range(gint.shape[0])) ham_2body = FermionOperator() for (p, p_qubit), (q, q_qubit), (r, r_qubit), (s, s_qubit) in product( enumerate(orb_qubit_map), repeat=4): if p != r and q != s: ham_2body += FermionOperator( ((p_qubit, 1), (r_qubit, 1), (s_qubit, 0), (q_qubit, 0)), gint[p, q, r, s]) * 0.5 return ham_2body
def get_ground_state_rdm_from_qubit_op( qubit_operator: QubitOperator, n_particles: int ) -> InteractionRDM: """Diagonalize operator and compute the ground state 1- and 2-RDM Args: qubit_operator (openfermion.QubitOperator): The openfermion operator to diagonalize n_particles (int): number of particles in the target ground state Returns: rdm (openfermion.InteractionRDM): interaction RDM of the ground state with the particle number n_particles """ sparse_operator = get_sparse_operator(qubit_operator) e, ground_state_wf = jw_get_ground_state_at_particle_number( sparse_operator, n_particles ) # float/np.array pair n_qubits = count_qubits(qubit_operator) one_body_tensor = [] for i in range(n_qubits): for j in range(n_qubits): idag_j = get_sparse_operator( FermionOperator(f"{i}^ {j}"), n_qubits=n_qubits ) idag_j = idag_j.toarray() one_body_tensor.append( np.conjugate(ground_state_wf) @ idag_j @ ground_state_wf ) one_body_tensor = np.array(one_body_tensor) one_body_tensor = one_body_tensor.reshape(n_qubits, n_qubits) two_body_tensor = np.zeros((n_qubits,) * 4, dtype=complex) for p in range(n_qubits): for q in range(0, p + 1): for r in range(n_qubits): for s in range(0, r + 1): pdag_qdag_r_s = get_sparse_operator( FermionOperator(f"{p}^ {q}^ {r} {s}"), n_qubits=n_qubits ) pdag_qdag_r_s = pdag_qdag_r_s.toarray() rdm_element = ( np.conjugate(ground_state_wf) @ pdag_qdag_r_s @ ground_state_wf ) two_body_tensor[p, q, r, s] = rdm_element two_body_tensor[q, p, r, s] = -rdm_element two_body_tensor[q, p, s, r] = rdm_element two_body_tensor[p, q, s, r] = -rdm_element return InteractionRDM(one_body_tensor, two_body_tensor)
def test_split_rank2468(self): """Split up to rank four operators """ ops = {} ops[2] = FermionOperator('10^ 1', 1.0) ops[4] = FermionOperator('3^ 7^ 8 7', 1.0) ops[6] = FermionOperator('7^ 6^ 2^ 2 1 9', 1.0) ops[8] = FermionOperator('0^ 3^ 2^ 11^ 12 3 9 4', 1.0) full_string = ops[2] + ops[4] + ops[6] + ops[8] terms, _ = split_openfermion_tensor(full_string) for rank in range(2, 9, 2): with self.subTest(rank=rank): self.assertEqual(ops[rank], terms[rank])
def test_mutate_config_operator_order_with_beta(self): """Same test as before but now we create a beta electron inbetween to ensure that the parity of the alpha string is accounted for """ ref0 = [29, 1, -1] ops0 = FermionOperator('8^ 1^ 2', 1.0) ref1 = [29, 1, 1] ops1 = FermionOperator('2 1^ 8^', 1.0) for term in ops0.terms: self.assertListEqual( ref0, list(openfermion_utils.mutate_config(15, 0, term))) for term in ops1.terms: self.assertListEqual( ref1, list(openfermion_utils.mutate_config(15, 0, term)))
def test_mutate_config_alpha_beta_order(self): """Any beta operator must commute with all alpha operators before it can act on the reference state. This changes the parity """ ref0 = [1, 4, -1] ops0 = FermionOperator('5^', 1.0) ref1 = [9, 0, 1] ops1 = FermionOperator('7', 1.0) for term in ops0.terms: self.assertListEqual( ref0, list(openfermion_utils.mutate_config(1, 0, term))) for term in ops1.terms: self.assertListEqual( ref1, list(openfermion_utils.mutate_config(9, 8, term)))