Example #1
0
    def _recursive_convert(self, operator: OperatorBase) -> OperatorBase:
        if isinstance(operator, EvolvedOp):
            if isinstance(operator.primitive, (PauliOp, PauliSumOp)):
                pauli = operator.primitive.primitive
                time = operator.coeff * operator.primitive.coeff
                evo = PauliEvolutionGate(
                    pauli,
                    time=time,
                    synthesis=self._get_evolution_synthesis())
                return CircuitOp(evo.definition)
                # operator = EvolvedOp(operator.primitive.to_pauli_op(), coeff=operator.coeff)
            if not {"Pauli"} == operator.primitive_strings():
                logger.warning(
                    "Evolved Hamiltonian is not composed of only Paulis, converting to "
                    "Pauli representation, which can be expensive.")
                # Setting massive=False because this conversion is implicit. User can perform this
                # action on the Hamiltonian with massive=True explicitly if they so choose.
                # TODO explore performance to see whether we should avoid doing this repeatedly
                pauli_ham = operator.primitive.to_pauli_op(massive=False)
                operator = EvolvedOp(pauli_ham, coeff=operator.coeff)

            if isinstance(operator.primitive, SummedOp):
                # TODO uncomment when we implement Abelian grouped evolution.
                # if operator.primitive.abelian:
                #     return self.evolution_for_abelian_paulisum(operator.primitive)
                # else:
                # Collect terms that are not the identity.
                oplist = [
                    x for x in operator.primitive
                    if not isinstance(x, PauliOp) or sum(x.primitive.x +
                                                         x.primitive.z) != 0
                ]
                # Collect the coefficients of any identity terms,
                # which become global phases when exponentiated.
                identity_phases = [
                    x.coeff for x in operator.primitive
                    if isinstance(x, PauliOp) and sum(x.primitive.x +
                                                      x.primitive.z) == 0
                ]
                # Construct sum without the identity operators.
                new_primitive = SummedOp(oplist,
                                         coeff=operator.primitive.coeff)
                trotterized = self.trotter.convert(new_primitive)
                circuit_no_identities = self._recursive_convert(trotterized)
                # Set the global phase of the QuantumCircuit to account for removed identity terms.
                global_phase = -sum(identity_phases) * operator.primitive.coeff
                circuit_no_identities.primitive.global_phase = global_phase
                return circuit_no_identities
            # Covers ListOp, ComposedOp, TensoredOp
            elif isinstance(operator.primitive, ListOp):
                converted_ops = [
                    self._recursive_convert(op)
                    for op in operator.primitive.oplist
                ]
                return operator.primitive.__class__(converted_ops,
                                                    coeff=operator.coeff)
        elif isinstance(operator, ListOp):
            return operator.traverse(self.convert).reduce()

        return operator
    def build(operator: OperatorBase = None) -> EvolutionBase:
        r"""
        A factory method for convenient automatic selection of an Evolution algorithm based on the
        Operator to be converted.

        Args:
            operator: the Operator being evolved

        Returns:
            EvolutionBase: the ``EvolutionBase`` best suited to evolve operator.

        Raises:
            ValueError: If operator is not of a composition for which we know the best Evolution
                method.

        """
        primitive_strings = operator.primitive_strings()
        if "Matrix" in primitive_strings:
            return MatrixEvolution()

        elif "Pauli" in primitive_strings or "SparsePauliOp" in primitive_strings:
            # TODO figure out what to do based on qubits and hamming weight.
            return PauliTrotterEvolution()

        else:
            raise ValueError("Evolutions of mixed Operators not yet supported.")
    def convert(self, operator: OperatorBase) -> OperatorBase:
        r"""
        Traverse the operator, replacing ``EvolvedOps`` with ``CircuitOps`` containing
        ``UnitaryGates`` or ``HamiltonianGates`` (if self.coeff is a ``ParameterExpression``)
        equalling the exponentiation of -i * operator. This is done by converting the
        ``EvolvedOp.primitive`` to a ``MatrixOp`` and simply calling ``.exp_i()`` on that.

        Args:
            operator: The Operator to convert.

        Returns:
            The converted operator.
        """
        if isinstance(operator, EvolvedOp):
            if not {"Matrix"} == operator.primitive_strings():
                logger.warning(
                    "Evolved Hamiltonian is not composed of only MatrixOps, converting "
                    "to Matrix representation, which can be expensive.")
                # Setting massive=False because this conversion is implicit. User can perform this
                # action on the Hamiltonian with massive=True explicitly if they so choose.
                # TODO explore performance to see whether we should avoid doing this repeatedly
                matrix_ham = operator.primitive.to_matrix_op(massive=False)
                operator = EvolvedOp(matrix_ham, coeff=operator.coeff)

            if isinstance(operator.primitive, ListOp):
                return operator.primitive.exp_i() * operator.coeff
            elif isinstance(operator.primitive, (MatrixOp, PauliOp)):
                return operator.primitive.exp_i()
        elif isinstance(operator, ListOp):
            return operator.traverse(self.convert).reduce()

        return operator
Example #4
0
    def convert(self, operator: OperatorBase) -> OperatorBase:
        """Convert the Operator to ``CircuitStateFns``, recursively if ``traverse`` is True.

        Args:
            operator: The Operator to convert

        Returns:
            The converted Operator.
        """

        if isinstance(operator, DictStateFn) and self._convert_dicts:
            return CircuitStateFn.from_dict(operator.primitive)
        if isinstance(operator, VectorStateFn) and self._convert_vectors:
            return CircuitStateFn.from_vector(operator.to_matrix(massive=True))
        elif isinstance(operator,
                        ListOp) and "Dict" in operator.primitive_strings():
            return operator.traverse(self.convert)
        else:
            return operator
Example #5
0
    def convert(self, operator: OperatorBase) -> OperatorBase:
        """Accepts an Operator and returns a new Operator with the Pauli measurements replaced by
        diagonal Pauli post-rotation based measurements so they can be evaluated by sampling and
        averaging.

        Args:
            operator: The operator to convert.

        Returns:
            The converted operator.
        """
        if isinstance(operator, ListOp):
            return operator.traverse(self.convert).reduce()

        if isinstance(operator, OperatorStateFn) and operator.is_measurement:
            # Change to Pauli representation if necessary
            if (isinstance(operator.primitive, (ListOp, PrimitiveOp))
                    and not isinstance(operator.primitive, PauliSumOp) and
                {"Pauli", "SparsePauliOp"} < operator.primitive_strings()):
                logger.warning(
                    "Measured Observable is not composed of only Paulis, converting to "
                    "Pauli representation, which can be expensive.")
                # Setting massive=False because this conversion is implicit. User can perform this
                # action on the Observable with massive=True explicitly if they so choose.
                pauli_obsv = operator.primitive.to_pauli_op(massive=False)
                operator = StateFn(pauli_obsv,
                                   is_measurement=True,
                                   coeff=operator.coeff)

            if self._grouper and isinstance(operator.primitive,
                                            (ListOp, PauliSumOp)):
                grouped = self._grouper.convert(operator.primitive)
                operator = StateFn(grouped,
                                   is_measurement=True,
                                   coeff=operator.coeff)

            # Convert the measurement into diagonal basis (PauliBasisChange chooses
            # this basis by default).
            cob = PauliBasisChange(
                replacement_fn=PauliBasisChange.measurement_replacement_fn)
            return cob.convert(operator).reduce()

        return operator
    def convert(self, operator: OperatorBase) -> OperatorBase:
        r"""
        Given a ``PauliOp``, or an Operator containing ``PauliOps`` if ``_traverse`` is True,
        converts each Pauli into the basis specified by self._destination and a
        basis-change-circuit, calls ``replacement_fn`` with these two Operators, and replaces
        the ``PauliOps`` with the output of ``replacement_fn``. For example, for the built-in
        ``operator_replacement_fn`` below, each PauliOp p will be replaced by the composition
        of the basis-change Clifford ``CircuitOp`` c with the destination PauliOp d and c†,
        such that p = c·d·c†, up to global phase.

        Args:
            operator: The Operator to convert.

        Returns:
            The converted Operator.

        """
        if (isinstance(operator, OperatorStateFn)
                and isinstance(operator.primitive, PauliSumOp)
                and operator.primitive.grouping_type == "TPB"):
            primitive = operator.primitive.primitive.copy()
            origin_x = reduce(np.logical_or, primitive.table.X)
            origin_z = reduce(np.logical_or, primitive.table.Z)
            origin_pauli = Pauli((origin_z, origin_x))
            cob_instr_op, _ = self.get_cob_circuit(origin_pauli)
            primitive.table.Z = np.logical_or(primitive.table.X,
                                              primitive.table.Z)
            primitive.table.X = False
            dest_pauli_sum_op = PauliSumOp(primitive,
                                           coeff=operator.coeff,
                                           grouping_type="TPB")
            return self._replacement_fn(cob_instr_op, dest_pauli_sum_op)

        if (isinstance(operator, OperatorStateFn)
                and isinstance(operator.primitive, SummedOp) and all(
                    isinstance(op, PauliSumOp) and op.grouping_type == "TPB"
                    for op in operator.primitive.oplist)):
            sf_list: List[OperatorBase] = [
                StateFn(op, is_measurement=operator.is_measurement)
                for op in operator.primitive.oplist
            ]
            listop_of_statefns = SummedOp(oplist=sf_list, coeff=operator.coeff)
            return listop_of_statefns.traverse(self.convert)

        if isinstance(operator, OperatorStateFn) and isinstance(
                operator.primitive, PauliSumOp):
            operator = OperatorStateFn(
                operator.primitive.to_pauli_op(),
                coeff=operator.coeff,
                is_measurement=operator.is_measurement,
            )

        if isinstance(operator, PauliSumOp):
            operator = operator.to_pauli_op()

        if isinstance(operator, (Pauli, PauliOp)):
            cob_instr_op, dest_pauli_op = self.get_cob_circuit(operator)
            return self._replacement_fn(cob_instr_op, dest_pauli_op)
        if isinstance(operator,
                      StateFn) and "Pauli" in operator.primitive_strings():
            # If the StateFn/Meas only contains a Pauli, use it directly.
            if isinstance(operator.primitive, PauliOp):
                cob_instr_op, dest_pauli_op = self.get_cob_circuit(
                    operator.primitive)
                return self._replacement_fn(cob_instr_op,
                                            dest_pauli_op * operator.coeff)
            # TODO make a canonical "distribute" or graph swap as method in ListOp?
            elif operator.primitive.distributive:
                if operator.primitive.abelian:
                    origin_pauli = self.get_tpb_pauli(operator.primitive)
                    cob_instr_op, _ = self.get_cob_circuit(origin_pauli)
                    diag_ops: List[OperatorBase] = [
                        self.get_diagonal_pauli_op(op)
                        for op in operator.primitive.oplist
                    ]
                    dest_pauli_op = operator.primitive.__class__(
                        diag_ops, coeff=operator.coeff, abelian=True)
                    return self._replacement_fn(cob_instr_op, dest_pauli_op)
                else:
                    sf_list = [
                        StateFn(op, is_measurement=operator.is_measurement)
                        for op in operator.primitive.oplist
                    ]
                    listop_of_statefns = operator.primitive.__class__(
                        oplist=sf_list, coeff=operator.coeff)
                    return listop_of_statefns.traverse(self.convert)

        elif (isinstance(operator, ListOp) and self._traverse
              and "Pauli" in operator.primitive_strings()):
            # If ListOp is abelian we can find a single post-rotation circuit
            # for the whole set. For now,
            # assume operator can only be abelian if all elements are
            # Paulis (enforced in AbelianGrouper).
            if operator.abelian:
                origin_pauli = self.get_tpb_pauli(operator)
                cob_instr_op, _ = self.get_cob_circuit(origin_pauli)
                oplist = cast(List[PauliOp], operator.oplist)
                diag_ops = [self.get_diagonal_pauli_op(op) for op in oplist]
                dest_list_op = operator.__class__(diag_ops,
                                                  coeff=operator.coeff,
                                                  abelian=True)
                return self._replacement_fn(cob_instr_op, dest_list_op)
            else:
                return operator.traverse(self.convert)

        return operator
    def build(operator: OperatorBase,
              backend: Optional[Union[Backend, BaseBackend, QuantumInstance]] = None,
              include_custom: bool = True) -> ExpectationBase:
        """
        A factory method for convenient automatic selection of an Expectation based on the
        Operator to be converted and backend used to sample the expectation value.

        Args:
            operator: The Operator whose expectation value will be taken.
            backend: The backend which will be used to sample the expectation value.
            include_custom: Whether the factory will include the (Aer) specific custom
                expectations if their behavior against the backend might not be as expected.
                For instance when using Aer qasm_simulator with paulis the Aer snapshot can
                be used but the outcome lacks shot noise and hence does not intuitively behave
                overall as people might expect when choosing a qasm_simulator. It is however
                fast as long as the more state vector like behavior is acceptable.

        Returns:
            The expectation algorithm which best fits the Operator and backend.

        Raises:
            ValueError: If operator is not of a composition for which we know the best Expectation
                method.
        """
        backend_to_check = backend.backend if isinstance(backend, QuantumInstance) else backend

        # pylint: disable=cyclic-import
        primitives = operator.primitive_strings()
        if primitives in ({'Pauli'}, {'SparsePauliOp'}):

            if backend_to_check is None:
                # If user has Aer but didn't specify a backend, use the Aer fast expectation
                if has_aer():
                    from qiskit import Aer
                    backend_to_check = Aer.get_backend('qasm_simulator')
                # If user doesn't have Aer, use statevector_simulator
                # for < 16 qubits, and qasm with warning for more.
                else:
                    if operator.num_qubits <= 16:
                        backend_to_check = BasicAer.get_backend('statevector_simulator')
                    else:
                        logger.warning(
                            '%d qubits is a very large expectation value. '
                            'Consider installing Aer to use '
                            'Aer\'s fast expectation, which will perform better here. We\'ll use '
                            'the BasicAer qasm backend for this expectation to avoid having to '
                            'construct the %dx%d operator matrix.',
                            operator.num_qubits,
                            2 ** operator.num_qubits,
                            2 ** operator.num_qubits)
                        backend_to_check = BasicAer.get_backend('qasm_simulator')

            # If the user specified Aer qasm backend and is using a
            # Pauli operator, use the Aer fast expectation if we are including such
            # custom behaviors.
            if is_aer_qasm(backend_to_check) and include_custom:
                return AerPauliExpectation()

            # If the user specified a statevector backend (either Aer or BasicAer),
            # use a converter to produce a
            # Matrix operator and compute using matmul
            elif is_statevector_backend(backend_to_check):
                if operator.num_qubits >= 16:
                    logger.warning(
                        'Note: Using a statevector_simulator with %d qubits can be very expensive. '
                        'Consider using the Aer qasm_simulator instead to take advantage of Aer\'s '
                        'built-in fast Pauli Expectation', operator.num_qubits)
                return MatrixExpectation()

            # All other backends, including IBMQ, BasicAer QASM, go here.
            else:
                return PauliExpectation()

        elif primitives == {'Matrix'}:
            return MatrixExpectation()

        else:
            raise ValueError('Expectations of Mixed Operators not yet supported.')