Ejemplo n.º 1
0
def reverse_jordan_wigner(qubit_operator, n_qubits=None):
    """Transforms a QubitOperator into a FermionOperator using the
    Jordan-Wigner transform.

    Operators are mapped as follows:
    Z_j -> I - 2 a^\dagger_j a_j
    X_j -> (a^\dagger_j + a_j) Z_{j-1} Z_{j-2} .. Z_0
    Y_j -> i (a^\dagger_j - a_j) Z_{j-1} Z_{j-2} .. Z_0

    Args:
        qubit_operator: the QubitOperator to be transformed.
        n_qubits: the number of qubits term acts on. If not set, defaults
                to the maximum qubit number acted on by term.

    Returns:
        transformed_term: An instance of the FermionOperator class.

    Raises:
        TypeError: Input must be a QubitOperator.
        TypeError: Invalid number of qubits specified.
        TypeError: Pauli operators must be X, Y or Z.
    """
    if not isinstance(qubit_operator, QubitOperator):
        raise TypeError('Input must be a QubitOperator.')
    if n_qubits is None:
        n_qubits = count_qubits(qubit_operator)
    if n_qubits < count_qubits(qubit_operator):
        raise ValueError('Invalid number of qubits specified.')

    # Loop through terms.
    transformed_operator = FermionOperator()
    for term in qubit_operator.terms:
        transformed_term = FermionOperator(())
        if term:
            working_term = QubitOperator(term)
            pauli_operator = term[-1]
            while pauli_operator is not None:

                # Handle Pauli Z.
                if pauli_operator[1] == 'Z':
                    transformed_pauli = FermionOperator(
                        ()) + number_operator(n_qubits, pauli_operator[0], -2.)

                # Handle Pauli X and Y.
                else:
                    raising_term = FermionOperator(((pauli_operator[0], 1), ))
                    lowering_term = FermionOperator(((pauli_operator[0], 0), ))
                    if pauli_operator[1] == 'Y':
                        raising_term *= 1.j
                        lowering_term *= -1.j

                    transformed_pauli = raising_term + lowering_term

                    # Account for the phase terms.
                    for j in reversed(range(pauli_operator[0])):
                        z_term = QubitOperator(((j, 'Z'), ))
                        working_term = z_term * working_term
                    term_key = list(working_term.terms)[0]
                    transformed_pauli *= working_term.terms[term_key]
                    working_term.terms[list(working_term.terms)[0]] = 1.

                # Get next non-identity operator acting below 'working_qubit'.
                assert len(working_term.terms) == 1
                working_qubit = pauli_operator[0] - 1
                for working_operator in reversed(list(working_term.terms)[0]):
                    if working_operator[0] <= working_qubit:
                        pauli_operator = working_operator
                        break
                    else:
                        pauli_operator = None

                # Multiply term by transformed operator.
                transformed_term *= transformed_pauli

        # Account for overall coefficient
        transformed_term *= qubit_operator.terms[term]
        transformed_operator += transformed_term
    return transformed_operator
Ejemplo n.º 2
0
def bravyi_kitaev_fast_interaction_op(iop):
    """
    Transform from InteractionOpeator to QubitOperator for Bravyi-Kitaev fast
    algorithm.

    The electronic Hamiltonian is represented in terms of creation and
    annihilation operators. These creation and annihilation operators could be
    used to define Majorana modes as follows:
        c_{2i} = a_i + a^{\dagger}_i,
        c_{2i+1} = (a_i - a^{\dagger}_{i})/(1j)
    These Majorana modes can be used to define edge operators B_i and A_{ij}:
        B_i=c_{2i}c_{2i+1},
        A_{ij}=c_{2i}c_{2j}
    using these edge operators the fermionic algebra can be generated and
    hence all the terms in the electronic Hamiltonian can be expressed in
    terms of edge operators. The terms in electronic Hamiltonian can be
    divided into five types (arXiv 1208.5986). We can find the edge operator
    expression for each of those five types. For example, the excitation
    operator term in Hamiltonian when represented in terms of edge operators
    becomes:
        a_i^{\dagger}a_j+a_j^{\dagger}a_i = (-1j/2)*(A_ij*B_i+B_j*A_ij)
    For the sake of brevity the reader is encouraged to look up the
    expressions of other terms from the code below. The variables for edge
    operators are chosen according to the nomenclature defined above
    (B_i and A_ij).

    Args:
        iop (Interaction Operator):
        n_qubit (int): Number of qubits

    Returns:
        qubit_operator: An instance of the QubitOperator class.
    """
    n_qubits = count_qubits(iop)

    # Initialize qubit operator as constant.
    qubit_operator = QubitOperator((), iop.constant)
    edge_matrix = bravyi_kitaev_fast_edge_matrix(iop)
    edge_matrix_indices = numpy.array(numpy.nonzero(numpy.triu(edge_matrix) -
                                      numpy.diag(numpy.diag(edge_matrix))))
    # Loop through all indices.
    for p in range(n_qubits):
        for q in range(n_qubits):

            # Handle one-body terms.
            coefficient = complex(iop[p, q])
            if coefficient and p >= q:
                qubit_operator += (coefficient *
                                   one_body(edge_matrix_indices, p, q))

            # Keep looping for the two-body terms.
            for r in range(n_qubits):
                for s in range(n_qubits):
                    coefficient = complex(iop[p, q, r, s])

                    # Skip zero terms.
                    if (not coefficient) or (p == q) or (r == s):
                        continue

                    # Identify and skip one of the complex conjugates.
                    if [p, q, r, s] != [s, r, q, p]:
                        if len(set([p, q, r, s])) == 4:
                            if min(r, s) < min(p, q):
                                continue
                        elif p != r and q < p:
                                continue

                    # Handle the two-body terms.
                    transformed_term = two_body(edge_matrix_indices,
                                                p, q, r, s)
                    transformed_term *= coefficient
                    qubit_operator += transformed_term
    return qubit_operator
Ejemplo n.º 3
0
def get_interaction_operator(fermion_operator, n_qubits=None):
    """Convert a 2-body fermionic operator to InteractionOperator.

    This function should only be called on fermionic operators which
    consist of only a_p^\dagger a_q and a_p^\dagger a_q^\dagger a_r a_s
    terms. The one-body terms are stored in a matrix, one_body[p, q], and
    the two-body terms are stored in a tensor, two_body[p, q, r, s].

    Returns:
       interaction_operator: An instance of the InteractionOperator class.

    Raises:
        TypeError: Input must be a FermionOperator.
        TypeError: FermionOperator does not map to InteractionOperator.

    Warning:
        Even assuming that each creation or annihilation operator appears
        at most a constant number of times in the original operator, the
        runtime of this method is exponential in the number of qubits.
    """
    if not isinstance(fermion_operator, FermionOperator):
        raise TypeError('Input must be a FermionOperator.')

    if n_qubits is None:
        n_qubits = count_qubits(fermion_operator)
    if n_qubits < count_qubits(fermion_operator):
        raise ValueError('Invalid number of qubits specified.')

    # Normal order the terms and initialize.
    fermion_operator = normal_ordered(fermion_operator)
    constant = 0.
    one_body = numpy.zeros((n_qubits, n_qubits), complex)
    two_body = numpy.zeros((n_qubits, n_qubits, n_qubits, n_qubits), complex)

    # Loop through terms and assign to matrix.
    for term in fermion_operator.terms:
        coefficient = fermion_operator.terms[term]
        # Ignore this term if the coefficient is zero
        if abs(coefficient) < EQ_TOLERANCE:
            continue

        # Handle constant shift.
        if len(term) == 0:
            constant = coefficient

        elif len(term) == 2:
            # Handle one-body terms.
            if [operator[1] for operator in term] == [1, 0]:
                p, q = [operator[0] for operator in term]
                one_body[p, q] = coefficient
            else:
                raise InteractionOperatorError('FermionOperator does not map '
                                               'to InteractionOperator.')

        elif len(term) == 4:
            # Handle two-body terms.
            if [operator[1] for operator in term] == [1, 1, 0, 0]:
                p, q, r, s = [operator[0] for operator in term]
                two_body[p, q, r, s] = coefficient
            else:
                raise InteractionOperatorError('FermionOperator does not map '
                                               'to InteractionOperator.')

        else:
            # Handle non-molecular Hamiltonian.
            raise InteractionOperatorError('FermionOperator does not map '
                                           'to InteractionOperator.')

    # Form InteractionOperator and return.
    interaction_operator = InteractionOperator(constant, one_body, two_body)
    return interaction_operator
Ejemplo n.º 4
0
def apply_constraints(operator, n_fermions, use_scipy=True):
    """Function to use linear programming to apply constraints.

    Args:
        operator(FermionOperator): FermionOperator with only 1- and 2-body
            terms that we wish to vectorize.
        n_fermions(int): The number of particles in the simulation.
        use_scipy(bool): Whether to use scipy (True) or cvxopt (False).

    Returns:
        modified_operator(FermionOperator): The operator with reduced norm
            that has been modified with equality constraints.
    """
    # Get constraint matrix.
    n_orbitals = count_qubits(operator)
    constraints = constraint_matrix(n_orbitals, n_fermions)
    n_constraints, n_terms = constraints.get_shape()

    # Get vectorized operator.
    vectorized_operator = operator_to_vector(operator)
    initial_bound = numpy.sum(numpy.absolute(vectorized_operator[1::]))**2
    print('Initial bound on measurements is %f.' % initial_bound)

    # Get linear programming coefficient vector.
    n_variables = n_constraints + n_terms
    lp_vector = numpy.zeros(n_variables, float)
    lp_vector[-n_terms:] = 1.

    # Get linear programming constraint matrix.
    lp_constraint_matrix = scipy.sparse.dok_matrix((2 * n_terms, n_variables))
    for (i, j), value in constraints.items():
        if j:
            lp_constraint_matrix[j, i] = value
            lp_constraint_matrix[n_terms + j, i] = -value
    for i in range(n_terms):
        lp_constraint_matrix[i, n_constraints + i] = -1.
        lp_constraint_matrix[n_terms + i, n_constraints + i] = -1.

    # Get linear programming constraint vector.
    lp_constraint_vector = numpy.zeros(2 * n_terms, float)
    lp_constraint_vector[:n_terms] = vectorized_operator
    lp_constraint_vector[-n_terms:] = -vectorized_operator

    # Perform linear programming.
    print('Starting linear programming.')
    if use_scipy:
        options = {'maxiter': int(1e6)}
        bound = n_constraints * [(None, None)] + n_terms * [(0, None)]
        solution = scipy.optimize.linprog(c=lp_vector,
                                          A_ub=lp_constraint_matrix.toarray(),
                                          b_ub=lp_constraint_vector,
                                          bounds=bound,
                                          options=options)

        # Analyze results.
        print(solution['message'])
        assert solution['success']
        solution_vector = solution['x']
        objective = solution['fun']**2
        print('Program terminated after %i iterations.' % solution['nit'])

    else:
        # Convert to CVXOpt sparse matrix.
        from cvxopt import matrix, solvers, spmatrix
        lp_vector = matrix(lp_vector)
        lp_constraint_matrix = lp_constraint_matrix.tocoo()
        lp_constraint_matrix = spmatrix(lp_constraint_matrix.data,
                                        lp_constraint_matrix.row.tolist(),
                                        lp_constraint_matrix.col.tolist())
        lp_constraint_vector = matrix(lp_constraint_vector)

        # Run linear programming.
        solution = solvers.lp(c=lp_vector,
                              G=lp_constraint_matrix,
                              h=lp_constraint_vector,
                              solver='glpk')

        # Analyze results.
        print(solution['status'])
        solution_vector = numpy.array(solution['x']).transpose()[0]

    # Alternative bound.
    residuals = solution_vector[-n_terms:]
    alternative_bound = numpy.sum(numpy.absolute(residuals[1::]))**2
    print('Bound implied by solution vector is %f.' % alternative_bound)

    # Make sure residuals are positive.
    for residual in residuals:
        assert residual > -1e-6

    # Get bound on updated Hamiltonian.
    weights = solution_vector[:n_constraints]
    final_vectorized_operator = (vectorized_operator -
                                 constraints.transpose() * weights)
    final_bound = numpy.sum(numpy.absolute(final_vectorized_operator[1::]))**2
    print('Actual bound determined is %f.' % final_bound)

    # Return modified operator.
    modified_operator = vector_to_operator(final_vectorized_operator,
                                           n_orbitals)
    return (modified_operator + hermitian_conjugated(modified_operator)) / 2.
Ejemplo n.º 5
0
def get_linear_qubit_operator(qubit_operator, n_qubits=None):
    """ Return a linear operator with matvec defined to avoid instantiating a
    huge matrix, which requires lots of memory.

    The idea is that a single i_th qubit operator, O_i, is a 2-by-2 matrix, to
    be applied on a vector of length n_hilbert / 2^i, performs permutations and/
    or adds an extra factor for its first half and the second half, e.g. a `Z`
    operator keeps the first half unchanged, while adds a factor of -1 to the
    second half, while an `I` keeps it both components unchanged.

    Note that the vector length is n_hilbert / 2^i, therefore when one works on
    i monotonically (in increasing order), one keeps splitting the vector to the
    right size and then apply O_i on them independently.

    Also note that operator O_i, is an *envelop operator* for all operators
    after it, i.e. {O_j | j > i}, which implies that starting with i = 0, one
    can split the vector, apply O_i, split the resulting vector (cached) again
    for the next operator.

    Args:
      qubit_operator(QubitOperator): A qubit operator to be applied on vectors.

    Returns:
      linear_operator(LinearOperator): The equivalent operator which is well
      defined when applying to a vector.

    """
    if n_qubits is None:
        n_qubits = count_qubits(qubit_operator)
    if n_qubits < count_qubits(qubit_operator):
        raise ValueError('Invalid number of qubits specified.')

    n_hilbert = 2 ** n_qubits

    def matvec(vec):
        """matvec for the LinearOperator class.

        Args:
          vec(numpy.ndarray): 1D numpy array.

        Returns:
          retvec(numpy.ndarray): same to the shape of input vec.
        """
        if len(vec) != n_hilbert:
            raise ValueError('Invalid length of vector specified: %d != %d'
                             %(len(vec), n_hilbert))

        retvec = numpy.zeros(vec.shape, dtype=complex)
        # Loop through the terms.
        for qubit_term in qubit_operator.terms:
            vecs = [vec]
            tensor_factor = 0
            coefficient = qubit_operator.terms[qubit_term]

            for pauli_operator in qubit_term:
                # Split vector by half and half for each bit.
                if pauli_operator[0] > tensor_factor:
                    vecs = [v for iter_v in vecs for v in numpy.split(
                        iter_v, 2 ** (pauli_operator[0] - tensor_factor))]

                # Note that this is to make sure that XYZ operations always work
                # on vector pairs.
                vec_pairs = [numpy.split(v, 2) for v in vecs]

                # There is an non-identity op here, transform the vector.
                xyz = {
                    'X' : lambda vps: [[vp[1], vp[0]] for vp in vps],
                    'Y' : lambda vps: [[-1j * vp[1], 1j * vp[0]] for vp in vps],
                    'Z' : lambda vps: [[vp[0], -vp[1]] for vp in vps],
                }
                vecs = [v for vp in xyz[pauli_operator[1]](vec_pairs)
                        for v in vp]
                tensor_factor = pauli_operator[0] + 1

            # No need to check tensor_factor, i.e. to deal with bits left.
            retvec += coefficient * numpy.concatenate(vecs)
        return retvec
    return scipy.sparse.linalg.LinearOperator((n_hilbert, n_hilbert),
                                              matvec=matvec, dtype=complex)
Ejemplo n.º 6
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)
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
 def test_n_qubits_interaction_operator(self):
     self.assertEqual(self.n_qubits,
                      count_qubits(self.interaction_operator))
Ejemplo n.º 9
0
 def test_n_qubits_bad_type(self):
     with self.assertRaises(TypeError):
         count_qubits('twelve')
Ejemplo n.º 10
0
 def test_n_qubits_fermion_operator(self):
     self.assertEqual(self.n_qubits, count_qubits(self.fermion_operator))
Ejemplo n.º 11
0
 def test_n_qubits_qubit_operator(self):
     self.assertEqual(self.n_qubits, count_qubits(self.qubit_operator))
Ejemplo n.º 12
0
 def test_n_qubits_single_fermion_term(self):
     self.assertEqual(self.n_qubits, count_qubits(self.fermion_term))
def simulation_gate_trotter_step(register,
                                 hamiltonian,
                                 input_ordering=None,
                                 first_order=True):
    """Simulate a unit time Trotter step under the plane wave dual basis
    Hamiltonian using the fermionic simulation gate.

    Args:
        register (projectq.QuReg): The register to apply the unitary to.
        hamiltonian (QubitOperator): Qubit operator representing the problem
            in the plane-wave dual basis.
        input_ordering (list): The input Jordan-Wigner ordering.
        first_order (bool): Whether to apply a first or second-order
                            Trotter step.

    Notes:
        Applying a first-order Trotter step reverses the input ordering.
    """
    n_qubits = count_qubits(hamiltonian)

    if not input_ordering:
        input_ordering = list(range(n_qubits))

    # The intermediate ordering is the halfway point for second-order Trotter.
    intermediate_ordering = input_ordering[::-1]
    if first_order:
        final_ordering = intermediate_ordering
    else:
        final_ordering = input_ordering[:]

    # Whether we're in an odd or an even stagger. Alternates at each step.
    odd = 0

    # If we do a second order Trotter step, the input and final orderings
    # are the same. Use a flag to force us to leave the initial step.
    moved = False

    while input_ordering != final_ordering or not moved:
        moved = True
        for i in range(odd, n_qubits - 1, 2):
            left = input_ordering[i]
            right = input_ordering[i + 1]
            # For real c, c*([3^ 2] + [2^ 3]) transforms under JW to
            # c/2 (X_2 X_3 + Y_2 Y_3); evolution is exp(-ic/2(XX+YY))
            xx_yy_angle = hamiltonian.terms.get(((left, 1), (right, 0)), 0.0)

            left, right = max(left, right), min(left, right)
            # Two-body terms c 2^ 1^ 2 1 are equal to -c n_2 n_1. JW maps this
            # to -c(I-Z_2)(I-Z_1)/4 = -c I/4 + c Z_1/4 + c Z_2/4 - c Z_1 Z_2/4.
            # Evolution is thus exp(ic/4*(I - Z_1 - Z_2 + Z_1 Z_2)).
            zz_angle = -hamiltonian.terms.get(
                ((left, 1), (right, 1), (left, 0), (right, 0)), 0.0) / 2.

            # Divide by two for second order Trotter.
            if not first_order:
                xx_yy_angle /= 2
                zz_angle /= 2

            special_F_adjacent(register, i, xx_yy_angle, zz_angle)
            # The single-Z rotation angle is the opposite of the ZZ angle.
            if numpy.abs(zz_angle) > 0.0:
                Rz(-zz_angle) | register[i]
                Rz(-zz_angle) | register[i + 1]
                Ph(-zz_angle / 2.) | register[0]

            num_operator_left = ((input_ordering[i], 1), (input_ordering[i],
                                                          0))
            if num_operator_left in hamiltonian.terms:
                # Jordan-Wigner maps a number term c*n_i to c*(I-Z_i)/2.
                # Time evolution is then exp(-i c*(I-Z_i)/2), which is equal to
                # a phase exp(-ic/2) and a rotation Rz(-c).
                z_angle = (-hamiltonian.terms[num_operator_left] /
                           (n_qubits - 1))

                # Divide by two for second order Trotter.
                if not first_order:
                    z_angle /= 2.
                # After special_F_adjacent, qubits i and i+1 have swapped;
                # the ith rotation must thus be applied to qubit i+1.
                if numpy.abs(z_angle) > 0.0:
                    Rz(z_angle) | register[i + 1]
                    Ph(z_angle / 2.) | register[0]

            num_operator_right = ((input_ordering[i + 1], 1),
                                  (input_ordering[i + 1], 0))
            if num_operator_right in hamiltonian.terms:
                # Jordan-Wigner maps a number term c*n_i to c*(I-Z_i)/2.
                # Time evolution is then exp(-i c*(I-Z_i)/2), which is equal to
                # a phase exp(-ic/2) and a rotation Rz(-c).
                z_angle = (-hamiltonian.terms[num_operator_right] /
                           (n_qubits - 1))

                # Divide by two for second order Trotter.
                if not first_order:
                    z_angle /= 2
                # After special_F_adjacent, qubits i and i+1 have swapped;
                # the (i+1)th rotation must thus be applied to qubit i.
                if numpy.abs(z_angle) > 0.0:
                    Rz(z_angle) | register[i]
                    Ph(z_angle / 2.) | register[0]

            # Finally, swap the two modes in input_ordering.
            input_ordering[i], input_ordering[i + 1] = (input_ordering[i + 1],
                                                        input_ordering[i])

        # Unless we're at the intermediate ordering, odd should flip.
        # This is only needed for second-order Trotter.
        if input_ordering != intermediate_ordering:
            odd = 1 - odd

    return input_ordering
Ejemplo n.º 14
0
def build_qaoa_circuit_grads(params, hamiltonians):
    """ Generates gradient circuits and corresponding factors for the QAOA ansatz
        defined in the function build_qaoa_circuit.

    Args:
        hamiltonians (list):
            A list of dict or zquantum.core.qubitoperator.QubitOperator objects representing Hamiltonians
            H1, H2, ..., Hk which forms one layer of the ansatz
                    exp(-i Hk tk) ... exp(-i H2 t2) exp(-i H1 t1)
            For example, in the case of QAOA proposed by Farhi et al, the list the list is then
            [H1, H2] where
                H1 is the Hamiltonian for which the ground state is sought, and
                H2 is the Hamiltonian for which the time evolution act as a diffuser 
                    in the search space.
        params (numpy.ndarray): 
            A list of sets of parameters. Each parameter in a set specifies the time
            duration of evolution under each of the Hamiltonians H1, H2, ... Hk.
            
    Returns:
        gradient_circuits (list of lists of zquantum.core.circuit.Circuit: the circuits)
        circuit_factors (list of lists of floats): combination coefficients for the expectation
            values of the list of circuits.
    """
    if mod(len(params), len(hamiltonians)) != 0:
        raise Warning('There are {} input parameters and {} Hamiltonians. Since {} does not divide {} the last layer will be incomplete.'.\
            format(len(params), len(hamiltonians), len(params), len(hamiltonians)))

    # Convert qubit operators from dicts to QubitOperator objects, if needed
    for index, hamiltonian in enumerate(hamiltonians):
        if isinstance(hamiltonian, dict):
            hamiltonians[index] = convert_dict_to_qubitop(hamiltonian)

    hadamard_layer = Circuit()

    # Start with a layer of Hadarmard gates
    n_qubits = count_qubits(hamiltonians[0])
    qubits = [Qubit(qubit_index) for qubit_index in range(n_qubits)]
    hadamard_layer.qubits = qubits
    for qubit_index in range(n_qubits):
        hadamard_layer.gates.append(Gate('H', (qubits[qubit_index], )))

    # Add time evolution layers
    gradient_circuits = []
    circuit_factors = []

    for index1 in range(params.shape[0]):

        hamiltonian_index1 = mod(index1, len(hamiltonians))
        current_hamiltonian = qubitop_to_pyquilpauli(
            hamiltonians[hamiltonian_index1])
        derivative_circuits_for_index1, factors = time_evolution_derivatives(
            current_hamiltonian, params[index1])
        param_circuits = []

        for derivative_circuit in derivative_circuits_for_index1:

            output_circuit = Circuit()
            output_circuit.qubits = qubits
            output_circuit += hadamard_layer

            for index2 in range(params.shape[0]):
                hamiltonian_index2 = mod(index2, len(hamiltonians))
                if index2 == index1:
                    output_circuit += derivative_circuit
                else:
                    current_hamiltonian = qubitop_to_pyquilpauli(
                        hamiltonians[hamiltonian_index2])
                    output_circuit += time_evolution(current_hamiltonian,
                                                     params[index2])
            param_circuits.append(output_circuit)

        circuit_factors.append(factors)
        gradient_circuits.append(param_circuits)

    return gradient_circuits, circuit_factors
Ejemplo n.º 15
0
def get_diagonal_coulomb_hamiltonian(fermion_operator,
                                     n_qubits=None,
                                     ignore_incompatible_terms=False):
    r"""Convert a FermionOperator to a DiagonalCoulombHamiltonian.

    Args:
        fermion_operator(FermionOperator): The operator to convert.
        n_qubits(int): Optionally specify the total number of qubits in the
            system
        ignore_incompatible_terms(bool): This flag determines the behavior
            of this method when it encounters terms that are not represented
            by the DiagonalCoulombHamiltonian class, namely, terms that are
            not quadratic and not quartic of the form
            a^\dagger_p a_p a^\dagger_q a_q. If set to True, this method will
            simply ignore those terms. If False, then this method will raise
            an error if it encounters such a term. The default setting is False.
    """
    if not isinstance(fermion_operator, FermionOperator):
        raise TypeError('Input must be a FermionOperator.')

    if n_qubits is None:
        n_qubits = count_qubits(fermion_operator)
    if n_qubits < count_qubits(fermion_operator):
        raise ValueError('Invalid number of qubits specified.')

    fermion_operator = normal_ordered(fermion_operator)
    constant = 0.
    one_body = numpy.zeros((n_qubits, n_qubits), complex)
    two_body = numpy.zeros((n_qubits, n_qubits), float)

    for term, coefficient in fermion_operator.terms.items():
        # Ignore this term if the coefficient is zero
        if abs(coefficient) < EQ_TOLERANCE:
            continue

        if len(term) == 0:
            constant = coefficient
        else:
            actions = [operator[1] for operator in term]
            if actions == [1, 0]:
                p, q = [operator[0] for operator in term]
                one_body[p, q] = coefficient
            elif actions == [1, 1, 0, 0]:
                p, q, r, s = [operator[0] for operator in term]
                if p == r and q == s:
                    if abs(numpy.imag(coefficient)) > EQ_TOLERANCE:
                        raise ValueError(
                            'FermionOperator does not map to '
                            'DiagonalCoulombHamiltonian (not Hermitian).')
                    coefficient = numpy.real(coefficient)
                    two_body[p, q] = -.5 * coefficient
                    two_body[q, p] = -.5 * coefficient
                elif not ignore_incompatible_terms:
                    raise ValueError('FermionOperator does not map to '
                                     'DiagonalCoulombHamiltonian '
                                     '(contains terms with indices '
                                     '{}).'.format((p, q, r, s)))
            elif not ignore_incompatible_terms:
                raise ValueError('FermionOperator does not map to '
                                 'DiagonalCoulombHamiltonian (contains terms '
                                 'with action {}.'.format(tuple(actions)))

    # Check that the operator is Hermitian
    if not is_hermitian(one_body):
        raise ValueError(
            'FermionOperator does not map to DiagonalCoulombHamiltonian '
            '(not Hermitian).')

    return DiagonalCoulombHamiltonian(one_body, two_body, constant)
    def save(self):
        """Method to save the class under a systematic name."""
        self.get_energies()
        self.get_integrals_FCIDUMP()
        self.molecular_hamiltonian, self.one_body_coeff, self.two_body_coeff = self.get_molecular_hamiltonian(
        )
        self.n_qubits = count_qubits(self.molecular_hamiltonian)
        self.n_orbitals = len(self.spinor)
        tmp_name = uuid.uuid4()
        with h5py.File("{}.hdf5".format(tmp_name), "w") as f:
            # Save geometry:
            d_geom = f.create_group("geometry")
            if not isinstance(self.geometry, basestring):
                atoms = [numpy.string_(item[0]) for item in self.geometry]
                positions = numpy.array(
                    [list(item[1]) for item in self.geometry])
            else:
                atoms = numpy.string_(self.geometry)
                positions = None
            d_geom.create_dataset("atoms",
                                  data=(atoms if atoms is not None else False))
            d_geom.create_dataset(
                "positions",
                data=(positions if positions is not None else False))
            # Save basis:
            f.create_dataset("basis", data=numpy.string_(self.basis))
            # Save multiplicity:
            f.create_dataset("multiplicity", data=self.multiplicity)
            # Save charge:
            f.create_dataset("charge", data=self.charge)
            # Save description:
            f.create_dataset("description",
                             data=numpy.string_(self.description))
            # Save name:
            f.create_dataset("name", data=numpy.string_(self.name))
            # Save n_atoms:
            f.create_dataset("n_atoms", data=self.n_atoms)
            # Save atoms:
            f.create_dataset("atoms", data=numpy.string_(self.atoms))
            # Save protons:
            f.create_dataset("protons", data=self.protons)
            # Save n_electrons:
            f.create_dataset("n_electrons", data=self.n_electrons)
            # Save generic attributes from calculations:
            f.create_dataset("n_orbitals",
                             data=(self.n_orbitals
                                   if self.n_orbitals is not None else False))
            f.create_dataset(
                "n_qubits",
                data=(self.n_qubits if self.n_qubits is not None else False))
            f.create_dataset(
                "nuclear_repulsion",
                data=(self.E_core if self.E_core is not None else False))
            # Save attributes generated from SCF calculation.
            f.create_dataset(
                "hf_energy",
                data=(self.hf_energy if self.hf_energy is not None else False))
            f.create_dataset(
                "orbital_energies",
                data=(str(self.spinor) if self.spinor is not None else False))
            # Save attributes generated from integrals.
            f.create_dataset("one_body_integrals",
                             data=(str(self.one_body_int) if self.one_body_int
                                   is not None else False))
            f.create_dataset("two_body_integrals",
                             data=(str(self.two_body_int) if self.two_body_int
                                   is not None else False))
            f.create_dataset("one_body_coefficients",
                             data=(self.one_body_coeff if self.one_body_coeff
                                   is not None else False))
            f.create_dataset("two_body_coefficients",
                             data=(self.two_body_coeff if self.two_body_coeff
                                   is not None else False))
            f.create_dataset(
                "print_molecular_hamiltonian",
                data=(str(self.molecular_hamiltonian)
                      if self.molecular_hamiltonian is not None else False))
            # Save attributes generated from MP2 calculation.
            f.create_dataset("mp2_energy",
                             data=(self.mp2_energy
                                   if self.mp2_energy is not None else False))
            # Save attributes generated from CCSD calculation.
            f.create_dataset("ccsd_energy",
                             data=(self.ccsd_energy
                                   if self.ccsd_energy is not None else False))

        # Remove old file first for compatibility with systems that don't allow
        # rename replacement.  Catching OSError for when file does not exist
        # yet
        try:
            os.remove("{}.hdf5".format(self.filename))
        except OSError:
            pass

        os.rename("{}.hdf5".format(tmp_name), "{}.hdf5".format(self.filename))
Ejemplo n.º 17
0
def hartree_fock_state_jellium(grid, n_electrons, spinless=True,
                               plane_wave=False):
    """Give the Hartree-Fock state of jellium.

    Args:
        grid (Grid): The discretization to use.
        n_electrons (int): Number of electrons in the system.
        spinless (bool): Whether to use the spinless model or not.
        plane_wave (bool): Whether to return the Hartree-Fock state in
                           the plane wave (True) or dual basis (False).

    Notes:
        The jellium model is built up by filling the lowest-energy
        single-particle states in the plane-wave Hamiltonian until
        n_electrons states are filled.
    """
    from openfermion.hamiltonians import plane_wave_kinetic
    # Get the jellium Hamiltonian in the plane wave basis.
    # For determining the Hartree-Fock state in the PW basis, only the
    # kinetic energy terms matter.
    hamiltonian = plane_wave_kinetic(grid, spinless=spinless)
    hamiltonian = normal_ordered(hamiltonian)
    hamiltonian.compress()

    # The number of occupied single-particle states is the number of electrons.
    # Those states with the lowest single-particle energies are occupied first.
    occupied_states = lowest_single_particle_energy_states(
        hamiltonian, n_electrons)
    occupied_states = numpy.array(occupied_states)

    if plane_wave:
        # In the plane wave basis the HF state is a single determinant.
        hartree_fock_state_index = numpy.sum(2 ** occupied_states)
        hartree_fock_state = csr_matrix(
            ([1.0], ([hartree_fock_state_index], [0])),
            shape=(2 ** count_qubits(hamiltonian), 1))

    else:
        # Inverse Fourier transform the creation operators for the state to get
        # to the dual basis state, then use that to get the dual basis state.
        hartree_fock_state_creation_operator = FermionOperator.identity()
        for state in occupied_states[::-1]:
            hartree_fock_state_creation_operator *= (
                FermionOperator(((int(state), 1),)))
        dual_basis_hf_creation_operator = inverse_fourier_transform(
            hartree_fock_state_creation_operator, grid, spinless)

        dual_basis_hf_creation = normal_ordered(
            dual_basis_hf_creation_operator)

        # Initialize the HF state as a sparse matrix.
        hartree_fock_state = dok_matrix(
            (2 ** count_qubits(hamiltonian), 1), dtype=complex)

        # Populate the elements of the HF state in the dual basis.
        for term in dual_basis_hf_creation.terms:
            index = 0
            for operator in term:
                index += 2 ** operator[0]
            hartree_fock_state[index, 0] = dual_basis_hf_creation.terms[term]
        hartree_fock_state = hartree_fock_state.tocsr()

    return hartree_fock_state
Ejemplo n.º 18
0
def simulation_ordered_grouped_low_depth_terms_with_info(
        hamiltonian, input_ordering=None, external_potential_at_end=False):
    """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:
        hamiltonian (FermionOperator): The Hamiltonian.
        input_ordering (list): The initial Jordan-Wigner canonical order.
                               If no input ordering is specified, defaults to
                               [0..n_qubits] where n_qubits is the number of
                               qubits in the Hamiltonian.
        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 in order of simulation,
        the indices they act on, and whether they are hopping operators
        (both also in the same order).

    Notes:
        Follows the "stagger"-based simulation order discussed in Kivlichan
        et al., "Quantum Simulation of Electronic Structure with Linear
        Depth and Connectivity", arxiv:1711.04789; as such, the only
        permitted types of terms are hopping (i^ j + j^ i) and potential
        terms which are products of at most two number operators.
    """
    n_qubits = count_qubits(hamiltonian)
    hamiltonian = normal_ordered(hamiltonian)

    ordered_terms = []
    ordered_indices = []
    ordered_is_hopping_operator = []

    # If no input mode ordering is specified, default to range(n_qubits).
    try:
        input_ordering = list(input_ordering)
    except TypeError:
        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.
    parity = 0
    while input_ordering != final_ordering:
        results = stagger_with_info(hamiltonian, input_ordering, parity,
                                    external_potential_at_end)
        terms_in_layer, indices_in_layer, is_hopping_operator_in_layer = (
            results)

        ordered_terms.extend(terms_in_layer)
        ordered_indices.extend(indices_in_layer)
        ordered_is_hopping_operator.extend(is_hopping_operator_in_layer)

        # Alternate even and odd steps of the reversal procedure.
        parity = 1 - parity

    # If all the rotations from the external potential are in the final layer,
    # i.e. we don't intersperse them throughout the Trotter step.
    if external_potential_at_end:
        terms_in_final_layer = []
        indices_in_final_layer = []
        is_hopping_operator_in_final_layer = []

        for qubit in range(n_qubits):
            coeff = hamiltonian.terms.get(((qubit, 1), (qubit, 0)), 0.0)
            if coeff:
                terms_in_final_layer.append(
                    FermionOperator(((qubit, 1), (qubit, 0)), coeff))
                indices_in_final_layer.append(set((qubit, )))
                is_hopping_operator_in_final_layer.append(False)

        ordered_terms.extend(terms_in_final_layer)
        ordered_indices.extend(indices_in_final_layer)
        ordered_is_hopping_operator.extend(is_hopping_operator_in_final_layer)

    return (ordered_terms, ordered_indices, ordered_is_hopping_operator)
def taper_off_qubits(operator,
                     stabilizers,
                     manual_input=False,
                     fixed_positions=None,
                     output_tapered_positions=False):
    r"""
    Remove qubits from given operator.

    Qubits are removed by eliminating an equivalent number of
    stabilizer conditions. Which qubits that are can either be determined
    automatically or their positions can be set manually.

    Qubits can be disregarded from the Hamiltonian when the effect of all its
    terms on them is rendered trivial. This algorithm employs a stabilizers
    like :math:`\pm X \otimes p` to fix the action of every Pauli
    string on the first qubit to :math:`Z` or the identity. A string
    :math:`X \otimes h` would for instance be multiplied with the stabilizer
    to obtain :math:`1 \otimes (\pm h\cdot p)` while a string
    :math:`Z \otimes h^\prime` would pass without correction. The first
    qubit can subsequently be removed as it must be in the computational basis
    in Hamiltonian eigenstates.
    For stabilizers acting as :math:`Y` (:math:`Z`) on selected qubits,
    the algorithm would fix the action of every Hamiltonian string to
    :math:`Z` (:math:`X`). Updating also the list of remaining stabilizer
    generators, the algorithm is run iteratively.

    Args:
        operator (QubitOperator): Operator of which qubits will be removed.
        stabilizers (QubitOperator): Stabilizer generators for the tapering.
                                     Can also be passed as a list of
                                     QubitOperator.
        manual_input (Boolean): Option to pass the list of fixed qubits
                                positions manually. Set to False by default.
        fixed_positions (list): (optional) List of fixed qubit positions.
                                Passing a list is only effective if
                                manual_input is True.
        output_tapered_positions (Boolean): Option to output the positions of
                                            qubits that have been removed.
    Returns:
        skimmed_operator (QubitOperator): Operator with fewer qubits.
        removed_positions (list): (optional) List of removed qubit positions.
                                  For the qubits to be gone in the qubit count,
                                  the remaining qubits have been moved up to
                                  those indices.
    """
    n_qbits = count_qubits(operator)
    (ham_to_update,
     qbts_to_rm) = reduce_number_of_terms(operator,
                                          stabilizers,
                                          maintain_length=False,
                                          manual_input=manual_input,
                                          fixed_positions=fixed_positions,
                                          output_fixed_positions=True)

    # Gets a list of the order of the qubits after tapering
    qbit_order = list(numpy.arange(n_qbits - len(qbts_to_rm), dtype=int))
    # Save the original list before it gets ordered
    removed_positions = qbts_to_rm
    qbts_to_rm.sort()
    for x in qbts_to_rm:
        qbit_order.insert(x, 'remove')

    # Remove the qubits
    skimmed_operator = QubitOperator()
    for term, coef in ham_to_update.terms.items():
        if term == ():
            skimmed_operator += QubitOperator('', coef)
            continue
        tap_tpls = []
        for p in term:
            if qbit_order[p[0]] != 'remove':
                tap_tpls.append((qbit_order[p[0]].item(), p[1]))

        skimmed_operator += QubitOperator(tuple(tap_tpls), coef)

    if output_tapered_positions:
        return skimmed_operator, removed_positions
    else:
        return skimmed_operator
Ejemplo n.º 20
0
from openfermion.ops import QubitOperator as QOp
from openfermion.utils import count_qubits


@qjit
def state_prep(q: qreg):
    X(q[0])


qsearch_optimizer = createTransformation("qsearch")

observable = QOp('', 5.907) + QOp('Y0 Y1', -2.1433) + \
                QOp('X0 X1', -2.1433) + QOp('Z0', .21829) + QOp('Z1', -6.125)
n_steps = 3
step_size = 0.1
n_qubits = count_qubits(observable)

problemModel = QuaSiMo.ModelFactory.createModel(state_prep, observable,
                                                n_qubits)
workflow = QuaSiMo.getWorkflow(
    "qite", {
        "steps": n_steps,
        "step-size": step_size,
        "circuit-optimizer": qsearch_optimizer
    })

set_verbose(True)
result = workflow.execute(problemModel)

energy = result["energy"]
finalCircuit = result["circuit"]
Ejemplo n.º 21
0
    def test_ucc_h2_singlet(self):
        geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))]
        basis = 'sto-3g'
        multiplicity = 1
        filename = os.path.join(THIS_DIRECTORY, 'data',
                                'H2_sto-3g_singlet_0.7414')
        self.molecule = MolecularData(geometry,
                                      basis,
                                      multiplicity,
                                      filename=filename)
        self.molecule.load()

        # Get molecular Hamiltonian.
        self.molecular_hamiltonian = self.molecule.get_molecular_hamiltonian()

        # Get FCI RDM.
        self.fci_rdm = self.molecule.get_molecular_rdm(use_fci=1)

        # Get explicit coefficients.
        self.nuclear_repulsion = self.molecular_hamiltonian.constant
        self.one_body = self.molecular_hamiltonian.one_body_tensor
        self.two_body = self.molecular_hamiltonian.two_body_tensor

        # Get fermion Hamiltonian.
        self.fermion_hamiltonian = normal_ordered(
            get_fermion_operator(self.molecular_hamiltonian))

        # Get qubit Hamiltonian.
        self.qubit_hamiltonian = jordan_wigner(self.fermion_hamiltonian)

        # Get the sparse matrix.
        self.hamiltonian_matrix = get_sparse_operator(
            self.molecular_hamiltonian)
        # Test UCCSD for accuracy against FCI using loaded t amplitudes.
        ucc_operator = uccsd_generator(self.molecule.ccsd_single_amps,
                                       self.molecule.ccsd_double_amps)

        hf_state = jw_hartree_fock_state(self.molecule.n_electrons,
                                         count_qubits(self.qubit_hamiltonian))
        uccsd_sparse = jordan_wigner_sparse(ucc_operator)
        uccsd_state = scipy.sparse.linalg.expm_multiply(uccsd_sparse, hf_state)
        expected_uccsd_energy = expectation(self.hamiltonian_matrix,
                                            uccsd_state)
        self.assertAlmostEqual(expected_uccsd_energy,
                               self.molecule.fci_energy,
                               places=4)
        print("UCCSD ENERGY: {}".format(expected_uccsd_energy))

        # Test CCSD singlet for precise match against FCI using loaded t
        # amplitudes
        packed_amplitudes = uccsd_singlet_get_packed_amplitudes(
            self.molecule.ccsd_single_amps, self.molecule.ccsd_double_amps,
            self.molecule.n_qubits, self.molecule.n_electrons)
        ccsd_operator = uccsd_singlet_generator(packed_amplitudes,
                                                self.molecule.n_qubits,
                                                self.molecule.n_electrons,
                                                anti_hermitian=False)

        ccsd_sparse_r = jordan_wigner_sparse(ccsd_operator)
        ccsd_sparse_l = jordan_wigner_sparse(
            -hermitian_conjugated(ccsd_operator))

        ccsd_state_r = scipy.sparse.linalg.expm_multiply(
            ccsd_sparse_r, hf_state)
        ccsd_state_l = scipy.sparse.linalg.expm_multiply(
            ccsd_sparse_l, hf_state)
        expected_ccsd_energy = ccsd_state_l.conjugate().dot(
            self.hamiltonian_matrix.dot(ccsd_state_r))
        self.assertAlmostEqual(expected_ccsd_energy, self.molecule.fci_energy)
def XBK(qubit_Hs,
        qubit_Cs,
        r,
        sampler,
        starting_lam=0,
        num_samples=1000,
        strength=1e3,
        verbose=False):

    n = count_qubits(qubit_Hs[0])
    min_energies, ground_states = [], []

    for p in range(int(math.ceil(r / 2 + 1))):
        qubit_H, qubit_C = qubit_Hs[p], qubit_Cs[p]

        #create C function to evalute sum(b^2)
        C_dict = convert_dict(qubit_C.terms)
        C_func = dict_to_func(C_dict)

        #calculate minimum energy for particular p value
        lam = starting_lam
        eigenvalue = min_energy = -1
        ground_state = []
        cycles = 0
        while min_energy < 0 and cycles < 10:
            #subtract lambda C from H
            H_prime = qubit_H - lam * qubit_C

            #construct qubo from reduced Hamiltonian
            bqm = dimod.higherorder.utils.make_quadratic(
                convert_dict(H_prime.terms), strength, dimod.SPIN)
            qubo, constant = bqm.to_qubo()

            if qubo == {}:
                break

            #run sampler
            response = sampler.sample_qubo(qubo, num_reads=num_samples)
            solutions = pd.DataFrame(response.data())

            #get mininum energy solution
            index = int(solutions[['energy']].idxmin())
            min_energy = round(solutions['energy'][index], 14) + constant
            full_solution = solutions['sample'][index]

            solution = []
            for key in ['s' + str(i) for i in range(n)]:
                try:
                    solution += [2 * full_solution[key] - 1]
                except KeyError:
                    solution += [0]
                    print('KeyError: ' + str(key))

            #calculate sum(b^2)
            try:
                sumBsq = int(C_func(*solution))
            except TypeError:
                sumBsq = int(C_func)

            if sumBsq == 0:  #stop the loop in zero case
                cycles += 1
                break

            #calculate the eigenvalue of H
            eigenvalue = lam + min_energy / sumBsq

            #set lam equal to eigenvalue for next cycle
            if min_energy < 0:
                lam = eigenvalue
                ground_state = [(val + 1) // 2 for val in solution]
            cycles += 1

        min_energies += [round(lam, 14)]
        ground_states += [ground_state]

        if verbose:
            print('P:', p, 'E:', round(lam, 5))

    index = min_energies.index(min(min_energies))
    min_energy = min_energies[index]
    ground_state = ground_states[index]

    if verbose:
        print('Energy:', round(min_energy, 5))

    return min_energy, ground_state
Ejemplo n.º 23
0
def qubit_operator_sparse(qubit_operator, n_qubits=None):
    """Initialize a Scipy sparse matrix from a QubitOperator.

    Args:
        qubit_operator(QubitOperator): instance of the QubitOperator class.
        n_qubits (int): Number of qubits.

    Returns:
        The corresponding Scipy sparse matrix.
    """
    if n_qubits is None:
        n_qubits = count_qubits(qubit_operator)
    if n_qubits < count_qubits(qubit_operator):
        raise ValueError('Invalid number of qubits specified.')

    # Construct the Scipy sparse matrix.
    n_hilbert = 2 ** n_qubits
    values_list = [[]]
    row_list = [[]]
    column_list = [[]]

    # Loop through the terms.
    for qubit_term in qubit_operator.terms:
        tensor_factor = 0
        coefficient = qubit_operator.terms[qubit_term]
        sparse_operators = [coefficient]
        for pauli_operator in qubit_term:

            # Grow space for missing identity operators.
            if pauli_operator[0] > tensor_factor:
                identity_qubits = pauli_operator[0] - tensor_factor
                identity = scipy.sparse.identity(
                    2 ** identity_qubits, dtype=complex, format='csc')
                sparse_operators += [identity]

            # Add actual operator to the list.
            sparse_operators += [pauli_matrix_map[pauli_operator[1]]]
            tensor_factor = pauli_operator[0] + 1

        # Grow space at end of string unless operator acted on final qubit.
        if tensor_factor < n_qubits or not qubit_term:
            identity_qubits = n_qubits - tensor_factor
            identity = scipy.sparse.identity(
                2 ** identity_qubits, dtype=complex, format='csc')
            sparse_operators += [identity]

        # Extract triplets from sparse_term.
        sparse_matrix = kronecker_operators(sparse_operators)
        values_list.append(sparse_matrix.tocoo(copy=False).data)
        (column, row) = sparse_matrix.nonzero()
        column_list.append(column)
        row_list.append(row)

    # Create sparse operator.
    values_list = numpy.concatenate(values_list)
    row_list = numpy.concatenate(row_list)
    column_list = numpy.concatenate(column_list)
    sparse_operator = scipy.sparse.coo_matrix((
        values_list, (row_list, column_list)),
        shape=(n_hilbert, n_hilbert)).tocsc(copy=False)
    sparse_operator.eliminate_zeros()
    return sparse_operator
def XBK_transform(op, r, p):

    n = count_qubits(op)
    op_terms = op.terms
    new_op = QubitOperator()

    #transform operator term by term
    for key in op_terms:
        coeff = op_terms[key]
        term = QubitOperator()

        #cycle through each of the r ancillarly qubit copies
        for j in range(r):
            for k in range(r):
                sign = 1 if (j < p) == (k < p) else -1
                sub_term = QubitOperator('', 1)

                #cycle through each of the n original qubits
                spot = 0
                for i in range(n):
                    try:
                        if key[spot][0] == i:
                            char = key[spot][1]
                            spot += 1
                        else:
                            char = 'I'
                    except IndexError:
                        char = 'I'

                    #use variable type to apply correct mapping
                    if char == 'X':
                        if j == k:
                            sub_term = QubitOperator('', 0)
                            break
                        else:
                            sub_term *= QubitOperator(
                                '', 1 / 2) - QubitOperator(
                                    'Z' + str(i + n * j) + ' Z' +
                                    str(i + n * k), 1 / 2)
                    elif char == 'Y':
                        if j == k:
                            sub_term = QubitOperator('', 0)
                            break
                        else:
                            sub_term *= QubitOperator(
                                'Z' + str(i + n * k), 1j / 2) - QubitOperator(
                                    'Z' + str(i + n * j), 1j / 2)
                    elif char == 'Z':
                        if j == k:
                            sub_term *= QubitOperator('Z' + str(i + n * j), 1)
                        else:
                            sub_term *= QubitOperator(
                                'Z' + str(i + n * j), 1 / 2) + QubitOperator(
                                    'Z' + str(i + n * k), 1 / 2)
                    else:
                        if j == k:
                            continue
                        else:
                            sub_term *= QubitOperator(
                                '', 1 / 2) + QubitOperator(
                                    'Z' + str(i + n * j) + ' Z' +
                                    str(i + n * k), 1 / 2)

                term += sign * sub_term
        new_op += coeff * term

    new_op.compress()
    return new_op
Ejemplo n.º 25
0
def bravyi_kitaev_fast_edge_matrix(iop, n_qubits=None):
    """
    Use InteractionOperator to construct edge matrix required for the algorithm

    Edge matrix contains the information about the edges between vertices.
    Edge matrix is required to build the operators in bravyi_kitaev_fast model.

    Args:
        iop (Interaction Operator):

    Returns:
        edge_matrix (Numpy array): A square numpy array containing information
            about the edges present in the model.
    """
    n_qubits = count_qubits(iop)
    edge_matrix = 1j*numpy.zeros((n_qubits, n_qubits))
    # Loop through all indices.
    for p in range(n_qubits):
        for q in range(n_qubits):

            # Handle one-body terms.
            coefficient = complex(iop[p, q])
            if coefficient and p >= q:
                edge_matrix[p, q] = bool(complex(iop[p, q]))

            # Keep looping for the two-body terms.
            for r in range(n_qubits):
                for s in range(n_qubits):
                    coefficient2 = complex(iop[p, q, r, s])

                    # Skip zero terms.
                    if (not coefficient2) or (p == q) or (r == s):
                        continue

                    # Identify and skip one of the complex conjugates.
                    if [p, q, r, s] != [s, r, q, p]:
                        if len(set([p, q, r, s])) == 4:
                            if min(r, s) < min(p, q):
                                continue
                        elif p != r and q < p:
                                continue

                    # Handle case of four unique indices.
                    if len(set([p, q, r, s])) == 4:

                        if coefficient2 and p >= q:
                            edge_matrix[p, q] = bool(complex(iop[p, q, r, s]))
                            a, b = sorted([r, s])
                            edge_matrix[b, a] = bool(complex(iop[p, q, r, s]))

                    # Handle case of three unique indices.
                    elif len(set([p, q, r, s])) == 3:

                        # Identify equal tensor factors.
                        if p == r:
                            a, b = sorted([q, s])
                            edge_matrix[b, a] = bool(complex(iop[p, q, r, s]))
                        elif p == s:
                            a, b = sorted([q, r])
                            edge_matrix[b, a] = bool(complex(iop[p, q, r, s]))
                        elif q == r:
                            a, b = sorted([p, s])
                            edge_matrix[b, a] = bool(complex(iop[p, q, r, s]))
                        elif q == s:
                            a, b = sorted([p, r])
                            edge_matrix[b, a] = bool(complex(iop[p, q, r, s]))

                    # Handle case of two unique indices.
                    elif len(set([p, q, r, s])) == 2:
                        if p == s:
                            a, b = sorted([p, q])
                            edge_matrix[b, a] = bool(complex(iop[p, q, r, s]))

                        else:
                            a, b = sorted([p, q])
                            edge_matrix[b, a] = bool(complex(iop[p, q, r, s]))

    return edge_matrix.transpose()
Ejemplo n.º 26
0
def simulation_ordered_grouped_hubbard_terms_with_info(hubbard_hamiltonian):
    """Give terms from the Fermi-Hubbard Hamiltonian in simulated order.

    Uses the simulation ordering, grouping terms into hopping
    (i^ j + j^ i) and on-site potential (i^j^ i j) operators.
    Pre-computes term information (indices each operator acts on, as
    well as whether each operator is a hopping operator).

    Args:
        hubbard_hamiltonian (FermionOperator): The Hamiltonian.
        original_ordering (list): The initial Jordan-Wigner canonical order.

    Returns:
        A 3-tuple of terms from the Hubbard Hamiltonian in order of
        simulation, the indices they act on, and whether they are hopping
        operators (both also in the same order).

    Notes:
        Assumes that the Hubbard model has spin and is on a 2D square
        aperiodic lattice. Uses the "stagger"-based Trotter step for the
        Hubbard model detailed in Kivlichan et al., "Quantum Simulation
        of Electronic Structure with Linear Depth and Connectivity",
        arxiv:1711.04789.
    """
    hamiltonian = normal_ordered(hubbard_hamiltonian)
    n_qubits = count_qubits(hamiltonian)
    side_length = int(numpy.sqrt(n_qubits / 2.0))

    ordered_terms = []
    ordered_indices = []
    ordered_is_hopping_operator = []

    original_ordering = list(range(n_qubits))
    for i in range(0, n_qubits - side_length, 2 * side_length):
        for j in range(2 * bool(i % (4 * side_length)), 2 * side_length, 4):
            original_ordering[i + j], original_ordering[i + j + 1] = (
                original_ordering[i + j + 1], original_ordering[i + j])

    input_ordering = list(original_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.
    parities = [False, True] * int(side_length / 2 + 1)  # int floors here
    for parity in parities:
        results = stagger_with_info(hubbard_hamiltonian, input_ordering, parity)
        terms_in_step, indices_in_step, is_hopping_operator_in_step = results

        ordered_terms.extend(terms_in_step)
        ordered_indices.extend(indices_in_step)
        ordered_is_hopping_operator.extend(is_hopping_operator_in_step)

    input_ordering = list(original_ordering)

    parities = [True] + [False, True] * int(side_length / 2)  # int floors here
    for parity in parities:
        results = stagger_with_info(hubbard_hamiltonian, input_ordering, parity)
        terms_in_step, indices_in_step, is_hopping_operator_in_step = results

        ordered_terms.extend(terms_in_step)
        ordered_indices.extend(indices_in_step)
        ordered_is_hopping_operator.extend(is_hopping_operator_in_step)

    return (ordered_terms, ordered_indices, ordered_is_hopping_operator)
Ejemplo n.º 27
0
def get_quadratic_hamiltonian(fermion_operator,
                              chemical_potential=0.,
                              n_qubits=None):
    """Convert a quadratic fermionic operator to QuadraticHamiltonian.

    This function should only be called on fermionic operators which
    consist of only a_p^\dagger a_q, a_p^\dagger a_q^\dagger, and a_p a_q
    terms.

    Returns:
       quadratic_hamiltonian: An instance of the QuadraticHamiltonian class.

    Raises:
        TypeError: Input must be a FermionOperator.
        TypeError: FermionOperator does not map to QuadraticHamiltonian.

    Warning:
        Even assuming that each creation or annihilation operator appears
        at most a constant number of times in the original operator, the
        runtime of this method is exponential in the number of qubits.
    """
    if not isinstance(fermion_operator, FermionOperator):
        raise TypeError('Input must be a FermionOperator.')

    if n_qubits is None:
        n_qubits = count_qubits(fermion_operator)
    if n_qubits < count_qubits(fermion_operator):
        raise ValueError('Invalid number of qubits specified.')

    # Normal order the terms and initialize.
    fermion_operator = normal_ordered(fermion_operator)
    constant = 0.
    combined_hermitian_part = numpy.zeros((n_qubits, n_qubits), complex)
    antisymmetric_part = numpy.zeros((n_qubits, n_qubits), complex)

    # Loop through terms and assign to matrix.
    for term in fermion_operator.terms:
        coefficient = fermion_operator.terms[term]
        # Ignore this term if the coefficient is zero
        if abs(coefficient) < EQ_TOLERANCE:
            continue

        if len(term) == 0:
            # Constant term
            constant = coefficient
        elif len(term) == 2:
            ladder_type = [operator[1] for operator in term]
            p, q = [operator[0] for operator in term]

            if ladder_type == [1, 0]:
                combined_hermitian_part[p, q] = coefficient
            elif ladder_type == [1, 1]:
                # Need to check that the corresponding [0, 0] term is present
                conjugate_term = ((p, 0), (q, 0))
                if conjugate_term not in fermion_operator.terms:
                    raise QuadraticHamiltonianError(
                        'FermionOperator does not map '
                        'to QuadraticHamiltonian (not Hermitian).')
                else:
                    matching_coefficient = -fermion_operator.terms[
                        conjugate_term].conjugate()
                    discrepancy = abs(coefficient - matching_coefficient)
                    if discrepancy > EQ_TOLERANCE:
                        raise QuadraticHamiltonianError(
                            'FermionOperator does not map '
                            'to QuadraticHamiltonian (not Hermitian).')
                antisymmetric_part[p, q] += .5 * coefficient
                antisymmetric_part[q, p] -= .5 * coefficient
            else:
                # ladder_type == [0, 0]
                # Need to check that the corresponding [1, 1] term is present
                conjugate_term = ((p, 1), (q, 1))
                if conjugate_term not in fermion_operator.terms:
                    raise QuadraticHamiltonianError(
                        'FermionOperator does not map '
                        'to QuadraticHamiltonian (not Hermitian).')
                else:
                    matching_coefficient = -fermion_operator.terms[
                        conjugate_term].conjugate()
                    discrepancy = abs(coefficient - matching_coefficient)
                    if discrepancy > EQ_TOLERANCE:
                        raise QuadraticHamiltonianError(
                            'FermionOperator does not map '
                            'to QuadraticHamiltonian (not Hermitian).')
                antisymmetric_part[p, q] -= .5 * coefficient.conjugate()
                antisymmetric_part[q, p] += .5 * coefficient.conjugate()
        else:
            # Operator contains non-quadratic terms
            raise QuadraticHamiltonianError('FermionOperator does not map '
                                            'to QuadraticHamiltonian '
                                            '(contains non-quadratic terms).')

    # Compute Hermitian part
    hermitian_part = (combined_hermitian_part +
                      chemical_potential * numpy.eye(n_qubits))

    # Check that the operator is Hermitian
    difference = hermitian_part - hermitian_part.T.conj()
    discrepancy = numpy.max(numpy.abs(difference))
    if discrepancy > EQ_TOLERANCE:
        raise QuadraticHamiltonianError(
            'FermionOperator does not map '
            'to QuadraticHamiltonian (not Hermitian).')

    # Form QuadraticHamiltonian and return.
    discrepancy = numpy.max(numpy.abs(antisymmetric_part))
    if discrepancy < EQ_TOLERANCE:
        # Hamiltonian conserves particle number
        quadratic_hamiltonian = QuadraticHamiltonian(
            constant, hermitian_part, chemical_potential=chemical_potential)
    else:
        # Hamiltonian does not conserve particle number
        quadratic_hamiltonian = QuadraticHamiltonian(constant, hermitian_part,
                                                     antisymmetric_part,
                                                     chemical_potential)

    return quadratic_hamiltonian
Ejemplo n.º 28
0
def get_quadratic_hamiltonian(fermion_operator,
                              chemical_potential=0.,
                              n_qubits=None,
                              ignore_incompatible_terms=False):
    r"""Convert a quadratic fermionic operator to QuadraticHamiltonian.

    Args:
        fermion_operator(FermionOperator): The operator to convert.
        chemical_potential(float): A chemical potential to include in
            the returned operator
        n_qubits(int): Optionally specify the total number of qubits in the
            system
        ignore_incompatible_terms(bool): This flag determines the behavior
            of this method when it encounters terms that are not quadratic
            that is, terms that are not of the form a^\dagger_p a_q.
            If set to True, this method will simply ignore those terms.
            If False, then this method will raise an error if it encounters
            such a term. The default setting is False.

    Returns:
       quadratic_hamiltonian: An instance of the QuadraticHamiltonian class.

    Raises:
        TypeError: Input must be a FermionOperator.
        TypeError: FermionOperator does not map to QuadraticHamiltonian.

    Warning:
        Even assuming that each creation or annihilation operator appears
        at most a constant number of times in the original operator, the
        runtime of this method is exponential in the number of qubits.
    """
    if not isinstance(fermion_operator, FermionOperator):
        raise TypeError('Input must be a FermionOperator.')

    if n_qubits is None:
        n_qubits = count_qubits(fermion_operator)
    if n_qubits < count_qubits(fermion_operator):
        raise ValueError('Invalid number of qubits specified.')

    # Normal order the terms and initialize.
    fermion_operator = normal_ordered(fermion_operator)
    constant = 0.
    combined_hermitian_part = numpy.zeros((n_qubits, n_qubits), complex)
    antisymmetric_part = numpy.zeros((n_qubits, n_qubits), complex)

    # Loop through terms and assign to matrix.
    for term in fermion_operator.terms:
        coefficient = fermion_operator.terms[term]
        # Ignore this term if the coefficient is zero
        if abs(coefficient) < EQ_TOLERANCE:
            continue

        if len(term) == 0:
            # Constant term
            constant = coefficient
        elif len(term) == 2:
            ladder_type = [operator[1] for operator in term]
            p, q = [operator[0] for operator in term]

            if ladder_type == [1, 0]:
                combined_hermitian_part[p, q] = coefficient
            elif ladder_type == [1, 1]:
                # Need to check that the corresponding [0, 0] term is present
                conjugate_term = ((p, 0), (q, 0))
                if conjugate_term not in fermion_operator.terms:
                    raise QuadraticHamiltonianError(
                        'FermionOperator does not map '
                        'to QuadraticHamiltonian (not Hermitian).')
                else:
                    matching_coefficient = -fermion_operator.terms[
                        conjugate_term].conjugate()
                    discrepancy = abs(coefficient - matching_coefficient)
                    if discrepancy > EQ_TOLERANCE:
                        raise QuadraticHamiltonianError(
                            'FermionOperator does not map '
                            'to QuadraticHamiltonian (not Hermitian).')
                antisymmetric_part[p, q] += .5 * coefficient
                antisymmetric_part[q, p] -= .5 * coefficient
            else:
                # ladder_type == [0, 0]
                # Need to check that the corresponding [1, 1] term is present
                conjugate_term = ((p, 1), (q, 1))
                if conjugate_term not in fermion_operator.terms:
                    raise QuadraticHamiltonianError(
                        'FermionOperator does not map '
                        'to QuadraticHamiltonian (not Hermitian).')
                else:
                    matching_coefficient = -fermion_operator.terms[
                        conjugate_term].conjugate()
                    discrepancy = abs(coefficient - matching_coefficient)
                    if discrepancy > EQ_TOLERANCE:
                        raise QuadraticHamiltonianError(
                            'FermionOperator does not map '
                            'to QuadraticHamiltonian (not Hermitian).')
                antisymmetric_part[p, q] -= .5 * coefficient.conjugate()
                antisymmetric_part[q, p] += .5 * coefficient.conjugate()
        elif not ignore_incompatible_terms:
            # Operator contains non-quadratic terms
            raise QuadraticHamiltonianError('FermionOperator does not map '
                                            'to QuadraticHamiltonian '
                                            '(contains non-quadratic terms).')

    # Compute Hermitian part
    hermitian_part = (combined_hermitian_part +
                      chemical_potential * numpy.eye(n_qubits))

    # Check that the operator is Hermitian
    if not is_hermitian(hermitian_part):
        raise QuadraticHamiltonianError(
            'FermionOperator does not map '
            'to QuadraticHamiltonian (not Hermitian).')

    # Form QuadraticHamiltonian and return.
    discrepancy = numpy.max(numpy.abs(antisymmetric_part))
    if discrepancy < EQ_TOLERANCE:
        # Hamiltonian conserves particle number
        quadratic_hamiltonian = QuadraticHamiltonian(
            hermitian_part,
            constant=constant,
            chemical_potential=chemical_potential)
    else:
        # Hamiltonian does not conserve particle number
        quadratic_hamiltonian = QuadraticHamiltonian(hermitian_part,
                                                     antisymmetric_part,
                                                     constant,
                                                     chemical_potential)

    return quadratic_hamiltonian
Ejemplo n.º 29
0
def pauli_exp_to_qasm(qubit_operator_list,
                      evolution_time=1.0,
                      qubit_list=None,
                      ancilla=None):
    """Exponentiate a list of QubitOperators to a QASM string generator.

    Exponentiates a list of QubitOperators, and yields string generators in
        QASM format using the formula:  exp(-1.0j * evolution_time * op).

    Args:
        qubit_operator_list (list of QubitOperators): list of single Pauli-term
            QubitOperators to be exponentiated
        evolution_time (float): evolution time of the operators in
            the list
        qubit_list: (list/tuple or None)Specifies the labels for the qubits
            to be output in qasm.
            If a list/tuple, must have length greater than or equal to the
            number of qubits in the QubitOperator. Entries in the
            list must be castable to string.
            If None, qubits are labeled by index (i.e. an integer).
        ancilla (string or None): if any, an ancilla qubit to perform
            the rotation conditional on (for quantum phase estimation)

    Yields:
        string
    """

    num_qubits = max([count_qubits(qubit_operator)
                      for qubit_operator in qubit_operator_list])
    if qubit_list is None:
        qubit_list = list(range(num_qubits))
    else:
        if type(qubit_list) is not tuple and type(qubit_list) is not list:
            raise TypeError('qubit_list must be one of None, tuple, or list.')
        if len(qubit_list) < num_qubits:
            raise TypeError('qubit_list must have an entry for every qubit')

    for qubit_operator in qubit_operator_list:
        # ret_val = ""
        ret_list = []

        for term in qubit_operator.terms:

            term_coeff = qubit_operator.terms[term]

            # Force float
            term_coeff = float(numpy.real(term_coeff))

            # List of operators and list of qubit ids
            ops = []
            qids = []
            string_basis_1 = []  # Basis rotations 1
            string_basis_2 = []  # Basis rotations 2

            for p in term:  # p = single pauli term
                qid = qubit_list[p[0]]
                pop = p[1]  # Pauli op

                qids.append(qid)  # Qubit index
                ops.append(pop)  # Pauli operator

                if pop == 'X':
                    string_basis_1.append("H {}".format(qid))    # Hadamard
                    string_basis_2.append("H {}".format(qid))    # Hadamard
                elif pop == 'Y':
                    string_basis_1.append(
                        "Rx 1.5707963267948966 {}".format(qid))
                    string_basis_2.append(
                        "Rx -1.5707963267948966 {}".format(qid))

            # Prep for CNOTs
            cnot_pairs = numpy.vstack((qids[:-1], qids[1:]))
            cnots1 = []
            cnots2 = []
            for i in range(cnot_pairs.shape[1]):
                pair = cnot_pairs[:, i]
                cnots1.append("CNOT {} {}".format(pair[0], pair[1]))
            for i in numpy.arange(cnot_pairs.shape[1])[::-1]:
                pair = cnot_pairs[:, i]
                cnots2.append("CNOT {} {}".format(pair[0], pair[1]))

            # Exponentiating each Pauli string requires five parts

            # 1. Perform basis rotations
            ret_list = ret_list + string_basis_1

            # 2. First set CNOTs
            ret_list = ret_list + cnots1

            # 3. Rotation (Note kexp & Ntrot)
            if ancilla is not None:
                if len(qids) > 0:
                    ret_list = ret_list + ["C-Phase {} {} {}".format(
                        -2 * term_coeff * evolution_time, ancilla, qids[-1])]
                    ret_list = ret_list + ["Rz {} {}".format(
                        1 * term_coeff * evolution_time, ancilla)]
                else:
                    ret_list = ret_list + ["Rz {} {}".format(
                        1 * term_coeff*evolution_time, ancilla)]
            else:
                if len(qids) > 0:
                    ret_list = ret_list + ["Rz {} {}".format(
                        term_coeff * evolution_time, qids[-1])]

            # 4. Second set of CNOTs
            ret_list = ret_list + cnots2

            # 5. Rotate back to Z basis
            ret_list = ret_list + string_basis_2

            for gate in ret_list:
                yield gate
 def number_of_qubits(self):
     """Returns number of qubits used for the ansatz circuit."""
     return count_qubits(
         change_operator_type(self._cost_hamiltonian, QubitOperator))