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 convert(self, operator: OperatorBase) -> OperatorBase: """Check if operator is a SummedOp, in which case covert it into a sum of mutually commuting sums, or if the Operator contains sub-Operators and ``traverse`` is True, attempt to convert any sub-Operators. Args: operator: The Operator to attempt to convert. Returns: The converted Operator. """ if isinstance(operator, PauliSumOp): return self.group_subops(operator) if isinstance(operator, ListOp): if isinstance(operator, SummedOp) and all( isinstance(op, PauliOp) for op in operator.oplist): # For now, we only support graphs over Paulis. return self.group_subops(operator) elif self._traverse: return operator.traverse(self.convert) elif isinstance(operator, OperatorStateFn) and self._traverse: return OperatorStateFn( self.convert(operator.primitive), is_measurement=operator.is_measurement, coeff=operator.coeff, ) elif isinstance(operator, EvolvedOp) and self._traverse: return EvolvedOp(self.convert(operator.primitive), coeff=operator.coeff) return operator
def _check_is_diagonal(operator: OperatorBase) -> bool: """Check whether ``operator`` is diagonal. Args: operator: The operator to check for diagonality. Returns: True, if the operator is diagonal, False otherwise. Raises: OpflowError: If the operator is not diagonal. """ if isinstance(operator, PauliOp): # every X component must be False return not np.any(operator.primitive.x) # For sums (PauliSumOp and SummedOp), we cover the case of sums of diagonal paulis, but don't # raise since there might be summand canceling the non-diagonal parts. That case is checked # in the inefficient matrix check at the bottom. if isinstance(operator, PauliSumOp): if not np.any(operator.primitive.paulis.x): return True elif isinstance(operator, SummedOp): if all( isinstance(op, PauliOp) and not np.any(op.primitive.x) for op in operator.oplist): return True elif isinstance(operator, ListOp): return all(operator.traverse(_check_is_diagonal)) # cannot efficiently check if a operator is diagonal, converting to matrix matrix = operator.to_matrix() return np.all(matrix == np.diag(np.diagonal(matrix)))
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: """Accept an Operator and return a new Operator with the Pauli measurements replaced by Matrix based measurements. Args: operator: The operator to convert. Returns: The converted operator. """ if isinstance(operator, OperatorStateFn) and operator.is_measurement: return operator.to_matrix_op() elif isinstance(operator, ListOp): return operator.traverse(self.convert) else: return operator
def convert(self, operator: OperatorBase) -> OperatorBase: """Accept an Operator and return a new Operator with the Pauli measurements replaced by AerSnapshot-based expectation circuits. Args: operator: The operator to convert. Returns: The converted operator. """ if isinstance(operator, OperatorStateFn) and operator.is_measurement: return self._replace_pauli_sums(operator.primitive) * operator.coeff elif isinstance(operator, ListOp): return operator.traverse(self.convert) else: 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 _check_is_diagonal(operator: OperatorBase) -> bool: """Check whether ``operator`` is diagonal. Args: operator: The operator to check for diagonality. Returns: True, if the operator is diagonal, False otherwise. Raises: OpflowError: If the operator is not diagonal. """ if isinstance(operator, PauliOp): # every X component must be False if not np.any(operator.primitive.x): return True return False if isinstance(operator, SummedOp): # cover the case of sums of diagonal paulis, but don't raise since there might be summands # canceling the non-diagonal parts # ignoring mypy since we know that all operators are PauliOps if all( isinstance(op, PauliOp) and not np.any(op.primitive.x) for op in operator.oplist): return True if isinstance(operator, ListOp): return all(operator.traverse(_check_is_diagonal)) # cannot efficiently check if a operator is diagonal, converting to matrix matrix = operator.to_matrix() if np.all(matrix == np.diag(np.diagonal(matrix))): return True return False
def convert(self, operator: OperatorBase) -> OperatorBase: """Accept an Operator and return a new Operator with the Pauli measurements replaced by AerSnapshot-based expectation circuits. Args: operator: The operator to convert. If it contains non-hermitian terms, the operator is decomposed into hermitian and anti-hermitian parts. Returns: The converted operator. """ if isinstance(operator, OperatorStateFn) and operator.is_measurement: if isinstance(operator.primitive, ListOp): is_herm = all( (op.is_hermitian() for op in operator.primitive.oplist)) else: is_herm = operator.primitive.is_hermitian() if not is_herm: pauli_sum_re = (self._replace_pauli_sums( 1 / 2 * (operator.primitive + operator.primitive.adjoint()).reduce()) * operator.coeff) pauli_sum_im = (self._replace_pauli_sums( 1 / 2j * (operator.primitive - operator.primitive.adjoint()).reduce()) * operator.coeff) pauli_sum = (pauli_sum_re + 1j * pauli_sum_im).reduce() else: pauli_sum = self._replace_pauli_sums( operator.primitive) * operator.coeff return pauli_sum elif isinstance(operator, ListOp): return operator.traverse(self.convert) else: 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