예제 #1
0
    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))
예제 #2
0
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
예제 #3
0
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))
예제 #4
0
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)
예제 #5
0
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
예제 #6
0
 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))
예제 #7
0
 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()))