def test_ccr_offsite_even_aa(self): a2 = FermionOperator(((2, 0), )) a4 = FermionOperator(((4, 0), )) self.assertTrue( normal_ordered(a2 * a4).isclose(normal_ordered(-a4 * a2))) self.assertTrue( jordan_wigner(a2 * a4).isclose(jordan_wigner(-a4 * a2)))
def test_ccr_offsite_even_cc(self): c2 = FermionOperator(((2, 1), )) c4 = FermionOperator(((4, 1), )) self.assertTrue( normal_ordered(c2 * c4).isclose(normal_ordered(-c4 * c2))) self.assertTrue( jordan_wigner(c2 * c4).isclose(jordan_wigner(-c4 * c2)))
def test_ccr_offsite_odd_cc(self): c1 = FermionOperator(((1, 1), )) c4 = FermionOperator(((4, 1), )) self.assertTrue( normal_ordered(c1 * c4).isclose(normal_ordered(-c4 * c1))) self.assertTrue( jordan_wigner(c1 * c4).isclose(jordan_wigner(-c4 * c1)))
def test_ccr_offsite_odd_aa(self): a1 = FermionOperator(((1, 0), )) a4 = FermionOperator(((4, 0), )) self.assertTrue( normal_ordered(a1 * a4).isclose(normal_ordered(-a4 * a1))) self.assertTrue( jordan_wigner(a1 * a4).isclose(jordan_wigner(-a4 * a1)))
def test_get_molecular_operator(self): coefficient = 3. operators = ((2, 1), (3, 0), (0, 0), (3, 1)) op = FermionOperator(operators, coefficient) molecular_operator = get_interaction_operator(op) fermion_operator = get_fermion_operator(molecular_operator) fermion_operator = normal_ordered(fermion_operator) self.assertTrue(normal_ordered(op).isclose(fermion_operator))
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 operator_number 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_ccr_onsite(self): c1 = FermionOperator(((1, 1), )) a1 = hermitian_conjugated(c1) self.assertTrue( normal_ordered( c1 * a1).isclose(FermionOperator(()) - normal_ordered(a1 * c1))) self.assertTrue( jordan_wigner( c1 * a1).isclose(QubitOperator(()) - jordan_wigner(a1 * c1)))
def test_jordan_wigner_twobody_interaction_op_allunique(self): test_op = FermionOperator('1^ 2^ 3 4') test_op += hermitian_conjugated(test_op) retransformed_test_op = reverse_jordan_wigner( jordan_wigner(get_interaction_operator(test_op))) self.assertTrue( normal_ordered(retransformed_test_op).isclose( normal_ordered(test_op)))
def test_inverse_fourier_transform_2d(self): grid = Grid(dimensions=2, scale=1.5, length=3) spinless = True geometry = [('H', (0, 0)), ('H', (0.5, 0.8))] h_plane_wave = plane_wave_hamiltonian(grid, geometry, spinless, True) h_dual_basis = plane_wave_hamiltonian(grid, geometry, spinless, False) h_dual_basis_t = inverse_fourier_transform(h_dual_basis, grid, spinless) self.assertTrue( normal_ordered(h_dual_basis_t).isclose( normal_ordered(h_plane_wave)))
def test_xy(self): xy = QubitOperator(((4, 'X'), (5, 'Y')), -2.j) transmed_xy = reverse_jordan_wigner(xy) retransmed_xy = jordan_wigner(transmed_xy) expected1 = -2j * (FermionOperator(((4, 1), ), 1j) - FermionOperator( ((4, 0), ), 1j)) expected2 = (FermionOperator(((5, 1), )) - FermionOperator(((5, 0), ))) expected = expected1 * expected2 self.assertTrue(xy.isclose(retransmed_xy)) self.assertTrue( normal_ordered(transmed_xy).isclose(normal_ordered(expected)))
def test_yy(self): yy = QubitOperator(((2, 'Y'), (3, 'Y')), 2.) transmed_yy = reverse_jordan_wigner(yy) retransmed_yy = jordan_wigner(transmed_yy) expected1 = -(FermionOperator(((2, 1), ), 2.) + FermionOperator( ((2, 0), ), 2.)) expected2 = (FermionOperator(((3, 1), )) - FermionOperator(((3, 0), ))) expected = expected1 * expected2 self.assertTrue(yy.isclose(retransmed_yy)) self.assertTrue( normal_ordered(transmed_yy).isclose(normal_ordered(expected)))
def test_fourier_transform(self): grid = Grid(dimensions=1, scale=1.5, length=3) spinless_set = [True, False] geometry = [('H', (0, )), ('H', (0.5, ))] for spinless in spinless_set: h_plane_wave = plane_wave_hamiltonian(grid, geometry, spinless, True) h_dual_basis = plane_wave_hamiltonian(grid, geometry, spinless, False) h_plane_wave_t = fourier_transform(h_plane_wave, grid, spinless) self.assertTrue( normal_ordered(h_plane_wave_t).isclose( normal_ordered(h_dual_basis)))
def test_xx(self): xx = QubitOperator(((3, 'X'), (4, 'X')), 2.) transmed_xx = reverse_jordan_wigner(xx) retransmed_xx = jordan_wigner(transmed_xx) expected1 = (FermionOperator(((3, 1), ), 2.) - FermionOperator( ((3, 0), ), 2.)) expected2 = (FermionOperator(((4, 1), ), 1.) + FermionOperator( ((4, 0), ), 1.)) expected = expected1 * expected2 self.assertTrue(xx.isclose(retransmed_xx)) self.assertTrue( normal_ordered(transmed_xx).isclose(normal_ordered(expected)))
def test_yx(self): yx = QubitOperator(((0, 'Y'), (1, 'X')), -0.5) transmed_yx = reverse_jordan_wigner(yx) retransmed_yx = jordan_wigner(transmed_yx) expected1 = 1j * (FermionOperator(((0, 1), )) + FermionOperator( ((0, 0), ))) expected2 = -0.5 * (FermionOperator(((1, 1), )) + FermionOperator( ((1, 0), ))) expected = expected1 * expected2 self.assertTrue(yx.isclose(retransmed_yx)) self.assertTrue( normal_ordered(transmed_yx).isclose(normal_ordered(expected)))
def dual_basis_jellium_hamiltonian(grid_length, dimension=3, wigner_seitz_radius=10., n_particles=None, spinless=True): """Return the jellium Hamiltonian with the given parameters. Args: grid_length (int): The number of spatial orbitals per dimension. dimension (int): The dimension of the system. wigner_seitz_radius (float): The radius per particle in Bohr. n_particles (int): The number of particles in the system. Defaults to half filling if not specified. """ n_qubits = grid_length**dimension if not spinless: n_qubits *= 2 if n_particles is None: # Default to half filling fraction. n_particles = n_qubits // 2 if not (0 <= n_particles <= n_qubits): raise ValueError('n_particles must be between 0 and the number of' ' spin-orbitals.') # Compute appropriate length scale. length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, dimension) grid = Grid(dimension, grid_length, length_scale) hamiltonian = jellium_model(grid, spinless=spinless, plane_wave=False) hamiltonian = normal_ordered(hamiltonian) hamiltonian.compress() return hamiltonian
def double_commutator(op1, op2, op3, indices2=None, indices3=None, is_hopping_operator2=None, is_hopping_operator3=None): """Return the double commutator [op1, [op2, op3]]. Assumes the operators are from the dual basis Hamiltonian. Args: op1, op2, op3 (FermionOperators): operators for the commutator. indices2, indices3 (set): The indices op2 and op3 act on. is_hopping_operator2 (bool): Whether op2 is a hopping operator. is_hopping_operator3 (bool): Whether op3 is a hopping operator. Returns: The double commutator of the given operators. """ if is_hopping_operator2 and is_hopping_operator3: indices2 = set(indices2) indices3 = set(indices3) # Determine which indices both op2 and op3 act on. try: intersection, = indices2.intersection(indices3) except ValueError: return FermionOperator.zero() # Remove the intersection from the set of indices, since it will get # cancelled out in the final result. indices2.remove(intersection) indices3.remove(intersection) # Find the indices of the final output hopping operator. index2, = indices2 index3, = indices3 coeff2 = op2.terms[list(op2.terms)[0]] coeff3 = op3.terms[list(op3.terms)[0]] commutator23 = (FermionOperator( ((index2, 1), (index3, 0)), coeff2 * coeff3) + FermionOperator( ((index3, 1), (index2, 0)), -coeff2 * coeff3)) else: commutator23 = normal_ordered(commutator(op2, op3)) return normal_ordered(commutator(op1, commutator23))
def get_qubit_expectations(self, qubit_operator): """Return expectations of QubitOperator in new QubitOperator. Args: qubit_operator: QubitOperator instance to be evaluated on this InteractionRDM. Returns: QubitOperator: QubitOperator with coefficients corresponding to expectation values of those operators. Raises: InteractionRDMError: Observable not contained in 1-RDM or 2-RDM. """ from fermilib.transforms import reverse_jordan_wigner qubit_operator_expectations = copy.deepcopy(qubit_operator) del qubit_operator_expectations.terms[()] for qubit_term in qubit_operator_expectations.terms: expectation = 0. # Map qubits back to fermions. reversed_fermion_operators = reverse_jordan_wigner( QubitOperator(qubit_term), self.n_qubits) reversed_fermion_operators = normal_ordered( reversed_fermion_operators) # Loop through fermion terms. for fermion_term in reversed_fermion_operators.terms: coefficient = reversed_fermion_operators.terms[fermion_term] # Handle molecular term. if FermionOperator(fermion_term).is_molecular_term(): if not fermion_term: expectation += coefficient else: indices = [operator[0] for operator in fermion_term] rdm_element = self[indices] expectation += rdm_element * coefficient # Handle non-molecular terms. elif len(fermion_term) > 4: raise InteractionRDMError('Observable not contained ' 'in 1-RDM or 2-RDM.') qubit_operator_expectations.terms[qubit_term] = expectation return qubit_operator_expectations
def get_interaction_operator(fermion_operator, n_qubits=None): """Convert a 2-body fermionic operator to InteractionOperator. This function should only be called on fermionic operators which consist of only a_p^\dagger a_q and a_p^\dagger a_q^\dagger a_r a_s terms. The one-body terms are stored in a matrix, one_body[p, q], and the two-body terms are stored in a tensor, two_body[p, q, r, s]. Returns: interaction_operator: An instance of the InteractionOperator class. Raises: TypeError: Input must be a FermionOperator. TypeError: FermionOperator does not map to InteractionOperator. Warning: Even assuming that each creation or annihilation operator appears at most a constant number of times in the original operator, the runtime of this method is exponential in the number of qubits. """ if not isinstance(fermion_operator, FermionOperator): raise TypeError('Input must be a FermionOperator.') if n_qubits is None: n_qubits = count_qubits(fermion_operator) if n_qubits < count_qubits(fermion_operator): n_qubits = count_qubits(fermion_operator) # Normal order the terms and initialize. fermion_operator = normal_ordered(fermion_operator) constant = 0. one_body = numpy.zeros((n_qubits, n_qubits), complex) two_body = numpy.zeros((n_qubits, n_qubits, n_qubits, n_qubits), complex) # Loop through terms and assign to matrix. for term in fermion_operator.terms: coefficient = fermion_operator.terms[term] # Handle constant shift. if len(term) == 0: constant = coefficient elif len(term) == 2: # Handle one-body terms. if [operator[1] for operator in term] == [1, 0]: p, q = [operator[0] for operator in term] one_body[p, q] = coefficient else: raise InteractionOperatorError('FermionOperator does not map ' 'to InteractionOperator.') elif len(term) == 4: # Handle two-body terms. if [operator[1] for operator in term] == [1, 1, 0, 0]: p, q, r, s = [operator[0] for operator in term] two_body[p, q, r, s] = coefficient else: raise InteractionOperatorError('FermionOperator does not map ' 'to InteractionOperator.') else: # Handle non-molecular Hamiltonian. raise InteractionOperatorError('FermionOperator does not map ' 'to InteractionOperator.') # Form InteractionOperator and return. interaction_operator = InteractionOperator(constant, one_body, two_body) return interaction_operator
def hartree_fock_state_jellium(grid, n_electrons, spinless=True, plane_wave=False): """Give the Hartree-Fock state of jellium. Args: grid (Grid): The discretization to use. n_electrons (int): Number of electrons in the system. spinless (bool): Whether to use the spinless model or not. plane_wave (bool): Whether to return the Hartree-Fock state in the plane wave (True) or dual basis (False). Notes: The jellium model is built up by filling the lowest-energy single-particle states in the plane-wave Hamiltonian until n_electrons states are filled. """ # Get the jellium Hamiltonian in the plane wave basis. hamiltonian = jellium_model(grid, spinless, plane_wave=True) hamiltonian = normal_ordered(hamiltonian) hamiltonian.compress() # Enumerate the single-particle states. n_single_particle_states = (grid.length**grid.dimensions) if not spinless: n_single_particle_states *= 2 # Compute the energies for each of the single-particle states. single_particle_energies = numpy.zeros(n_single_particle_states, dtype=float) for i in range(n_single_particle_states): single_particle_energies[i] = hamiltonian.terms.get(((i, 1), (i, 0)), 0.0) # The number of occupied single-particle states is the number of electrons. # Those states with the lowest single-particle energies are occupied first. occupied_states = single_particle_energies.argsort()[:n_electrons] if plane_wave: # In the plane wave basis the HF state is a single determinant. hartree_fock_state_index = numpy.sum(2**occupied_states) hartree_fock_state = csr_matrix( ([1.0], ([hartree_fock_state_index], [0])), shape=(2**n_single_particle_states, 1)) else: # Inverse Fourier transform the creation operators for the state to get # to the dual basis state, then use that to get the dual basis state. hartree_fock_state_creation_operator = FermionOperator.identity() for state in occupied_states[::-1]: hartree_fock_state_creation_operator *= (FermionOperator( ((int(state), 1), ))) dual_basis_hf_creation_operator = inverse_fourier_transform( hartree_fock_state_creation_operator, grid, spinless) dual_basis_hf_creation = normal_ordered( dual_basis_hf_creation_operator) # Initialize the HF state as a sparse matrix. hartree_fock_state = csr_matrix(([], ([], [])), shape=(2**n_single_particle_states, 1), dtype=complex) # Populate the elements of the HF state in the dual basis. for term in dual_basis_hf_creation.terms: index = 0 for operator in term: index += 2**operator[0] hartree_fock_state[index, 0] = dual_basis_hf_creation.terms[term] return hartree_fock_state