def pad_paulis_to_equal_length( self, pauli_op1: PauliOp, pauli_op2: PauliOp) -> Tuple[PauliOp, PauliOp]: r""" If ``pauli_op1`` and ``pauli_op2`` do not act over the same number of qubits, pad identities to the end of the shorter of the two so they are of equal length. Padding is applied to the end of the Paulis. Note that the Terra represents Paulis in big-endian order, so this will appear as padding to the beginning of the Pauli x and z bit arrays. Args: pauli_op1: A pauli_op to possibly pad. pauli_op2: A pauli_op to possibly pad. Returns: A tuple containing the padded PauliOps. """ num_qubits = max(pauli_op1.num_qubits, pauli_op2.num_qubits) pauli_1, pauli_2 = pauli_op1.primitive, pauli_op2.primitive # Padding to the end of the Pauli, but remember that Paulis are in reverse endianness. if not len(pauli_1.z) == num_qubits: missing_qubits = num_qubits - len(pauli_1.z) pauli_1 = Pauli((([False] * missing_qubits) + pauli_1.z.tolist(), ([False] * missing_qubits) + pauli_1.x.tolist())) if not len(pauli_2.z) == num_qubits: missing_qubits = num_qubits - len(pauli_2.z) pauli_2 = Pauli((([False] * missing_qubits) + pauli_2.z.tolist(), ([False] * missing_qubits) + pauli_2.x.tolist())) return PauliOp(pauli_1, coeff=pauli_op1.coeff), PauliOp(pauli_2, coeff=pauli_op2.coeff)
def cliffords(self) -> List[PauliSumOp]: """ Get clifford operators, build based on symmetries and single-qubit X. Returns: a list of unitaries used to diagonalize the Hamiltonian. """ cliffords = [ (PauliOp(pauli_symm) + PauliOp(sq_pauli)) / np.sqrt(2) for pauli_symm, sq_pauli in zip(self._symmetries, self._sq_paulis) ] return cliffords
def to_pauli_op(self, massive: bool = False) -> Union[PauliOp, SummedOp]: def to_native(x): return x.item() if isinstance(x, np.generic) else x if len(self.primitive) == 1: return PauliOp( Pauli( (self.primitive.paulis.z[0], self.primitive.paulis.x[0])), to_native(np.real_if_close(self.primitive.coeffs[0])) * self.coeff, ) coeffs = np.real_if_close(self.primitive.coeffs) return SummedOp( [ PauliOp(pauli, to_native(coeff)) for pauli, coeff in zip(self.primitive.paulis, coeffs) ], coeff=self.coeff, )
def to_pauli_op(self, massive: bool = False) -> Union[PauliOp, SummedOp]: def to_native(x): return x.item() if isinstance(x, np.generic) else x if len(self.primitive) == 1: return PauliOp( Pauli((self.primitive.table.Z[0], self.primitive.table.X[0])), to_native(np.real_if_close(self.primitive.coeffs[0])) * self.coeff, ) tables = self.primitive.table coeffs = np.real_if_close(self.primitive.coeffs) return SummedOp( [ PauliOp( Pauli((t.Z[0], t.X[0])), to_native(c), ) for t, c in zip(tables, coeffs) ], coeff=self.coeff, )
def destination(self, dest: Union[Pauli, PauliOp]) -> None: r""" The destination ``PauliOp``, or ``None`` if using the default destination, the diagonal basis. """ if isinstance(dest, Pauli): dest = PauliOp(dest) if not isinstance(dest, PauliOp): raise TypeError( "PauliBasisChange can only convert into Pauli bases, " "not {}.".format(type(dest))) self._destination = dest
def get_diagonal_pauli_op(self, pauli_op: PauliOp) -> PauliOp: """ Get the diagonal ``PualiOp`` to which ``pauli_op`` could be rotated with only single-qubit operations. Args: pauli_op: The ``PauliOp`` whose diagonal to compute. Returns: The diagonal ``PauliOp``. """ return PauliOp(Pauli((np.logical_or(pauli_op.primitive.z, pauli_op.primitive.x), [False] * pauli_op.num_qubits)), coeff=pauli_op.coeff)
def consistent_tapering(self, operator: PauliSumOp) -> OperatorBase: """ Tapering the `operator` with the same manner of how this tapered operator is created. i.e., using the same Cliffords and tapering values. Args: operator: the to-be-tapered operator Returns: The tapered operator Raises: OpflowError: The given operator does not commute with the symmetry """ for symmetry in self._symmetries: commutator_op = cast(PauliSumOp, commutator(operator, PauliOp(symmetry))) if not commutator_op.is_zero(): raise OpflowError( "The given operator does not commute with " "the symmetry, can not taper it." ) return self.taper(operator)
# Immutable convenience objects def make_immutable(obj): """Delete the __setattr__ property to make the object mostly immutable.""" # TODO figure out how to get correct error message # def throw_immutability_exception(self, *args): # raise OpflowError('Operator convenience globals are immutable.') obj.__setattr__ = None return obj # 1-Qubit Paulis X = make_immutable(PauliOp(Pauli("X"))) Y = make_immutable(PauliOp(Pauli("Y"))) Z = make_immutable(PauliOp(Pauli("Z"))) I = make_immutable(PauliOp(Pauli("I"))) # Clifford+T, and some other common non-parameterized gates CX = make_immutable(CircuitOp(CXGate())) S = make_immutable(CircuitOp(SGate())) H = make_immutable(CircuitOp(HGate())) T = make_immutable(CircuitOp(TGate())) Swap = make_immutable(CircuitOp(SwapGate())) CZ = make_immutable(CircuitOp(CZGate())) # 1-Qubit Paulis Zero = make_immutable(DictStateFn("0")) One = make_immutable(DictStateFn("1"))
def get_cob_circuit( self, origin: Union[Pauli, PauliOp]) -> Tuple[PrimitiveOp, PauliOp]: r""" Construct an Operator which maps the +1 and -1 eigenvectors of the origin Pauli to the +1 and -1 eigenvectors of the destination Pauli. It does so by 1) converting any \|i+⟩ or \|i+⟩ eigenvector bits in the origin to \|+⟩ and \|-⟩ with S†s, then 2) converting any \|+⟩ or \|+⟩ eigenvector bits in the converted origin to \|0⟩ and \|1⟩ with Hs, then 3) writing the parity of the significant (Z-measured, rather than I) bits in the origin to a single "origin anchor bit," using cnots, which will hold the parity of these bits, 4) swapping the parity of the pauli anchor bit into a destination anchor bit using a swap gate (only if they are different, if there are any bits which are significant in both origin and dest, we set both anchors to one of these bits to avoid a swap). 5) writing the parity of the destination anchor bit into the other significant bits of the destination, 6) converting the \|0⟩ and \|1⟩ significant eigenvector bits to \|+⟩ and \|-⟩ eigenvector bits in the destination where the destination demands it (e.g. pauli.x == true for a bit), using Hs 8) converting the \|+⟩ and \|-⟩ significant eigenvector bits to \|i+⟩ and \|i-⟩ eigenvector bits in the destination where the destination demands it (e.g. pauli.x == true and pauli.z == true for a bit), using Ss Args: origin: The ``Pauli`` or ``PauliOp`` to map. Returns: A tuple of a ``PrimitiveOp`` which equals the basis change mapping and a ``PauliOp`` which equals the destination basis. Raises: TypeError: Attempting to convert from non-Pauli origin. ValueError: Attempting to change a non-identity Pauli to an identity Pauli, or vice versa. """ # If pauli is an PrimitiveOp, extract the Pauli if isinstance(origin, Pauli): origin = PauliOp(origin) if not isinstance(origin, PauliOp): raise TypeError( f"PauliBasisChange can only convert Pauli-based OpPrimitives, not {type(origin)}" ) # If no destination specified, assume nearest Pauli in {Z,I}^n basis, # the standard basis change for expectations. destination = self.destination or self.get_diagonal_pauli_op(origin) # Pad origin or destination if either are not as long as the other origin, destination = self.pad_paulis_to_equal_length( origin, destination) origin_sig_bits = np.logical_or(origin.primitive.x, origin.primitive.z) destination_sig_bits = np.logical_or(destination.primitive.x, destination.primitive.z) if not any(origin_sig_bits) or not any(destination_sig_bits): if not (any(origin_sig_bits) or any(destination_sig_bits)): # Both all Identity, just return Identities return I ^ origin.num_qubits, destination else: # One is Identity, one is not raise ValueError( "Cannot change to or from a fully Identity Pauli.") # Steps 1 and 2 cob_instruction = self.get_diagonalizing_clifford(origin) # Construct CNOT chain, assuming full connectivity... - Steps 3)-5) cob_instruction = self.construct_cnot_chain( origin, destination).compose(cob_instruction) # Step 6 and 7 dest_diagonlizing_clifford = self.get_diagonalizing_clifford( destination).adjoint() cob_instruction = dest_diagonlizing_clifford.compose(cob_instruction) return cast(PrimitiveOp, cob_instruction), destination
# Immutable convenience objects def make_immutable(obj): """ Delete the __setattr__ property to make the object mostly immutable. """ # TODO figure out how to get correct error message # def throw_immutability_exception(self, *args): # raise OpflowError('Operator convenience globals are immutable.') obj.__setattr__ = None return obj # 1-Qubit Paulis X = make_immutable(PauliOp(Pauli('X'))) Y = make_immutable(PauliOp(Pauli('Y'))) Z = make_immutable(PauliOp(Pauli('Z'))) I = make_immutable(PauliOp(Pauli('I'))) # Clifford+T, and some other common non-parameterized gates CX = make_immutable(CircuitOp(CXGate())) S = make_immutable(CircuitOp(SGate())) H = make_immutable(CircuitOp(HGate())) T = make_immutable(CircuitOp(TGate())) Swap = make_immutable(CircuitOp(SwapGate())) CZ = make_immutable(CircuitOp(CZGate())) # 1-Qubit Paulis Zero = make_immutable(DictStateFn('0')) One = make_immutable(DictStateFn('1'))