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