def __init__(self, data, coeffs=None): """Initialize an operator object. Args: data (Paulilist, SparsePauliOp, PauliTable): Pauli list of terms. coeffs (np.ndarray): complex coefficients for Pauli terms. Raises: QiskitError: If the input data or coeffs are invalid. """ if isinstance(data, SparsePauliOp): pauli_list = data._pauli_list coeffs = data._coeffs else: pauli_list = PauliList(data) if coeffs is None: coeffs = np.ones(pauli_list.size, dtype=complex) # Initialize PauliList self._pauli_list = PauliList.from_symplectic(pauli_list.z, pauli_list.x) # Initialize Coeffs self._coeffs = np.asarray((-1j) ** pauli_list.phase * coeffs, dtype=complex) if self._coeffs.shape != (self._pauli_list.size,): raise QiskitError( "coeff vector is incorrect shape for number" " of Paulis {} != {}".format(self._coeffs.shape, self._pauli_list.size) ) # Initialize LinearOp super().__init__(num_qubits=self._pauli_list.num_qubits)
def simplify(self, atol=None, rtol=None): """Simplify PauliList by combining duplicates and removing zeros. Args: atol (float): Optional. Absolute tolerance for checking if coefficients are zero (Default: 1e-8). rtol (float): Optional. relative tolerance for checking if coefficients are zero (Default: 1e-5). Returns: SparsePauliOp: the simplified SparsePauliOp operator. """ # Get default atol and rtol if atol is None: atol = self.atol if rtol is None: rtol = self.rtol # Filter non-zero coefficients non_zero = np.logical_not( np.isclose(self.coeffs, 0, atol=atol, rtol=rtol)) paulis_x = self.paulis.x[non_zero] paulis_z = self.paulis.z[non_zero] nz_coeffs = self.coeffs[non_zero] # Pack bool vectors into np.uint8 vectors by np.packbits array = np.packbits(paulis_x, axis=1) * 256 + np.packbits(paulis_z, axis=1) indexes, inverses = unordered_unique(array) if np.all(non_zero) and indexes.shape[0] == array.shape[0]: # No zero operator or duplicate operator return self.copy() coeffs = np.zeros(indexes.shape[0], dtype=complex) np.add.at(coeffs, inverses, nz_coeffs) # Delete zero coefficient rows is_zero = np.isclose(coeffs, 0, atol=atol, rtol=rtol) # Check edge case that we deleted all Paulis # In this case we return an identity Pauli with a zero coefficient if np.all(is_zero): x = np.zeros((1, self.num_qubits), dtype=bool) z = np.zeros((1, self.num_qubits), dtype=bool) coeffs = np.array([0j], dtype=complex) else: non_zero = np.logical_not(is_zero) non_zero_indexes = indexes[non_zero] x = paulis_x[non_zero_indexes] z = paulis_z[non_zero_indexes] coeffs = coeffs[non_zero] return SparsePauliOp(PauliList.from_symplectic(z, x), coeffs, ignore_pauli_phase=True, copy=False)
def compose(self, other, qargs=None, front=False): if qargs is None: qargs = getattr(other, "qargs", None) if not isinstance(other, SparsePauliOp): other = SparsePauliOp(other) # Validate composition dimensions and qargs match self._op_shape.compose(other._op_shape, qargs, front) x1 = np.reshape( np.stack(other.size * [self.paulis.x], axis=1), (self.size * other.size, self.num_qubits), ) z1 = np.reshape( np.stack(other.size * [self.paulis.z], axis=1), (self.size * other.size, self.num_qubits), ) p1 = np.reshape( np.stack(other.size * [self.paulis.phase], axis=1), self.size * other.size, ) paulis1 = PauliList.from_symplectic(z1, x1, p1) x2 = np.reshape( np.stack(self.size * [other.paulis.x]), (self.size * other.size, other.num_qubits) ) z2 = np.reshape( np.stack(self.size * [other.paulis.z]), (self.size * other.size, other.num_qubits) ) p2 = np.reshape( np.stack(self.size * [other.paulis.phase]), self.size * other.size, ) paulis2 = PauliList.from_symplectic(z2, x2, p2) pauli_list = paulis1.compose(paulis2, qargs, front) coeffs = np.kron(self.coeffs, other.coeffs) return SparsePauliOp(pauli_list, coeffs)
def _multiply(self, other): if not isinstance(other, Number): raise QiskitError("other is not a number") if other == 0: # Check edge case that we deleted all Paulis # In this case we return an identity Pauli with a zero coefficient paulis = PauliList.from_symplectic( np.zeros((1, self.num_qubits), dtype=bool), np.zeros((1, self.num_qubits), dtype=bool), ) coeffs = np.array([0j]) return SparsePauliOp(paulis, coeffs) # Otherwise we just update the phases return SparsePauliOp(self.paulis, other * self.coeffs)
def simplify(self, atol=None, rtol=None): """Simplify PauliList by combining duplicates and removing zeros. Args: atol (float): Optional. Absolute tolerance for checking if coefficients are zero (Default: 1e-8). rtol (float): Optional. relative tolerance for checking if coefficients are zero (Default: 1e-5). Returns: SparsePauliOp: the simplified SparsePauliOp operator. """ # Get default atol and rtol if atol is None: atol = self.atol if rtol is None: rtol = self.rtol array = np.column_stack((self.paulis.x, self.paulis.z)) flatten_paulis, indexes = np.unique(array, return_inverse=True, axis=0) coeffs = np.zeros(self.size, dtype=complex) for i, val in zip(indexes, self.coeffs): coeffs[i] += val # Delete zero coefficient rows # TODO: Add atol/rtol for zero comparison non_zero = [ i for i in range(coeffs.size) if not np.isclose(coeffs[i], 0, atol=atol, rtol=rtol) ] # Check edge case that we deleted all Paulis # In this case we return an identity Pauli with a zero coefficient if len(non_zero) == 0: x = np.zeros((1, self.num_qubits), dtype=bool) z = np.zeros((1, self.num_qubits), dtype=bool) coeffs = np.array([0j], dtype=complex) else: x, z = ( flatten_paulis[non_zero] .reshape((len(non_zero), 2, self.num_qubits)) .transpose(1, 0, 2) ) coeffs = coeffs[non_zero] return SparsePauliOp(PauliList.from_symplectic(z, x), coeffs)
def chop(self, tol=1e-14): """Set real and imaginary parts of the coefficients to 0 if ``< tol`` in magnitude. For example, the operator representing ``1+1e-17j X + 1e-17 Y`` with a tolerance larger than ``1e-17`` will be reduced to ``1 X`` whereas :meth:`.SparsePauliOp.simplify` would return ``1+1e-17j X``. If a both the real and imaginary part of a coefficient is 0 after chopping, the corresponding Pauli is removed from the operator. Args: tol (float): The absolute tolerance to check whether a real or imaginary part should be set to 0. Returns: SparsePauliOp: This operator with chopped coefficients. """ realpart_nonzero = np.abs(self.coeffs.real) > tol imagpart_nonzero = np.abs(self.coeffs.imag) > tol remaining_indices = np.logical_or(realpart_nonzero, imagpart_nonzero) remaining_real = realpart_nonzero[remaining_indices] remaining_imag = imagpart_nonzero[remaining_indices] if len(remaining_real) == 0: # if no Paulis are left x = np.zeros((1, self.num_qubits), dtype=bool) z = np.zeros((1, self.num_qubits), dtype=bool) coeffs = np.array([0j], dtype=complex) else: coeffs = np.zeros(np.count_nonzero(remaining_indices), dtype=complex) coeffs.real[remaining_real] = self.coeffs.real[realpart_nonzero] coeffs.imag[remaining_imag] = self.coeffs.imag[imagpart_nonzero] x = self.paulis.x[remaining_indices] z = self.paulis.z[remaining_indices] return SparsePauliOp(PauliList.from_symplectic(z, x), coeffs, ignore_pauli_phase=True, copy=False)
def _evolve_clifford(self, other, qargs=None, frame="h"): """Heisenberg picture evolution of a Pauli by a Clifford.""" if frame == "s": adj = other else: adj = other.adjoint() if qargs is None: qargs_ = slice(None) else: qargs_ = list(qargs) # pylint: disable=cyclic-import from qiskit.quantum_info.operators.symplectic.pauli_list import PauliList num_paulis = self._x.shape[0] ret = self.copy() ret._x[:, qargs_] = False ret._z[:, qargs_] = False idx = np.concatenate((self._x[:, qargs_], self._z[:, qargs_]), axis=1) for idx_, row in zip( idx.T, PauliList.from_symplectic(z=adj.table.Z, x=adj.table.X, phase=2 * adj.table.phase), ): # most of the logic below is to properly index if self is a PauliList (2D), # while not trying to index if the object is just a Pauli (1D). if idx_.any(): if np.sum(idx_) == num_paulis: ret.compose(row, qargs=qargs, inplace=True) else: ret[idx_] = ret[idx_].compose(row, qargs=qargs) return ret