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_add_to_existing_result(self): prior_terms = FermionOperator('0^ 1') operator_a = FermionOperator('2^ 1') operator_b = FermionOperator('0^ 2') commutator_ordered_diagonal_coulomb_with_two_body_operator( operator_a, operator_b, prior_terms=prior_terms) self.assertTrue(prior_terms.isclose(FermionOperator.zero()))
def test_commutator(self): operator_a = ( FermionOperator('0^ 0', 0.3) + FermionOperator('1^ 1', 0.1j) + FermionOperator('1^ 0^ 1 0', -0.2) + FermionOperator('1^ 3') + FermionOperator('3^ 0') + FermionOperator('3^ 2', 0.017) - FermionOperator('2^ 3', 1.99) + FermionOperator('3^ 1^ 3 1', .09) + FermionOperator('2^ 0^ 2 0', .126j) + FermionOperator('4^ 2^ 4 2') + FermionOperator('3^ 0^ 3 0')) operator_b = ( FermionOperator('3^ 1', 0.7) + FermionOperator('1^ 3', -9.) + FermionOperator('1^ 0^ 3 0', 0.1) - FermionOperator('3^ 0^ 1 0', 0.11) + FermionOperator('3^ 2^ 3 2') + FermionOperator('3^ 1^ 3 1', -1.37) + FermionOperator('4^ 2^ 4 2') + FermionOperator('4^ 1^ 4 1') + FermionOperator('1^ 0^ 4 0', 16.7) + FermionOperator('1^ 0^ 4 3', 1.67) + FermionOperator('4^ 3^ 5 2', 1.789j) + FermionOperator('6^ 5^ 4 1', -11.789j)) reference = normal_ordered(commutator(operator_a, operator_b)) result = commutator_ordered_diagonal_coulomb_with_two_body_operator( operator_a, operator_b) diff = result - reference self.assertTrue(diff.isclose(FermionOperator.zero()))
def test_integration_random_diagonal_coulomb_hamiltonian(self): hamiltonian1 = normal_ordered( get_fermion_operator( random_diagonal_coulomb_hamiltonian(n_qubits=7))) hamiltonian2 = normal_ordered( get_fermion_operator( random_diagonal_coulomb_hamiltonian(n_qubits=7))) reference = normal_ordered(commutator(hamiltonian1, hamiltonian2)) result = commutator_ordered_diagonal_coulomb_with_two_body_operator( hamiltonian1, hamiltonian2) self.assertTrue(result.isclose(reference))
def test_no_warning_on_nonstandard_input_second_arg(self): with warnings.catch_warnings(record=True) as w: operator_a = FermionOperator('3^ 2^ 3 2') operator_b = FermionOperator('4^ 3^ 4 1') reference = FermionOperator('4^ 3^ 2^ 4 2 1') result = ( commutator_ordered_diagonal_coulomb_with_two_body_operator( operator_a, operator_b)) self.assertFalse(w) # Result should still be correct even though we hit the warning. self.assertTrue(result.isclose(reference))
def test_warning_on_bad_input_first_arg(self): with warnings.catch_warnings(record=True) as w: operator_a = FermionOperator('4^ 3^ 2 1') operator_b = FermionOperator('3^ 2^ 3 2') reference = normal_ordered(commutator(operator_a, operator_b)) result = ( commutator_ordered_diagonal_coulomb_with_two_body_operator( operator_a, operator_b)) self.assertTrue(len(w) == 1) self.assertIn('Defaulted to standard commutator evaluation', str(w[-1].message)) # Result should still be correct in this case. diff = result - reference self.assertTrue(diff.isclose(FermionOperator.zero()))
def benchmark_commutator_diagonal_coulomb_operators_2D_spinless_jellium( side_length): """Test speed of computing commutators using specialized functions. Args: side_length: The side length of the 2D jellium grid. There are side_length ** 2 qubits, and O(side_length ** 4) terms in the Hamiltonian. Returns: runtime_commutator: The time it takes to compute a commutator, after partitioning the terms and normal ordering, using the regular commutator function. runtime_diagonal_commutator: The time it takes to compute the same commutator using methods restricted to diagonal Coulomb operators. """ hamiltonian = normal_ordered( jellium_model(Grid(2, side_length, 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 start = time.time() _ = normal_ordered(commutator(part_a, part_b)) end = time.time() runtime_commutator = end - start start = time.time() _ = commutator_ordered_diagonal_coulomb_with_two_body_operator( part_a, part_b) end = time.time() runtime_diagonal_commutator = end - start return runtime_commutator, runtime_diagonal_commutator
def test_nonstandard_second_arg(self): operator_a = (FermionOperator('0^ 0', 0.3) + FermionOperator('1^ 1', 0.1j) + FermionOperator('2^ 0^ 2 0', -0.2) + FermionOperator('2^ 1^ 2 1', -0.2j) + FermionOperator('1^ 3') + FermionOperator('3^ 0') + FermionOperator('4^ 4', -1.4j)) operator_b = (FermionOperator('4^ 1^ 3 0', 0.1) - FermionOperator('3^ 0^ 1 0', 0.11)) reference = (FermionOperator('1^ 0^ 1 0', -0.11) + FermionOperator('3^ 0^ 1 0', 0.011j) + FermionOperator('3^ 0^ 3 0', 0.11) + FermionOperator('3^ 2^ 0^ 2 1 0', -0.022j) + FermionOperator('4^ 1^ 3 0', -0.03 - 0.13j) + FermionOperator('4^ 2^ 1^ 3 2 0', -0.02 + 0.02j)) res = commutator_ordered_diagonal_coulomb_with_two_body_operator( operator_a, operator_b) self.assertTrue(res.isclose(reference))
def fermionic_swap_trotter_error_operator_diagonal_two_body( hamiltonian, external_potential_at_end=False): """Compute the fermionic swap network Trotter error of a diagonal two-body Hamiltonian. Args: hamiltonian (FermionOperator): The diagonal Coulomb Hamiltonian to compute the Trotter error for. Returns: error_operator: The second-order Trotter error operator. Notes: Follows Eq 9 of Poulin et al., arXiv:1406.4920, applied to the Trotter step detailed in Kivlichan et al., arxiv:1711.04789. """ single_terms = numpy.array( simulation_ordered_grouped_low_depth_terms_with_info( hamiltonian, external_potential_at_end=external_potential_at_end)[0]) # Cache the halved terms for use in the second commutator. halved_single_terms = single_terms / 2.0 term_mode_mask = bit_mask_of_modes_acted_on_by_fermionic_terms( single_terms, count_qubits(hamiltonian)) error_operator = FermionOperator.zero() for beta, term_beta in enumerate(single_terms): modes_acted_on_by_term_beta = set() for beta_action in term_beta.terms: modes_acted_on_by_term_beta.update( set(operator[0] for operator in beta_action)) beta_mode_mask = numpy.logical_or.reduce( [term_mode_mask[mode] for mode in modes_acted_on_by_term_beta]) # alpha_prime indices that could have a nonzero commutator, i.e. # there's overlap between the modes the corresponding terms act on. valid_alpha_primes = numpy.where(beta_mode_mask)[0] # Only alpha_prime < beta enters the error operator; filter for this. valid_alpha_primes = valid_alpha_primes[valid_alpha_primes < beta] for alpha_prime in valid_alpha_primes: term_alpha_prime = single_terms[alpha_prime] inner_commutator_term = ( commutator_ordered_diagonal_coulomb_with_two_body_operator( term_beta, term_alpha_prime)) modes_acted_on_by_inner_commutator = set() for inner_commutator_action in inner_commutator_term.terms: modes_acted_on_by_inner_commutator.update( set(operator[0] for operator in inner_commutator_action)) # If the inner commutator has no action, the commutator is zero. if not modes_acted_on_by_inner_commutator: continue inner_commutator_mask = numpy.logical_or.reduce([ term_mode_mask[mode] for mode in modes_acted_on_by_inner_commutator ]) # alpha indices that could have a nonzero commutator. valid_alphas = numpy.where(inner_commutator_mask)[0] # Filter so alpha <= beta in the double commutator. valid_alphas = valid_alphas[valid_alphas <= beta] for alpha in valid_alphas: # If alpha = beta, only use half the term. if alpha != beta: outer_term_alpha = single_terms[alpha] else: outer_term_alpha = halved_single_terms[alpha] # Add the partial double commutator to the error operator. commutator_ordered_diagonal_coulomb_with_two_body_operator( outer_term_alpha, inner_commutator_term, prior_terms=error_operator) # Divide by 12 to match the error operator definition. error_operator /= 12.0 return error_operator
def split_operator_trotter_error_operator_diagonal_two_body(hamiltonian, order): """Compute the split-operator Trotter error of a diagonal two-body Hamiltonian. Args: hamiltonian (FermionOperator): The diagonal Coulomb Hamiltonian to compute the Trotter error for. order (str): Whether to simulate the split-operator Trotter step with the kinetic energy T first (order='T+V') or with the potential energy V first (order='V+T'). Returns: error_operator: The second-order Trotter error operator. Notes: The second-order split-operator Trotter error is calculated from the double commutator [T, [V, T]] + [V, [V, T]] / 2 when T is simulated before V (i.e. exp(-iTt/2) exp(-iVt) exp(-iTt/2)), and from the double commutator [V, [T, V]] + [T, [T, V]] / 2 when V is simulated before T, following Equation 9 of "The Trotter Step Size Required for Accurate Quantum Simulation of Quantum Chemistry" by Poulin et al. The Trotter error operator is then obtained by dividing by 12. """ n_qubits = count_qubits(hamiltonian) potential_terms, kinetic_terms = ( diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian)) # Cache halved potential and kinetic terms for the second commutator. halved_potential_terms = potential_terms / 2.0 halved_kinetic_terms = kinetic_terms / 2.0 # Assign the outer term of the second commutator based on the ordering. outer_potential_terms = (halved_potential_terms if order == 'T+V' else potential_terms) outer_kinetic_terms = (halved_kinetic_terms if order == 'V+T' else kinetic_terms) potential_mask = bit_mask_of_modes_acted_on_by_fermionic_terms( potential_terms, n_qubits) kinetic_mask = bit_mask_of_modes_acted_on_by_fermionic_terms( kinetic_terms, n_qubits) error_operator = FermionOperator.zero() for potential_term in potential_terms: modes_acted_on_by_potential_term = set() for potential_term_action in potential_term.terms: modes_acted_on_by_potential_term.update( set(operator[0] for operator in potential_term_action)) if not modes_acted_on_by_potential_term: continue potential_term_mode_mask = numpy.logical_or.reduce( [kinetic_mask[mode] for mode in modes_acted_on_by_potential_term]) for kinetic_term in kinetic_terms[potential_term_mode_mask]: inner_commutator_term = ( commutator_ordered_diagonal_coulomb_with_two_body_operator( potential_term, kinetic_term)) modes_acted_on_by_inner_commutator = set() for inner_commutator_action in inner_commutator_term.terms: modes_acted_on_by_inner_commutator.update( set(operator[0] for operator in inner_commutator_action)) if not modes_acted_on_by_inner_commutator: continue inner_commutator_mode_mask = numpy.logical_or.reduce([ potential_mask[mode] for mode in modes_acted_on_by_inner_commutator ]) # halved_potential_terms for T+V order, potential_terms for V+T for outer_potential_term in outer_potential_terms[ inner_commutator_mode_mask]: commutator_ordered_diagonal_coulomb_with_two_body_operator( outer_potential_term, inner_commutator_term, prior_terms=error_operator) inner_commutator_mode_mask = numpy.logical_or.reduce([ kinetic_mask[qubit] for qubit in modes_acted_on_by_inner_commutator ]) # kinetic_terms for T+V order, halved_kinetic_terms for V+T for outer_kinetic_term in outer_kinetic_terms[ inner_commutator_mode_mask]: commutator_ordered_diagonal_coulomb_with_two_body_operator( outer_kinetic_term, inner_commutator_term, prior_terms=error_operator) # Divide by 12 to match the error operator definition. # If order='V+T', also flip the sign to account for inner_commutator_term # not flipping between the different orderings. if order == 'T+V': error_operator /= 12.0 else: error_operator /= -12.0 return error_operator