def test_sum_of_ordered_terms_equals_full_hamiltonian(self): grid_length = 4 dimension = 2 wigner_seitz_radius = 10.0 inverse_filling_fraction = 2 n_qubits = grid_length**dimension # Compute appropriate length scale. n_particles = n_qubits // inverse_filling_fraction # Generate the Hamiltonian. hamiltonian = dual_basis_jellium_hamiltonian(grid_length, dimension, wigner_seitz_radius, n_particles) terms = simulation_ordered_grouped_dual_basis_terms_with_info( hamiltonian)[0] terms_total = sum(terms, FermionOperator.zero()) length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, dimension) grid = Grid(dimension, grid_length, length_scale) hamiltonian = jellium_model(grid, spinless=True, plane_wave=False) hamiltonian = normal_ordered(hamiltonian) self.assertTrue(terms_total.isclose(hamiltonian))
def _fourier_transform_helper(hamiltonian, grid, spinless, phase_factor, vec_func_1, vec_func_2): hamiltonian_t = FermionOperator.zero() normalize_factor = numpy.sqrt(1.0 / float(grid.num_points())) for term in hamiltonian.terms: transformed_term = FermionOperator.identity() for ladder_op_mode, ladder_op_type in term: indices_1 = grid_indices(ladder_op_mode, grid, spinless) vec1 = vec_func_1(indices_1, grid) new_basis = FermionOperator.zero() for indices_2 in grid.all_points_indices(): vec2 = vec_func_2(indices_2, grid) spin = None if spinless else ladder_op_mode % 2 orbital = orbital_id(grid, indices_2, spin) exp_index = phase_factor * 1.0j * numpy.dot(vec1, vec2) if ladder_op_type == 1: exp_index *= -1.0 element = FermionOperator(((orbital, ladder_op_type),), numpy.exp(exp_index)) new_basis += element new_basis *= normalize_factor transformed_term *= new_basis # Coefficient. transformed_term *= hamiltonian.terms[term] hamiltonian_t += transformed_term return hamiltonian_t
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 simulation_ordered_grouped_dual_basis_terms_with_info( dual_basis_hamiltonian, input_ordering=None): """Give terms from the dual basis Hamiltonian in simulated order. Uses the simulation ordering, grouping terms into hopping (i^ j + j^ i) and number (i^j^ i j + c_i i^ i + c_j j^ j) operators. Pre-computes term information (indices each operator acts on, as well as whether each operator is a hopping operator. Args: dual_basis_hamiltonian (FermionOperator): The Hamiltonian. input_ordering (list): The initial Jordan-Wigner canonical order. Returns: A 3-tuple of terms from the plane-wave dual basis Hamiltonian in order of simulation, the indices they act on, and whether they are hopping operators (both also in the same order). """ zero = FermionOperator.zero() hamiltonian = dual_basis_hamiltonian n_qubits = count_qubits(hamiltonian) ordered_terms = [] ordered_indices = [] ordered_is_hopping_operator = [] # If no input mode ordering is specified, default to range(n_qubits). if not input_ordering: input_ordering = list(range(n_qubits)) # Half a second-order Trotter step reverses the input ordering: this tells # us how much we need to include in the ordered list of terms. final_ordering = list(reversed(input_ordering)) # Follow odd-even transposition sort. In alternating steps, swap each even # qubits with the odd qubit to its right, and in the next step swap each # the odd qubits with the even qubit to its right. Do this until the input # ordering has been reversed. odd = 0 while input_ordering != final_ordering: for i in range(odd, n_qubits - 1, 2): # Always keep the max on the left to avoid having to normal order. left = max(input_ordering[i], input_ordering[i + 1]) right = min(input_ordering[i], input_ordering[i + 1]) # Calculate the hopping operators in the Hamiltonian. left_hopping_operator = FermionOperator( ((left, 1), (right, 0)), hamiltonian.terms.get(((left, 1), (right, 0)), 0.0)) right_hopping_operator = FermionOperator( ((right, 1), (left, 0)), hamiltonian.terms.get(((right, 1), (left, 0)), 0.0)) # Calculate the two-number operator l^ r^ l r in the Hamiltonian. two_number_operator = FermionOperator( ((left, 1), (right, 1), (left, 0), (right, 0)), hamiltonian.terms.get( ((left, 1), (right, 1), (left, 0), (right, 0)), 0.0)) # Calculate the left number operator, left^ left. left_number_operator = FermionOperator( ((left, 1), (left, 0)), hamiltonian.terms.get(((left, 1), (left, 0)), 0.0)) # Calculate the right number operator, right^ right. right_number_operator = FermionOperator( ((right, 1), (right, 0)), hamiltonian.terms.get(((right, 1), (right, 0)), 0.0)) # Divide single-number terms by n_qubits-1 to avoid over-counting. # Each qubit is swapped n_qubits-1 times total. left_number_operator /= (n_qubits - 1) right_number_operator /= (n_qubits - 1) # If the overall hopping operator isn't close to zero, append it. # Include the indices it acts on and that it's a hopping operator. if not (left_hopping_operator + right_hopping_operator).isclose(zero): ordered_terms.append(left_hopping_operator + right_hopping_operator) ordered_indices.append(set((left, right))) ordered_is_hopping_operator.append(True) # If the overall number operator isn't close to zero, append it. # Include the indices it acts on and that it's a number operator. if not (two_number_operator + left_number_operator + right_number_operator).isclose(zero): ordered_terms.append(two_number_operator + left_number_operator + right_number_operator) ordered_indices.append(set((left, right))) ordered_is_hopping_operator.append(False) # Track the current Jordan-Wigner canonical ordering. input_ordering[i], input_ordering[i + 1] = (input_ordering[i + 1], input_ordering[i]) # Alternate even and odd steps of the reversal procedure. odd = 1 - odd return (ordered_terms, ordered_indices, ordered_is_hopping_operator)
def dual_basis_error_operator(terms, indices=None, is_hopping_operator=None, jellium_only=False, verbose=False): """Determine the difference between the exact generator of unitary evolution and the approximate generator given by the second-order Trotter-Suzuki expansion. Args: terms: a list of FermionOperators in the Hamiltonian in the order in which they will be simulated. indices: a set of indices the terms act on in the same order as terms. is_hopping_operator: a list of whether each term is a hopping operator. jellium_only: Whether the terms are from the jellium Hamiltonian only, rather than the full dual basis Hamiltonian (i.e. whether c_i = c for all number operators i^ i, or whether they depend on i as is possible in the general case). verbose: Whether to print percentage progress. Returns: The difference between the true and effective generators of time evolution for a single Trotter step. Notes: follows Equation 9 of Poulin et al.'s work in "The Trotter Step Size Required for Accurate Quantum Simulation of Quantum Chemistry". """ more_info = bool(indices) n_terms = len(terms) if verbose: import time start = time.time() error_operator = FermionOperator.zero() for beta in range(n_terms): if verbose and beta % (n_terms // 30) == 0: print('%4.3f percent done in' % ((float(beta) / n_terms)**3 * 100), time.time() - start) for alpha in range(beta + 1): for alpha_prime in range(beta): # If we have pre-computed info on indices, use it to determine # trivial double commutation. if more_info: if (not trivially_double_commutes_dual_basis_using_term_info( indices[alpha], indices[beta], indices[alpha_prime], is_hopping_operator[alpha], is_hopping_operator[beta], is_hopping_operator[alpha_prime], jellium_only)): # Determine the result of the double commutator. double_com = double_commutator( terms[alpha], terms[beta], terms[alpha_prime], indices[beta], indices[alpha_prime], is_hopping_operator[beta], is_hopping_operator[alpha_prime]) if alpha == beta: double_com /= 2.0 error_operator += double_com # If we don't have more info, check for trivial double # commutation using the terms directly. elif not trivially_double_commutes_dual_basis( terms[alpha], terms[beta], terms[alpha_prime]): double_com = double_commutator(terms[alpha], terms[beta], terms[alpha_prime]) if alpha == beta: double_com /= 2.0 error_operator += double_com error_operator /= 12.0 return error_operator
def test_sparse_matrix_zero_n_qubit(self): sparse_operator = get_sparse_operator(FermionOperator.zero(), 4) sparse_operator.eliminate_zeros() self.assertEqual(len(list(sparse_operator.data)), 0) self.assertEqual(sparse_operator.shape, (16, 16))
def test_double_commutator_no_intersection_with_union_of_second_two(self): com = double_commutator(FermionOperator('4^ 3^ 6 5'), FermionOperator('2^ 1 0'), FermionOperator('0^')) self.assertTrue(com.isclose(FermionOperator.zero()))