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 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 test_commutes_number_operators(self): com = commutator(FermionOperator('4^ 3^ 4 3'), FermionOperator('2^ 2')) com = normal_ordered(com) self.assertEqual(com, FermionOperator.zero()) com = commutator(BosonOperator('4^ 3^ 4 3'), BosonOperator('2^ 2')) com = normal_ordered(com) self.assertTrue(com == BosonOperator.zero())
def test_commutator(self): operator_a = FermionOperator('') self.assertEqual(FermionOperator.zero(), commutator(operator_a, self.fermion_operator)) operator_b = QubitOperator('X1 Y2') self.assertEqual(commutator(self.qubit_operator, operator_b), (self.qubit_operator * operator_b - operator_b * self.qubit_operator))
def test_sum_of_ordered_terms_equals_full_hamiltonian_odd_side_len(self): hamiltonian = normal_ordered( fermi_hubbard(5, 5, 1.0, -0.3, periodic=False)) hamiltonian.compress() terms = simulation_ordered_grouped_hubbard_terms_with_info( hamiltonian)[0] terms_total = sum(terms, FermionOperator.zero()) self.assertTrue(terms_total == hamiltonian)
def test_commutes_identity(self): com = commutator(FermionOperator.identity(), FermionOperator('2^ 3', 2.3)) self.assertEqual(com, FermionOperator.zero()) com = commutator(BosonOperator.identity(), BosonOperator('2^ 3', 2.3)) self.assertTrue(com == BosonOperator.zero()) com = commutator(QuadOperator.identity(), QuadOperator('q2 p3', 2.3)) self.assertTrue(com == QuadOperator.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_commutes_no_intersection(self): com = commutator(FermionOperator('2^ 3'), FermionOperator('4^ 5^ 3')) com = normal_ordered(com) self.assertEqual(com, FermionOperator.zero()) com = commutator(BosonOperator('2^ 3'), BosonOperator('4^ 5^ 3')) com = normal_ordered(com) self.assertTrue(com == BosonOperator.zero()) com = commutator(QuadOperator('q2 p3'), QuadOperator('q4 q5 p3')) com = normal_ordered(com) self.assertTrue(com == QuadOperator.zero())
def test_sum_of_ordered_terms_equals_full_side_length_2_hopping_only(self): hamiltonian = normal_ordered( fermi_hubbard(2, 2, 1., 0.0, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're # hopping operators. result = simulation_ordered_grouped_hubbard_terms_with_info( hamiltonian) terms, _, _ = result terms_total = sum(terms, FermionOperator.zero()) self.assertTrue(terms_total == hamiltonian)
def test_canonical_anticommutation_relations(self): op_1 = FermionOperator('3') op_1_dag = FermionOperator('3^') op_2 = FermionOperator('4') op_2_dag = FermionOperator('4^') zero = FermionOperator.zero() one = FermionOperator.identity() self.assertEqual(one, normal_ordered(anticommutator(op_1, op_1_dag))) self.assertEqual(zero, normal_ordered(anticommutator(op_1, op_2))) self.assertEqual(zero, normal_ordered(anticommutator(op_1, op_2_dag))) self.assertEqual(zero, normal_ordered(anticommutator(op_1_dag, op_2))) self.assertEqual(zero, normal_ordered(anticommutator(op_1_dag, op_2_dag))) self.assertEqual(one, normal_ordered(anticommutator(op_2, op_2_dag)))
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 test_sum_of_ordered_terms_equals_full_hamiltonian(self): grid_length = 4 dimension = 1 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 length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, dimension) hamiltonian = dual_basis_jellium_hamiltonian(grid_length, dimension) terms = ordered_low_depth_terms_no_info(hamiltonian) terms_total = sum(terms, FermionOperator.zero()) grid = Grid(dimension, grid_length, length_scale) hamiltonian = jellium_model(grid, spinless=True, plane_wave=False) hamiltonian = normal_ordered(hamiltonian) self.assertTrue(terms_total == hamiltonian)
def swap_adjacent_fermionic_modes(fermion_operator, mode): """Swap adjacent modes in a fermionic operator. Returns: a new FermionOperator with mode and mode+1 swapped. Args: fermion_operator (projectq.FermionOperator): Original operator. mode (integer): The mode to be swapped with mode + 1. Notes: Because the swap must be fermionic, the sign of the operator is flipped if both creation operators mode^ and (mode+1)^ (or the corresponding annihilation operators) are present. """ new_operator = FermionOperator.zero() for term in fermion_operator.terms: new_term = list(term) multiplier = 1 if (mode, 1) in term and (mode + 1, 1) in term: multiplier *= -1 else: if (mode, 1) in term: new_term[term.index((mode, 1))] = (mode + 1, 1) if (mode + 1, 1) in term: new_term[term.index((mode + 1, 1))] = (mode, 1) if (mode, 0) in term and (mode + 1, 0) in term: multiplier *= -1 else: if (mode, 0) in term: new_term[term.index((mode, 0))] = (mode + 1, 0) if (mode + 1, 0) in term: new_term[term.index((mode + 1, 0))] = (mode, 0) new_operator.terms[tuple(new_term)] = (multiplier * fermion_operator.terms[term]) return new_operator
def test_commutes_no_intersection(self): com = commutator(FermionOperator('2^ 3'), FermionOperator('4^ 5^ 3')) com = normal_ordered(com) self.assertTrue(com.isclose(FermionOperator.zero()))
def test_commutator_hopping_with_double_number_two_intersections(self): com = commutator(FermionOperator('2^ 3'), FermionOperator('3^ 2^ 3 2')) com = normal_ordered(com) self.assertEqual(com, FermionOperator.zero())
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 test_commutes_identity(self): com = commutator(FermionOperator.identity(), FermionOperator('2^ 3', 2.3)) self.assertEqual(com, FermionOperator.zero())
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.assertEqual(com, FermionOperator.zero())
def fermi_hubbard(x_dimension, y_dimension, tunneling, coulomb, chemical_potential=0., magnetic_field=0., periodic=True, spinless=False, particle_hole_symmetry=False): """Return symbolic representation of a Fermi-Hubbard Hamiltonian. The idea of this model is that some fermions move around on a grid and the energy of the model depends on where the fermions are. The Hamiltonians of this model live on a grid of dimensions `x_dimension` x `y_dimension`. The grid can have periodic boundary conditions or not. In the standard Fermi-Hubbard model (which we call the "spinful" model), there is room for an "up" fermion and a "down" fermion at each site on the grid. In this model, there are a total of `2N` spin-orbitals, where `N = x_dimension * y_dimension` is the number of sites. In the spinless model, there is only one spin-orbital per site for a total of `N`. The Hamiltonian for the spinful model has the form .. math:: \\begin{align} H = &- t \sum_{\langle i,j \\rangle} \sum_{\sigma} (a^\dagger_{i, \sigma} a_{j, \sigma} + a^\dagger_{j, \sigma} a_{i, \sigma}) + U \sum_{i} a^\dagger_{i, \\uparrow} a_{i, \\uparrow} a^\dagger_{j, \downarrow} a_{j, \downarrow} \\\\ &- \mu \sum_i \sum_{\sigma} a^\dagger_{i, \sigma} a_{i, \sigma} - h \sum_i (a^\dagger_{i, \\uparrow} a_{i, \\uparrow} - a^\dagger_{i, \downarrow} a_{i, \downarrow}) \\end{align} where - The indices :math:`\langle i, j \\rangle` run over pairs :math:`i` and :math:`j` of sites that are connected to each other in the grid - :math:`\sigma \in \\{\\uparrow, \downarrow\\}` is the spin - :math:`t` is the tunneling amplitude - :math:`U` is the Coulomb potential - :math:`\mu` is the chemical potential - :math:`h` is the magnetic field One can also construct the Hamiltonian for the spinless model, which has the form .. math:: H = - t \sum_{k=1}^{N-1} (a_k^\dagger a_{k + 1} + a_{k+1}^\dagger a_k) + U \sum_{k=1}^{N-1} a_k^\dagger a_k a_{k+1}^\dagger a_{k+1} - \mu \sum_{k=1}^N a_k^\dagger a_k. Args: x_dimension (int): The width of the grid. y_dimension (int): The height of the grid. tunneling (float): The tunneling amplitude :math:`t`. coulomb (float): The attractive local interaction strength :math:`U`. chemical_potential (float, optional): The chemical potential :math:`\mu` at each site. Default value is 0. magnetic_field (float, optional): The magnetic field :math:`h` at each site. Default value is 0. Ignored for the spinless case. periodic (bool, optional): If True, add periodic boundary conditions. Default is True. spinless (bool, optional): If True, return a spinless Fermi-Hubbard model. Default is False. particle_hole_symmetry (bool, optional): If False, the repulsion term corresponds to: .. math:: U \sum_{k=1}^{N-1} a_k^\dagger a_k a_{k+1}^\dagger a_{k+1} If True, the repulsion term is replaced by: .. math:: U \sum_{k=1}^{N-1} (a_k^\dagger a_k - \\frac12) (a_{k+1}^\dagger a_{k+1} - \\frac12) which is unchanged under a particle-hole transformation. Default is False Returns: hubbard_model: An instance of the FermionOperator class. """ tunneling = float(tunneling) coulomb = float(coulomb) chemical_potential = float(chemical_potential) magnetic_field = float(magnetic_field) # Initialize fermion operator class. n_sites = x_dimension * y_dimension n_spin_orbitals = n_sites if not spinless: n_spin_orbitals *= 2 hubbard_model = FermionOperator.zero() # Select particle-hole symmetry if particle_hole_symmetry: coulomb_shift = FermionOperator((), 0.5) else: coulomb_shift = FermionOperator.zero() # Loop through sites and add terms. for site in range(n_sites): # Add chemical potential to the spinless case. The magnetic field # doesn't contribute. if spinless and chemical_potential: hubbard_model += number_operator(n_spin_orbitals, site, -chemical_potential) # With spin, add the chemical potential and magnetic field terms. elif not spinless: hubbard_model += number_operator( n_spin_orbitals, up_index(site), -chemical_potential - magnetic_field) hubbard_model += number_operator( n_spin_orbitals, down_index(site), -chemical_potential + magnetic_field) # Add local pair interaction terms. operator_1 = number_operator(n_spin_orbitals, up_index(site)) - coulomb_shift operator_2 = number_operator(n_spin_orbitals, down_index(site)) - coulomb_shift hubbard_model += coulomb * operator_1 * operator_2 # Index coupled orbitals. right_neighbor = site + 1 bottom_neighbor = site + x_dimension # Account for periodic boundaries. if periodic: if (x_dimension > 2) and ((site + 1) % x_dimension == 0): right_neighbor -= x_dimension if (y_dimension > 2) and (site + x_dimension + 1 > n_sites): bottom_neighbor -= x_dimension * y_dimension # Add transition to neighbor on right. if (right_neighbor) % x_dimension or (periodic and x_dimension > 2): if spinless: # Add Coulomb term. operator_1 = number_operator(n_spin_orbitals, site, 1.0) - coulomb_shift operator_2 = number_operator(n_spin_orbitals, right_neighbor, 1.0) - coulomb_shift hubbard_model += coulomb * operator_1 * operator_2 # Add hopping term. operators = ((site, 1), (right_neighbor, 0)) else: # Add hopping term. operators = ((up_index(site), 1), (up_index(right_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) hubbard_model += hopping_term hubbard_model += hermitian_conjugated(hopping_term) operators = ((down_index(site), 1), (down_index(right_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) hubbard_model += hopping_term hubbard_model += hermitian_conjugated(hopping_term) # Add transition to neighbor below. if site + x_dimension + 1 <= n_sites or (periodic and y_dimension > 2): if spinless: # Add Coulomb term. operator_1 = number_operator(n_spin_orbitals, site) - coulomb_shift operator_2 = number_operator(n_spin_orbitals, bottom_neighbor) - coulomb_shift hubbard_model += coulomb * operator_1 * operator_2 # Add hopping term. operators = ((site, 1), (bottom_neighbor, 0)) else: # Add hopping term. operators = ((up_index(site), 1), (up_index(bottom_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) hubbard_model += hopping_term hubbard_model += hermitian_conjugated(hopping_term) operators = ((down_index(site), 1), (down_index(bottom_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) hubbard_model += hopping_term hubbard_model += hermitian_conjugated(hopping_term) return hubbard_model
def stagger_with_info(hamiltonian, input_ordering, parity): """Give terms simulated in a single stagger of a Trotter step. Groups 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: hamiltonian (FermionOperator): The Hamiltonian. input_ordering (list): The initial Jordan-Wigner canonical order. parity (boolean): Whether to determine the terms from the next even (False = 0) or odd (True = 1) stagger. Returns: A 3-tuple of terms from the Hamiltonian that are simulated in the stagger, the indices they act on, and whether they are hopping operators (all in the same order). Notes: The "staggers" used here are the left (parity=False) and right (parity=True) staggers detailed in Kivlichan et al., "Quantum Simulation of Electronic Structure with Linear Depth and Connectivity", arxiv:1711.04789. As such, the Hamiltonian must be in the form discussed in that paper. This constrains it to have only hopping terms (i^ j + j^ i) and potential terms which are products of at most two number operators (n_i or n_i n_j). """ terms_in_step = [] indices_in_step = [] is_hopping_operator_in_step = [] zero = FermionOperator.zero() n_qubits = count_qubits(hamiltonian) # A single round of odd-even transposition sort. for i in range(parity, 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) == zero: terms_in_step.append(left_hopping_operator + right_hopping_operator) indices_in_step.append(set((left, right))) is_hopping_operator_in_step.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) == zero: terms_in_step.append(two_number_operator + left_number_operator + right_number_operator) indices_in_step.append(set((left, right))) is_hopping_operator_in_step.append(False) # Modify the current Jordan-Wigner canonical ordering in-place. input_ordering[i], input_ordering[i + 1] = (input_ordering[i + 1], input_ordering[i]) return terms_in_step, indices_in_step, is_hopping_operator_in_step
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 low_depth_second_order_trotter_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", applied to the "stagger"-based Trotter step for detailed in Kivlichan et al., "Quantum Simulation of Electronic Structure with Linear Depth and Connectivity", arxiv:1711.04789. """ 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_commutes_number_operators(self): com = commutator(FermionOperator('4^ 3^ 4 3'), FermionOperator('2^ 2')) com = normal_ordered(com) self.assertTrue(com.isclose(FermionOperator.zero()))
def test_commutes_identity(self): com = commutator(FermionOperator.identity(), FermionOperator('2^ 3', 2.3)) self.assertTrue(com.isclose(FermionOperator.zero()))