def __getitem__(self, index): """Return a view of the PauliList.""" # Returns a view of specified rows of the PauliList # This supports all slicing operations the underlying array supports. if isinstance(index, tuple): if len(index) == 1: index = index[0] elif len(index) > 2: raise IndexError(f"Invalid PauliList index {index}") # Row-only indexing if isinstance(index, (int, np.integer)): # Single Pauli return Pauli( BasePauli( self._z[np.newaxis, index], self._x[np.newaxis, index], self._phase[np.newaxis, index], )) elif isinstance(index, (slice, list, np.ndarray)): # Sub-Table view return PauliList( BasePauli(self._z[index], self._x[index], self._phase[index])) # Row and Qubit indexing return PauliList((self._z[index], self._x[index], 0))
def _add(self, other, qargs=None): """Append two PauliLists. If ``qargs`` are specified the other operator will be added assuming it is identity on all other subsystems. Args: other (PauliList): another table. qargs (None or list): optional subsystems to add on (Default: None) Returns: PauliList: the concatinated list self + other. """ if qargs is None: qargs = getattr(other, "qargs", None) if not isinstance(other, PauliList): other = PauliList(other) self._op_shape._validate_add(other._op_shape, qargs) base_phase = np.hstack((self._phase, other._phase)) if qargs is None or (sorted(qargs) == qargs and len(qargs) == self.num_qubits): base_z = np.vstack([self._z, other._z]) base_x = np.vstack([self._x, other._x]) else: # Pad other with identity and then add padded = BasePauli( np.zeros((self.size, self.num_qubits), dtype=bool), np.zeros((self.size, self.num_qubits), dtype=bool), np.zeros(self.size, dtype=int), ) padded = padded.compose(other, qargs=qargs, inplace=True) base_z = np.vstack([self._z, padded._z]) base_x = np.vstack([self._x, padded._x]) return PauliList(BasePauli(base_z, base_x, base_phase))
def from_symplectic(cls, z, x, phase=0): """Construct a PauliList from a symplectic data. Args: z (np.ndarray): 2D boolean Numpy array. x (np.ndarray): 2D boolean Numpy array. phase (np.ndarray or None): Optional, 1D integer array from Z_4. Returns: PauliList: the constructed PauliList. """ base_z, base_x, base_phase = cls._from_array(z, x, phase) return cls(BasePauli(base_z, base_x, base_phase))
def _from_circuit(cls, instr): """Convert a Pauli circuit to BasePauli data.""" # Try and convert single instruction if isinstance(instr, (PauliGate, IGate, XGate, YGate, ZGate)): return cls._from_pauli_instruction(instr) if isinstance(instr, Instruction): # Convert other instructions to circuit definition if instr.definition is None: raise QiskitError("Cannot apply Instruction: {}".format( instr.name)) # Convert to circuit instr = instr.definition # Initialize identity Pauli ret = Pauli( BasePauli( np.zeros((1, instr.num_qubits), dtype=bool), np.zeros((1, instr.num_qubits), dtype=bool), np.zeros(1, dtype=int), )) # Add circuit global phase if specified if instr.global_phase: ret.phase = cls._phase_from_complex( np.exp(1j * float(instr.global_phase))) # Recursively apply instructions for dinstr, qregs, cregs in instr.data: if cregs: raise QiskitError( "Cannot apply instruction with classical registers: {}". format(dinstr.name)) if not isinstance(dinstr, Barrier): next_instr = BasePauli(*cls._from_circuit(dinstr)) if next_instr is not None: qargs = [tup.index for tup in qregs] ret = ret.compose(next_instr, qargs=qargs) return ret._z, ret._x, ret._phase
def delete(self, ind, qubit=False): """Return a copy with Pauli rows deleted from table. When deleting qubits the qubit index is the same as the column index of the underlying :attr:`X` and :attr:`Z` arrays. Args: ind (int or list): index(es) to delete. qubit (bool): if True delete qubit columns, otherwise delete Pauli rows (Default: False). Returns: PauliList: the resulting table with the entries removed. Raises: QiskitError: if ind is out of bounds for the array size or number of qubits. """ if isinstance(ind, int): ind = [ind] # Row deletion if not qubit: if max(ind) >= len(self): raise QiskitError( "Indices {} are not all less than the size" " of the PauliList ({})".format(ind, len(self)) ) z = np.delete(self._z, ind, axis=0) x = np.delete(self._x, ind, axis=0) phase = np.delete(self._phase, ind) return PauliList(BasePauli(z, x, phase)) # Column (qubit) deletion if max(ind) >= self.num_qubits: raise QiskitError( "Indices {} are not all less than the number of" " qubits in the PauliList ({})".format(ind, self.num_qubits) ) z = np.delete(self._z, ind, axis=1) x = np.delete(self._x, ind, axis=1) # Use self.phase, not self._phase as deleting qubits can change the # ZX phase convention return PauliList.from_symplectic(z, x, self.phase)
def unique(self, return_index=False, return_counts=False): """Return unique Paulis from the table. **Example** .. jupyter-execute:: from qiskit.quantum_info.operators import PauliList pt = PauliList(['X', 'Y', '-X', 'I', 'I', 'Z', 'X', 'iZ']) unique = pt.unique() print(unique) Args: return_index (bool): If True, also return the indices that result in the unique array. (Default: False) return_counts (bool): If True, also return the number of times each unique item appears in the table. Returns: PauliList: unique the table of the unique rows. unique_indices: np.ndarray, optional The indices of the first occurrences of the unique values in the original array. Only provided if ``return_index`` is True. unique_counts: np.array, optional The number of times each of the unique values comes up in the original array. Only provided if ``return_counts`` is True. """ # Check if we need to stack the phase array if np.any(self._phase != self._phase[0]): # Create a single array of Pauli's and phases for calling np.unique on # so that we treat different phased Pauli's as unique array = np.hstack([ self._z, self._x, self.phase.reshape((self.phase.shape[0], 1)) ]) else: # All Pauli's have the same phase so we only need to sort the array array = np.hstack([self._z, self._x]) # Get indexes of unique entries if return_counts: _, index, counts = np.unique(array, return_index=True, return_counts=True, axis=0) else: _, index = np.unique(array, return_index=True, axis=0) # Sort the index so we return unique rows in the original array order sort_inds = index.argsort() index = index[sort_inds] unique = PauliList( BasePauli(self._z[index], self._x[index], self._phase[index])) # Concatinate return tuples ret = (unique, ) if return_index: ret += (index, ) if return_counts: ret += (counts[sort_inds], ) if len(ret) == 1: return ret[0] return ret
def insert(self, ind, value, qubit=False): """Insert Pauli's into the table. When inserting qubits the qubit index is the same as the column index of the underlying :attr:`X` and :attr:`Z` arrays. Args: ind (int): index to insert at. value (PauliList): values to insert. qubit (bool): if True delete qubit columns, otherwise delete Pauli rows (Default: False). Returns: PauliList: the resulting table with the entries inserted. Raises: QiskitError: if the insertion index is invalid. """ if not isinstance(ind, int): raise QiskitError("Insert index must be an integer.") if not isinstance(value, PauliList): value = PauliList(value) # Row insertion size = self._num_paulis if not qubit: if ind > size: raise QiskitError( "Index {} is larger than the number of rows in the" " PauliList ({}).".format(ind, size)) base_z = np.insert(self._z, ind, value._z, axis=0) base_x = np.insert(self._x, ind, value._x, axis=0) base_phase = np.insert(self._phase, ind, value._phase) return PauliList(BasePauli(base_z, base_x, base_phase)) # Column insertion if ind > self.num_qubits: raise QiskitError("Index {} is greater than number of qubits" " in the PauliList ({})".format( ind, self.num_qubits)) if len(value) == 1: # Pad blocks to correct size value_x = np.vstack(size * [value.x]) value_z = np.vstack(size * [value.z]) value_phase = np.vstack(size * [value.phase]) elif len(value) == size: # Blocks are already correct size value_x = value.x value_z = value.z value_phase = value.phase else: # Blocks are incorrect size raise QiskitError("Input PauliList must have a single row, or" " the same number of rows as the Pauli Table" " ({}).".format(size)) # Build new array by blocks z = np.hstack([self.z[:, :ind], value_z, self.z[:, ind:]]) x = np.hstack([self.x[:, :ind], value_x, self.x[:, ind:]]) phase = self.phase + value_phase return PauliList.from_symplectic(z, x, phase)