Beispiel #1
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
    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))
Beispiel #3
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 #4
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))
    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 #6
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())
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
Beispiel #8
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())
    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 #10
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 #11
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))
Beispiel #12
0
    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)
Beispiel #13
0
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
Beispiel #14
0
 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()))
Beispiel #15
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 #16
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)
Beispiel #17
0
 def test_commutes_identity(self):
     com = commutator(FermionOperator.identity(),
                      FermionOperator('2^ 3', 2.3))
     self.assertEqual(com, FermionOperator.zero())
Beispiel #18
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())
Beispiel #19
0
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
Beispiel #21
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))
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
Beispiel #23
0
 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()))
Beispiel #24
0
 def test_commutes_identity(self):
     com = commutator(FermionOperator.identity(),
                      FermionOperator('2^ 3', 2.3))
     self.assertTrue(com.isclose(FermionOperator.zero()))