def compose( self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False ) -> OperatorBase: new_self, other = self._expand_shorter_operator_and_permute(other, permutation) new_self = cast(PauliOp, new_self) if front: return other.compose(new_self) # If self is identity, just return other. if not any(new_self.primitive.x + new_self.primitive.z): return other * new_self.coeff # Both Paulis if isinstance(other, PauliOp): product = new_self.primitive.dot(other.primitive) return PrimitiveOp(product, coeff=new_self.coeff * other.coeff) # pylint: disable=cyclic-import from .pauli_sum_op import PauliSumOp if isinstance(other, PauliSumOp): return PauliSumOp( SparsePauliOp(new_self.primitive).dot(other.primitive), coeff=new_self.coeff * other.coeff, ) # pylint: disable=cyclic-import from ..state_fns.circuit_state_fn import CircuitStateFn from .circuit_op import CircuitOp if isinstance(other, (CircuitOp, CircuitStateFn)): return new_self.to_circuit_op().compose(other) return super(PauliOp, new_self).compose(other)
def statefn_replacement_fn( cob_instr_op: PrimitiveOp, dest_pauli_op: Union[PauliOp, PauliSumOp, ListOp]) -> OperatorBase: r""" A built-in convenience replacement function which produces state functions isomorphic to an ``OperatorStateFn`` state function holding the origin ``PauliOp``. Args: cob_instr_op: The basis-change ``CircuitOp``. dest_pauli_op: The destination Pauli type operator. Returns: The ``~CircuitOp @ StateFn`` composition equivalent to a state function defined by the original ``PauliOp``. """ return ComposedOp([cob_instr_op.adjoint(), StateFn(dest_pauli_op)])
def operator_replacement_fn( cob_instr_op: PrimitiveOp, dest_pauli_op: Union[PauliOp, PauliSumOp, ListOp] ) -> OperatorBase: r""" A built-in convenience replacement function which produces Operators isomorphic to the origin ``PauliOp``. Args: cob_instr_op: The basis-change ``CircuitOp``. dest_pauli_op: The destination ``PauliOp``. Returns: The ``~CircuitOp @ PauliOp @ CircuitOp`` composition isomorphic to the original ``PauliOp``. """ return ComposedOp([cob_instr_op.adjoint(), dest_pauli_op, cob_instr_op])
def construct_cnot_chain(self, diag_pauli_op1: PauliOp, diag_pauli_op2: PauliOp) -> PrimitiveOp: r""" Construct a ``CircuitOp`` (or ``PauliOp`` if equal to the identity) which takes the eigenvectors of ``diag_pauli_op1`` to the eigenvectors of ``diag_pauli_op2``, assuming both are diagonal (or performing this operation on their diagonalized Paulis implicitly if not). This works by the insight that the eigenvalue of a diagonal Pauli's eigenvector is equal to or -1 if the parity is 1 and 1 if the parity is 0, or 1 - (2 * parity). Therefore, using CNOTs, we can write the parity of diag_pauli_op1's significant bits onto some qubit, and then write out that parity onto diag_pauli_op2's significant bits. Args: diag_pauli_op1: The origin ``PauliOp``. diag_pauli_op2: The destination ``PauliOp``. Return: The ``PrimitiveOp`` performs the mapping. """ # TODO be smarter about connectivity and actual distance between pauli and destination # TODO be smarter in general pauli_1 = (diag_pauli_op1.primitive if isinstance( diag_pauli_op1, PauliOp) else diag_pauli_op1) pauli_2 = (diag_pauli_op2.primitive if isinstance( diag_pauli_op2, PauliOp) else diag_pauli_op2) origin_sig_bits = np.logical_or(pauli_1.z, pauli_1.x) destination_sig_bits = np.logical_or(pauli_2.z, pauli_2.x) num_qubits = max(len(pauli_1.z), len(pauli_2.z)) sig_equal_sig_bits = np.logical_and(origin_sig_bits, destination_sig_bits) non_equal_sig_bits = np.logical_not( origin_sig_bits == destination_sig_bits) # Equivalent to np.logical_xor(origin_sig_bits, destination_sig_bits) if not any(non_equal_sig_bits): return I ^ num_qubits # I am deeply sorry for this code, but I don't know another way to do it. sig_in_origin_only_indices = np.extract( np.logical_and(non_equal_sig_bits, origin_sig_bits), np.arange(num_qubits)) sig_in_dest_only_indices = np.extract( np.logical_and(non_equal_sig_bits, destination_sig_bits), np.arange(num_qubits)) if len(sig_in_origin_only_indices) > 0 and len( sig_in_dest_only_indices) > 0: origin_anchor_bit = min(sig_in_origin_only_indices) dest_anchor_bit = min(sig_in_dest_only_indices) else: # Set to lowest equal bit origin_anchor_bit = min( np.extract(sig_equal_sig_bits, np.arange(num_qubits))) dest_anchor_bit = origin_anchor_bit cnots = QuantumCircuit(num_qubits) # Step 3) Take the indices of bits which are sig_bits in # pauli but but not in dest, and cnot them to the pauli anchor. for i in sig_in_origin_only_indices: if not i == origin_anchor_bit: cnots.cx(i, origin_anchor_bit) # Step 4) if not origin_anchor_bit == dest_anchor_bit: cnots.swap(origin_anchor_bit, dest_anchor_bit) # Need to do this or a Terra bug sometimes flips cnots. No time to investigate. cnots.i(0) # Step 6) for i in sig_in_dest_only_indices: if not i == dest_anchor_bit: cnots.cx(i, dest_anchor_bit) return PrimitiveOp(cnots)