Beispiel #1
0
    def test_jellium_hamiltonian_correctly_broken_up(self):
        grid = Grid(2, 3, 1.)

        hamiltonian = jellium_model(grid, spinless=True, plane_wave=False)

        potential_terms, kinetic_terms = (
            diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian)
        )

        potential = sum(potential_terms, FermionOperator.zero())
        kinetic = sum(kinetic_terms, FermionOperator.zero())

        true_potential = dual_basis_jellium_model(grid,
                                                  spinless=True,
                                                  kinetic=False)
        true_kinetic = dual_basis_jellium_model(grid,
                                                spinless=True,
                                                potential=False)
        for i in range(count_qubits(true_kinetic)):
            coeff = true_kinetic.terms.get(((i, 1), (i, 0)))
            if coeff:
                true_kinetic -= FermionOperator(((i, 1), (i, 0)), coeff)
                true_potential += FermionOperator(((i, 1), (i, 0)), coeff)

        self.assertEqual(potential, true_potential)
        self.assertEqual(kinetic, true_kinetic)
Beispiel #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.grid_indices(ladder_op_mode, spinless)
            vec1 = vec_func_1(indices_1)
            new_basis = FermionOperator.zero()
            for indices_2 in grid.all_points_indices():
                vec2 = vec_func_2(indices_2)
                spin = None if spinless else ladder_op_mode % 2
                orbital = grid.orbital_id(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
Beispiel #3
0
    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()))
Beispiel #4
0
    def test_zero_hamiltonian(self):
        potential_terms, kinetic_terms = (
            diagonal_coulomb_potential_and_kinetic_terms_as_arrays(
                FermionOperator.zero()))

        self.assertListEqual(list(potential_terms), [])
        self.assertListEqual(list(kinetic_terms), [])
Beispiel #5
0
    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()))
Beispiel #6
0
    def test_simple_hamiltonian(self):
        hamiltonian = (FermionOperator('3^ 1^ 3 1') + FermionOperator('1^ 1') -
                       FermionOperator('1^ 2') - FermionOperator('2^ 1'))

        potential_terms, kinetic_terms = (
            diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian)
        )

        potential = sum(potential_terms, FermionOperator.zero())
        kinetic = sum(kinetic_terms, FermionOperator.zero())

        self.assertEqual(
            potential,
            (FermionOperator('1^ 1') + FermionOperator('3^ 1^ 3 1')))
        self.assertEqual(kinetic,
                         (-FermionOperator('1^ 2') - FermionOperator('2^ 1')))
Beispiel #7
0
    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())
Beispiel #8
0
 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))
Beispiel #9
0
    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()))
Beispiel #10
0
    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())
Beispiel #11
0
    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)
Beispiel #12
0
def diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian):
    """Give the potential and kinetic terms of a diagonal Coulomb Hamiltonian
    as arrays.

    Args:
        hamiltonian (FermionOperator): The diagonal Coulomb Hamiltonian to
                                       separate the potential and kinetic terms
                                       for. Identity is arbitrarily chosen
                                       to be part of the potential.

    Returns:
        Tuple of (potential_terms, kinetic_terms). Both elements of the tuple
        are numpy arrays of FermionOperators.
    """
    if not isinstance(hamiltonian, FermionOperator):
        try:
            hamiltonian = normal_ordered(get_fermion_operator(hamiltonian))
        except TypeError:
            raise TypeError('hamiltonian must be either a FermionOperator '
                            'or DiagonalCoulombHamiltonian.')

    potential = FermionOperator.zero()
    kinetic = FermionOperator.zero()

    for term, coeff in hamiltonian.terms.items():
        acted = set(term[i][0] for i in range(len(term)))
        if len(acted) == len(term) / 2:
            potential += FermionOperator(term, coeff)
        else:
            kinetic += FermionOperator(term, coeff)

    potential_terms = numpy.array([
        FermionOperator(term, coeff)
        for term, coeff in potential.terms.items()
    ])

    kinetic_terms = numpy.array([
        FermionOperator(term, coeff) for term, coeff in kinetic.terms.items()
    ])

    return (potential_terms, kinetic_terms)
Beispiel #13
0
    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())
Beispiel #14
0
    def test_split_operator_error_operator_VT_order_against_definition(self):
        hamiltonian = (normal_ordered(fermi_hubbard(3, 3, 1., 4.0)) -
                       2.3 * FermionOperator.identity())
        potential_terms, kinetic_terms = (
            diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian)
        )
        potential = sum(potential_terms, FermionOperator.zero())
        kinetic = sum(kinetic_terms, FermionOperator.zero())

        error_operator = (
            split_operator_trotter_error_operator_diagonal_two_body(
                hamiltonian, order='V+T'))

        # V-then-T ordered double commutators: [V, [T, V]] + [T, [T, V]] / 2
        inner_commutator = normal_ordered(commutator(kinetic, potential))
        error_operator_definition = normal_ordered(
            commutator(potential, inner_commutator))
        error_operator_definition += normal_ordered(
            commutator(kinetic, inner_commutator)) / 2.0
        error_operator_definition /= 12.0

        self.assertEqual(error_operator, error_operator_definition)
Beispiel #15
0
    def test_diagonal_coulomb_hamiltonian_class(self):
        hamiltonian = DiagonalCoulombHamiltonian(numpy.array([[1, 1], [1, 1]],
                                                             dtype=float),
                                                 numpy.array([[0, 1], [1, 0]],
                                                             dtype=float),
                                                 constant=2.3)

        potential_terms, kinetic_terms = (
            diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian)
        )

        potential = sum(potential_terms, FermionOperator.zero())
        kinetic = sum(kinetic_terms, FermionOperator.zero())

        expected_potential = (2.3 * FermionOperator.identity() +
                              FermionOperator('0^ 0') +
                              FermionOperator('1^ 1') -
                              FermionOperator('1^ 0^ 1 0', 2.0))
        expected_kinetic = FermionOperator('0^ 1') + FermionOperator('1^ 0')

        self.assertEqual(potential, expected_potential)
        self.assertEqual(kinetic, expected_kinetic)
Beispiel #16
0
    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)
Beispiel #17
0
    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)))
Beispiel #18
0
    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()))
Beispiel #19
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]].

    Args:
        op1, op2, op3 (FermionOperators or BosonOperators): operators for
            the commutator. All three operators must be of the same type.
        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))
Beispiel #20
0
    def test_hubbard_trotter_error_matches_low_depth_trotter_error(self):
        hamiltonian = normal_ordered(fermi_hubbard(3, 3, 1., 2.3))

        error_operator = (
            fermionic_swap_trotter_error_operator_diagonal_two_body(
                hamiltonian))
        error_operator.compress()

        # Unpack result into terms, indices they act on, and whether
        # they're hopping operators.
        result = simulation_ordered_grouped_low_depth_terms_with_info(
            hamiltonian)
        terms, indices, is_hopping = result

        old_error_operator = low_depth_second_order_trotter_error_operator(
            terms, indices, is_hopping, jellium_only=True)

        old_error_operator -= error_operator
        self.assertEqual(old_error_operator, FermionOperator.zero())
Beispiel #21
0
    def test_1D_jellium_trotter_error_matches_low_depth_trotter_error(self):
        hamiltonian = normal_ordered(
            jellium_model(
                hypercube_grid_with_given_wigner_seitz_radius_and_filling(
                    1, 5, wigner_seitz_radius=10., spinless=True),
                spinless=True,
                plane_wave=False))

        error_operator = (
            fermionic_swap_trotter_error_operator_diagonal_two_body(
                hamiltonian))
        error_operator.compress()

        # Unpack result into terms, indices they act on, and whether
        # they're hopping operators.
        result = simulation_ordered_grouped_low_depth_terms_with_info(
            hamiltonian)
        terms, indices, is_hopping = result

        old_error_operator = low_depth_second_order_trotter_error_operator(
            terms, indices, is_hopping, jellium_only=True)

        old_error_operator -= error_operator
        self.assertEqual(old_error_operator, FermionOperator.zero())
Beispiel #22
0
    def test_identity_masks_no_modes(self):
        mask = bit_mask_of_modes_acted_on_by_fermionic_terms(
            [FermionOperator.zero()], n_qubits=3)

        self.assertTrue(numpy.array_equal(mask, numpy.zeros((3, 1))))
def commutator_ordered_diagonal_coulomb_with_two_body_operator(
        operator_a, operator_b, prior_terms=None):
    """Compute the commutator of two-body operators provided that both are
    normal-ordered and that the first only has diagonal Coulomb interactions.

    Args:
        operator_a: The first FermionOperator argument of the commutator.
            All terms must be normal-ordered, and furthermore either hopping
            operators (i^ j) or diagonal Coulomb operators (i^ i or i^ j^ i j).
        operator_b: The second FermionOperator argument of the commutator.
            operator_b can be any arbitrary two-body operator.
        prior_terms (optional): The initial FermionOperator to add to.

    Returns:
        The commutator, or the commutator added to prior_terms if provided.

    Notes:
        The function could be readily extended to the case of arbitrary
        two-body operator_a given that operator_b has the desired form;
        however, the extra check slows it down without desirable added utility.
    """
    if prior_terms is None:
        prior_terms = FermionOperator.zero()

    for term_a in operator_a.terms:
        coeff_a = operator_a.terms[term_a]
        for term_b in operator_b.terms:
            coeff_b = operator_b.terms[term_b]

            coefficient = coeff_a * coeff_b

            # If term_a == term_b the terms commute, nothing to add.
            if term_a == term_b or not term_a or not term_b:
                continue

            # Case 1: both operators are two-body, operator_a is i^ j^ i j.
            if (len(term_a) == len(term_b) == 4
                    and term_a[0][0] == term_a[2][0]
                    and term_a[1][0] == term_a[3][0]):
                _commutator_two_body_diagonal_with_two_body(
                    term_a, term_b, coefficient, prior_terms)

            # Case 2: commutator of a 1-body and a 2-body operator
            elif (len(term_b) == 4
                  and len(term_a) == 2) or (len(term_a) == 4
                                            and len(term_b) == 2):
                _commutator_one_body_with_two_body(term_a, term_b, coefficient,
                                                   prior_terms)

            # Case 3: both terms are one-body operators (both length 2)
            elif len(term_a) == 2 and len(term_b) == 2:
                _commutator_one_body_with_one_body(term_a, term_b, coefficient,
                                                   prior_terms)

            # Final case (case 4): violation of the input promise. Still
            # compute the commutator, but warn the user.
            else:
                warnings.warn('Defaulted to standard commutator evaluation '
                              'due to an out-of-spec operator.')
                additional = FermionOperator.zero()
                additional.terms[term_a + term_b] = coefficient
                additional.terms[term_b + term_a] = -coefficient
                additional = term_reordering.normal_ordered(additional)

                prior_terms += additional

    return prior_terms
Beispiel #24
0
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(  # pylint: disable=C
                            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
Beispiel #25
0
 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())
Beispiel #26
0
def stagger_with_info(hamiltonian,
                      input_ordering,
                      parity,
                      external_potential_at_end=False):
    """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.
        external_potential_at_end (bool): Whether to include the rotations from
            the external potential at the end of the Trotter step, or
            intersperse them throughout it.

    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_layer = []
    indices_in_layer = []
    is_hopping_operator_in_layer = []

    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))

        if not external_potential_at_end:
            # 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-accounting
            # for the interspersed rotations. Each qubit is swapped n_qubits-1
            # times total.
            left_number_operator /= (n_qubits - 1)
            right_number_operator /= (n_qubits - 1)

        else:
            left_number_operator = FermionOperator.zero()
            right_number_operator = FermionOperator.zero()

        # 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) == FermionOperator.zero():
            terms_in_layer.append(left_hopping_operator +
                                  right_hopping_operator)
            indices_in_layer.append(set((left, right)))
            is_hopping_operator_in_layer.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) == FermionOperator.zero():
            terms_in_layer.append(two_number_operator + left_number_operator +
                                  right_number_operator)
            terms_in_layer[-1].compress()

            indices_in_layer.append(set((left, right)))
            is_hopping_operator_in_layer.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_layer, indices_in_layer, is_hopping_operator_in_layer
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
Beispiel #29
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.assertEqual(com, FermionOperator.zero())