diff --git a/qiskit/quantum_info/__init__.py b/qiskit/quantum_info/__init__.py index 65716bd1415f..3603b5cf114e 100644 --- a/qiskit/quantum_info/__init__.py +++ b/qiskit/quantum_info/__init__.py @@ -30,6 +30,7 @@ pauli_group Quaternion PauliTable + StabilizerTable pauli_basis States @@ -117,7 +118,7 @@ from .operators.measures import process_fidelity from .operators import average_gate_fidelity from .operators import gate_error -from .operators.symplectic import PauliTable +from .operators.symplectic import PauliTable, StabilizerTable from .operators.symplectic import pauli_basis from .states import Statevector, DensityMatrix diff --git a/qiskit/quantum_info/operators/__init__.py b/qiskit/quantum_info/operators/__init__.py index dd90929da48b..a8a748d6cebf 100644 --- a/qiskit/quantum_info/operators/__init__.py +++ b/qiskit/quantum_info/operators/__init__.py @@ -21,5 +21,5 @@ from .quaternion import Quaternion from .measures import process_fidelity, average_gate_fidelity, gate_error -from .symplectic import PauliTable +from .symplectic import PauliTable, StabilizerTable from .symplectic import pauli_basis diff --git a/qiskit/quantum_info/operators/symplectic/__init__.py b/qiskit/quantum_info/operators/symplectic/__init__.py index 06e57328e329..58cb82e60998 100644 --- a/qiskit/quantum_info/operators/symplectic/__init__.py +++ b/qiskit/quantum_info/operators/symplectic/__init__.py @@ -18,3 +18,4 @@ from .pauli_table import PauliTable from .pauli_utils import pauli_basis +from .stabilizer_table import StabilizerTable diff --git a/qiskit/quantum_info/operators/symplectic/stabilizer_table.py b/qiskit/quantum_info/operators/symplectic/stabilizer_table.py new file mode 100644 index 000000000000..773bc8c6cd3b --- /dev/null +++ b/qiskit/quantum_info/operators/symplectic/stabilizer_table.py @@ -0,0 +1,1047 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2020 +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Symplectic Stabilizer Table Class +""" +# pylint: disable=invalid-name, abstract-method + +import numpy as np + +from qiskit.exceptions import QiskitError +from qiskit.quantum_info.operators.custom_iterator import CustomIterator +from qiskit.quantum_info.operators.symplectic.pauli_table import PauliTable + + +class StabilizerTable(PauliTable): + r"""Symplectic representation of a list Stabilizer matrices. + + **Symplectic Representation** + + The symplectic representation of a single-qubit Stabilizer matrix + is a pair of boolean values :math:`[x, z]` and a boolean phase `p` + such that the Stabilizer matrix is given by + :math:`S = (-1)^p \sigma_z^z.\sigma_x^x`. + The correspondence between labels, symplectic representation, + stabilizer matrices, and Pauli matrices for the single-qubit case is + shown in the following table. + + .. list-table:: Table 1: Stabilizer Representations + :header-rows: 1 + + * - Label + - Phase + - Symplectic + - Matrix + - Pauli + * - ``"+I"`` + - 0 + - :math:`[0, 0]` + - :math:`\begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}` + - :math:`I` + * - ``"-I"`` + - 1 + - :math:`[0, 0]` + - :math:`\begin{bmatrix} -1 & 0 \\ 0 & -1 \end{bmatrix}` + - :math:`-I` + * - ``"X"`` + - 0 + - :math:`[1, 0]` + - :math:`\begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix}` + - :math:`X` + * - ``"-X"`` + - 1 + - :math:`[1, 0]` + - :math:`\begin{bmatrix} 0 & -1 \\ -1 & 0 \end{bmatrix}` + - :math:`-X` + * - ``"Y"`` + - 0 + - :math:`[1, 1]` + - :math:`\begin{bmatrix} 0 & 1 \\ -1 & 0 \end{bmatrix}` + - :math:`iY` + * - ``"-Y"`` + - 1 + - :math:`[1, 1]` + - :math:`\begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix}` + - :math:`-iY` + * - ``"Z"`` + - 0 + - :math:`[0, 1]` + - :math:`\begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix}` + - :math:`Z` + * - ``"-Z"`` + - 1 + - :math:`[0, 1]` + - :math:`\begin{bmatrix} -1 & 0 \\ 0 & 1 \end{bmatrix}` + - :math:`-Z` + + Internally this is stored as a length `N` boolean phase vector + :math:`[p_{N-1}, ..., p_{0}]` and a :class:`PauliTable` + :math:`M \times 2N` boolean matrix: + + .. math:: + + \left(\begin{array}{ccc|ccc} + x_{0,0} & ... & x_{0,N-1} & z_{0,0} & ... & z_{0,N-1} \\ + x_{1,0} & ... & x_{1,N-1} & z_{1,0} & ... & z_{1,N-1} \\ + \vdots & \ddots & \vdots & \vdots & \ddots & \vdots \\ + x_{M-1,0} & ... & x_{M-1,N-1} & z_{M-1,0} & ... & z_{M-1,N-1} + \end{array}\right) + + where each row is a block vector :math:`[X_i, Z_i]` with + :math:`X_i = [x_{i,0}, ..., x_{i,N-1}]`, :math:`Z_i = [z_{i,0}, ..., z_{i,N-1}]` + is the symplectic representation of an `N`-qubit Pauli. + This representation is based on reference [1]. + + StabilizerTable's can be created from a list of labels using :meth:`from_labels`, + and converted to a list of labels or a list of matrices using + :meth:`to_labels` and :meth:`to_matrix` respectively. + + **Group Product** + + The product of the stabilizer elements is defined with respect to the + matrix multiplication of the matrices in Table 1. In terms of + stabilizes labels the dot product group structure is + + +-------+----+----+----+----+ + | A.B | I | X | Y | Z | + +=======+====+====+====+====+ + | **I** | I | X | Y | Z | + +-------+----+----+----+----+ + | **X** | X | I | -Z | Y | + +-------+----+----+----+----+ + | **Y** | Y | Z | -I | -X | + +-------+----+----+----+----+ + | **Z** | Z | -Y | X | I | + +-------+----+----+----+----+ + + The :meth:`dot` method will return the output for + :code:`row.dot(col) = row.col`, while the :meth:`compose` will return + :code:`row.compose(col) = col.row` from the above table. + + Note that while this dot product is different to the matrix product + of the :class:`PauliTable`, it does not change the commutation structure + of elements. Hence :meth:`commutes:` will be the same for the same + labels. + + **Qubit Ordering** + + The qubits are ordered in the table such the least significant qubit + `[x_{i, 0}, z_{i, 0}]` is the first element of each of the :math:`X_i, Z_i` + vector blocks. This is the opposite order to position in string labels or + matrix tensor products where the least significant qubit is the right-most + string character. For example Pauli ``"ZX"`` has ``"X"`` on qubit-0 + and ``"Z"`` on qubit 1, and would have symplectic vectors :math:`x=[1, 0]`, + :math:`z=[0, 1]`. + + **Data Access** + + Subsets of rows can be accessed using the list access ``[]`` operator and + will return a table view of part of the StabilizerTable. The underlying + phase vector and Pauli array can be directly accessed using the :attr:`phase` + and :attr:`array` properties respectively. The sub-arrays for only the + `X` or `Z` blocks can be accessed using the :attr:`X` and :attr:`Z` + properties respectively. + + The Pauli part of the Stabilizer table can be viewed and accessed as a + :class:`PauliTable` object using the :attr:`pauli` property. Note that this + doesn't copy the underlying array so any changes made to the Pauli table + will also change the stabilizer table. + + **Iteration** + + Rows in the Stabilizer table can be iterated over like a list. Iteration can + also be done using the label or matrix representation of each row using the + :meth:`label_iter` and :meth:`matrix_iter` methods. + + References: + 1. S. Aaronson, D. Gottesman, *Improved Simulation of Stabilizer Circuits*, + Phys. Rev. A 70, 052328 (2004). + `arXiv:quant-ph/0406196 `_ + """ + + def __init__(self, data, phase=None): + """Initialize the StabilizerTable. + + Args: + data (array or str or PauliTable): input PauliTable data. + phase (array or bool or None): optional phase vector for input data + (Default: None). + + Raises: + QiskitError: if input array or phase vector has an invalid shape. + + Additional Information: + The input array is not copied so multiple Pauli and Stabilizer tables + can share the same underlying array. + """ + if isinstance(data, str) and phase is None: + pauli, phase = StabilizerTable._from_label(data) + elif isinstance(data, StabilizerTable): + pauli = data._array + if phase is None: + phase = data._phase + else: + pauli = data + # Initialize the Pauli table + super().__init__(pauli) + + # Initialize the phase vector + if phase is None or phase is False: + self._phase = np.zeros(self.size, dtype=np.bool) + elif phase is True: + self._phase = np.ones(self.size, dtype=np.bool) + else: + self._phase = np.asarray(phase, dtype=np.bool) + if self._phase.shape != (self.size, ): + raise QiskitError("Phase vector is incorrect shape.") + + def __repr__(self): + return 'StabilizerTable(\n{},\nphase={})'.format( + repr(self._array), repr(self._phase)) + + def __str__(self): + """String representation""" + return 'StabilizerTable: {}'.format(self.to_labels()) + + def __eq__(self, other): + """Test if two StabilizerTables are equal""" + if isinstance(other, StabilizerTable): + return np.all(self._phase == other._phase) and self.pauli == other.pauli + return False + + def copy(self): + """Return a copy of the StabilizerTable.""" + return StabilizerTable(self._array.copy(), + self._phase.copy()) + + # --------------------------------------------------------------------- + # PauliTable and phase access + # --------------------------------------------------------------------- + + @property + def pauli(self): + """Return PauliTable""" + return PauliTable(self._array) + + @pauli.setter + def pauli(self, value): + if not isinstance(value, PauliTable): + value = PauliTable(value) + self._array[:, :] = value._array + + @property + def phase(self): + """Return phase vector""" + return self._phase + + @phase.setter + def phase(self, value): + self._phase[:] = value + + # --------------------------------------------------------------------- + # Array methods + # --------------------------------------------------------------------- + + def __getitem__(self, key): + """Return a view of StabilizerTable""" + if isinstance(key, int): + key = [key] + return StabilizerTable(self._array[key], self._phase[key]) + + def __setitem__(self, key, value): + """Update StabilizerTable""" + if not isinstance(value, StabilizerTable): + value = StabilizerTable(value) + self._array[key] = value.array + self._phase[key] = value.phase + + def delete(self, ind, qubit=False): + """Return a copy with Stabilizer rows deleted from table. + + When deleting qubit columns, qubit-0 is the right-most + (largest index) column, and qubit-(N-1) is the left-most + (0 index) column 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 + Stabilizer rows (Default: False). + + Returns: + StabilizerTable: the resulting table with the entries removed. + + Raises: + QiskitError: if ind is out of bounds for the array size or + number of qubits. + """ + if qubit: + # When deleting qubit columns we don't need to modify + # the phase vector + table = super().delete(ind, True) + return StabilizerTable(table, self._phase) + + if isinstance(ind, int): + ind = [ind] + if max(ind) >= self.size: + raise QiskitError("Indices {} are not all less than the size" + " of the SatbilizerTable ({})".format(ind, self.size)) + return StabilizerTable(np.delete(self._array, ind, axis=0), + np.delete(self._phase, ind, axis=0)) + + def insert(self, ind, value, qubit=False): + """Insert stabilizers's into the table. + + When inserting qubit columns, qubit-0 is the right-most + (largest index) column, and qubit-(N-1) is the left-most + (0 index) column of the underlying :attr:`X` and :attr:`Z` + arrays. + + Args: + ind (int): index to insert at. + value (StabilizerTable): values to insert. + qubit (bool): if True delete qubit columns, otherwise delete + Pauli rows (Default: False). + + Returns: + StabilizerTable: 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, StabilizerTable): + value = StabilizerTable(value) + + # Update PauliTable component + table = super().insert(ind, value, qubit=qubit) + + # Update phase vector + if not qubit: + phase = np.insert(self._phase, ind, value._phase, axis=0) + else: + phase = np.logical_xor(self._phase, value._phase) + return StabilizerTable(table, phase) + + def argsort(self, weight=False): + """Return indices for sorting the rows of the PauliTable. + + The default sort method is lexicographic sorting of Paulis by + qubit number. By using the `weight` kwarg the output can additionally + be sorted by the number of non-identity terms in the Stabilizer, + where the set of all Pauli's of a given weight are still ordered + lexicographically. + + This does not sort based on phase values. It will preserve the + original order of rows with the same Pauli's but different phases. + + Args: + weight (bool): optionally sort by weight if True (Default: False). + + Returns: + array: the indices for sorting the table. + """ + return super().argsort(weight=weight) + + def sort(self, weight=False): + """Sort the rows of the table. + + The default sort method is lexicographic sorting by qubit number. + By using the `weight` kwarg the output can additionally be sorted + by the number of non-identity terms in the Pauli, where the set of + all Pauli's of a given weight are still ordered lexicographically. + + This does not sort based on phase values. It will preserve the + original order of rows with the same Pauli's but different phases. + + Consider sorting all a random ordering of all 2-qubit Paulis + + .. jupyter-execute:: + + from numpy.random import shuffle + from qiskit.quantum_info.operators import StabilizerTable + + # 2-qubit labels + labels = ['+II', '+IX', '+IY', '+IZ', '+XI', '+XX', '+XY', '+XZ', + '+YI', '+YX', '+YY', '+YZ', '+ZI', '+ZX', '+ZY', '+ZZ', + '-II', '-IX', '-IY', '-IZ', '-XI', '-XX', '-XY', '-XZ', + '-YI', '-YX', '-YY', '-YZ', '-ZI', '-ZX', '-ZY', '-ZZ'] + # Shuffle Labels + shuffle(labels) + st = StabilizerTable.from_labels(labels) + print('Initial Ordering') + print(st) + + # Lexicographic Ordering + srt = st.sort() + print('Lexicographically sorted') + print(srt) + + # Weight Ordering + srt = st.sort(weight=True) + print('Weight sorted') + print(srt) + + Args: + weight (bool): optionally sort by weight if True (Default: False). + + Returns: + StabilizerTable: a sorted copy of the original table. + """ + return super().sort(weight=weight) + + def unique(self, return_index=False, return_counts=False): + """Return unique stabilizers from the table. + + **Example** + + .. jupyter-execute:: + + from qiskit.quantum_info.operators import StabilizerTable + + st = StabilizerTable.from_labels(['+X', '+I', '-I', '-X', '+X', '-X', '+I']) + unique = st.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: + StabilizerTable: 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. + """ + # Combine array and phases into single array for sorting + stack = np.hstack([self._array, + self._phase.reshape((self.size, 1))]) + if return_counts: + _, index, counts = np.unique(stack, return_index=True, + return_counts=True, axis=0) + else: + _, index = np.unique(stack, 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 = self[index] + # Concatenate 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 + + # --------------------------------------------------------------------- + # Utility methods + # --------------------------------------------------------------------- + + def tensor(self, other): + """Return the tensor output product of two tables. + + This returns the combination of the tensor product of all + stabilizers in the `current` table with all stabilizers in the + `other` table. The `other` tables qubits will be the + least-significant in the returned table. This is the opposite + tensor order to :meth:`tensor`. + + **Example** + + .. jupyter-execute:: + + from qiskit.quantum_info.operators import StabilizerTable + + current = StabilizerTable.from_labels(['+I', '-X']) + other = StabilizerTable.from_labels(['-Y', '+Z']) + print(current.tensor(other)) + + Args: + other (StabilizerTable): another StabilizerTable. + + Returns: + StabilizerTable: the tensor outer product table. + + Raises: + QiskitError: if other cannot be converted to a StabilizerTable. + """ + if not isinstance(other, StabilizerTable): + other = StabilizerTable(other) + pauli = super().tensor(other) + phase1, phase2 = self._block_stack(self.phase, other.phase) + + phase = np.logical_xor(phase1, phase2) + return StabilizerTable(pauli, phase) + + def expand(self, other): + """Return the expand output product of two tables. + + This returns the combination of the tensor product of all + stabilizers in the `other` table with all stabilizers in the + `current` table. The `current` tables qubits will be the + least-significant in the returned table. This is the opposite + tensor order to :meth:`tensor`. + + **Example** + + .. jupyter-execute:: + + from qiskit.quantum_info.operators import StabilizerTable + + current = StabilizerTable.from_labels(['+I', '-X']) + other = StabilizerTable.from_labels(['-Y', '+Z']) + print(current.expand(other)) + + Args: + other (StabilizerTable): another StabilizerTable. + + Returns: + StabilizerTable: the expand outer product table. + + Raises: + QiskitError: if other cannot be converted to a StabilizerTable. + """ + if not isinstance(other, StabilizerTable): + other = StabilizerTable(other) + pauli = super().expand(other) + phase1, phase2 = self._block_stack(self.phase, other.phase) + phase = np.logical_xor(phase1, phase2) + return StabilizerTable(pauli, phase) + + def compose(self, other, qargs=None, front=False): + """Return the compose output product of two tables. + + This returns the combination of the compose product of all + stabilizers in the current table with all stabilizers in the + other table. + + The individual stabilizer compose product is given by + + +----------------------+----+----+----+----+ + | :code:`A.compose(B)` | I | X | Y | Z | + +======================+====+====+====+====+ + | **I** | I | X | Y | Z | + +----------------------+----+----+----+----+ + | **X** | X | I | Z | -Y | + +----------------------+----+----+----+----+ + | **Y** | Y | -Z | -I | X | + +----------------------+----+----+----+----+ + | **Z** | Z | Y | -X | I | + +----------------------+----+----+----+----+ + + If `front=True` the composition will be given by the + :meth:`dot` method. + + **Example** + + .. jupyter-execute:: + + from qiskit.quantum_info.operators import StabilizerTable + + current = StabilizerTable.from_labels(['+I', '-X']) + other = StabilizerTable.from_labels(['+X', '-Z']) + print(current.compose(other)) + + Args: + other (StabilizerTable): another StabilizerTable. + qargs (None or list): qubits to apply compose product on + (Default: None). + front (bool): If True use `dot` composition method + (default: False). + + Returns: + StabilizerTable: the compose outer product table. + + Raises: + QiskitError: if other cannot be converted to a StabilizerTable. + """ + if not isinstance(other, StabilizerTable): + other = StabilizerTable(other) + if qargs is None and other.num_qubits != self.num_qubits: + raise QiskitError("other StabilizerTable must be on the same number of qubits.") + if qargs and other.num_qubits != len(qargs): + raise QiskitError("Number of qubits in the other StabilizerTable does not match qargs.") + + # Stack X and Z blocks for output size + x1, x2 = self._block_stack(self.X, other.X) + z1, z2 = self._block_stack(self.Z, other.Z) + phase1, phase2 = self._block_stack(self.phase, other.phase) + + if qargs is not None: + ret_x, ret_z = x1.copy(), z1.copy() + x1 = x1[:, qargs] + z1 = z1[:, qargs] + ret_x[:, qargs] = x1 ^ x2 + ret_z[:, qargs] = z1 ^ z2 + pauli = np.hstack([ret_x, ret_z]) + else: + pauli = np.hstack((x1 ^ x2, z1 ^ z2)) + + # We pick up a minus sign for products: + # Y.Y = -I, X.Y = -Z, Y.Z = -X, Z.X = -Y + if front: + minus = (x1 & z2 & (x2 | z1)) | (~x1 & x2 & z1 & ~z2) + else: + minus = (x2 & z1 & (x1 | z2)) | (~x2 & x1 & z2 & ~z1) + phase_shift = np.array(np.sum(minus, axis=1) % 2, dtype=np.bool) + phase = phase_shift ^ phase1 ^ phase2 + return StabilizerTable(pauli, phase) + + def dot(self, other, qargs=None): + """Return the dot output product of two tables. + + This returns the combination of the compose product of all + stabilizers in the current table with all stabilizers in the + other table. + + The individual stabilizer dot product is given by + + +------------------+----+----+----+----+ + | :code:`A.dot(B)` | I | X | Y | Z | + +==================+====+====+====+====+ + | **I** | I | X | Y | Z | + +------------------+----+----+----+----+ + | **X** | X | I | -Z | Y | + +------------------+----+----+----+----+ + | **Y** | Y | Z | -I | -X | + +------------------+----+----+----+----+ + | **Z** | Z | -Y | X | I | + +------------------+----+----+----+----+ + + **Example** + + .. jupyter-execute:: + + from qiskit.quantum_info.operators import StabilizerTable + + current = StabilizerTable.from_labels(['+I', '-X']) + other = StabilizerTable.from_labels(['+X', '-Z']) + print(current.dot(other)) + + Args: + other (StabilizerTable): another StabilizerTable. + qargs (None or list): qubits to apply dot product on + (Default: None). + + Returns: + StabilizerTable: the dot outer product table. + + Raises: + QiskitError: if other cannot be converted to a StabilizerTable. + """ + return super().dot(other, qargs=qargs) + + def _add(self, other, qargs=None): + """Append with another StabilizerTable. + + If ``qargs`` are specified the other operator will be added + assuming it is identity on all other subsystems. + + Args: + other (StabilizerTable): another table. + qargs (None or list): optional subsystems to add on + (Default: None) + + Returns: + StabilizerTable: the concatinated table self + other. + """ + if qargs is None: + qargs = getattr(other, 'qargs', None) + + if not isinstance(other, StabilizerTable): + other = StabilizerTable(other) + + self._validate_add_dims(other, qargs) + + if qargs is None or (sorted(qargs) == qargs + and len(qargs) == self.num_qubits): + return StabilizerTable(np.vstack((self._array, other._array)), + np.hstack((self._phase, other._phase))) + + # Pad other with identity and then add + padded = StabilizerTable( + np.zeros((1, 2 * self.num_qubits), dtype=np.bool)) + padded = padded.compose(other, qargs=qargs) + + return StabilizerTable( + np.vstack((self._array, padded._array)), + np.hstack((self._phase, padded._phase))) + + def _multiply(self, other): + """Multiply (XOR) phase vector of the StabilizerTable. + + This updates the phase vector of the table. Allowed values for + multiplication are ``False``, ``True``, 1 or -1. Multiplying by + -1 or ``False`` is equivalent. As is multiplying by 1 or ``True``. + + Args: + other (bool or int): a Boolean value. + + Returns: + StabilizerTable: the updated stabilizer table. + + Raises: + QiskitError: if other is not in (False, True, 1, -1). + """ + # Numeric (integer) value case + if not isinstance(other, (bool, np.bool)) and other not in [1, -1]: + raise QiskitError( + "Can only multiply a Stabilizer value by +1 or -1 phase.") + + # We have to be careful we don't cast True <-> +1 when + # we store -1 phase as boolen True value + if (isinstance(other, (bool, np.bool)) and other) or other == -1: + ret = self.copy() + ret._phase ^= True + return ret + return self + + # --------------------------------------------------------------------- + # Representation conversions + # --------------------------------------------------------------------- + + @classmethod + def from_labels(cls, labels): + r"""Construct a StabilizerTable from a list of Pauli stabilizer strings. + + Pauli Stabilizer string labels are Pauli strings with an optional + ``"+"`` or ``"-"`` character. If there is no +/-sign a + phase is + used by default. + + .. list-table:: Stabilizer Representations + :header-rows: 1 + + * - Label + - Phase + - Symplectic + - Matrix + - Pauli + * - ``"+I"`` + - 0 + - :math:`[0, 0]` + - :math:`\begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}` + - :math:`I` + * - ``"-I"`` + - 1 + - :math:`[0, 0]` + - :math:`\begin{bmatrix} -1 & 0 \\ 0 & -1 \end{bmatrix}` + - :math:`-I` + * - ``"X"`` + - 0 + - :math:`[1, 0]` + - :math:`\begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix}` + - :math:`X` + * - ``"-X"`` + - 1 + - :math:`[1, 0]` + - :math:`\begin{bmatrix} 0 & -1 \\ -1 & 0 \end{bmatrix}` + - :math:`-X` + * - ``"Y"`` + - 0 + - :math:`[1, 1]` + - :math:`\begin{bmatrix} 0 & 1 \\ -1 & 0 \end{bmatrix}` + - :math:`iY` + * - ``"-Y"`` + - 1 + - :math:`[1, 1]` + - :math:`\begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix}` + - :math:`-iY` + * - ``"Z"`` + - 0 + - :math:`[0, 1]` + - :math:`\begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix}` + - :math:`Z` + * - ``"-Z"`` + - 1 + - :math:`[0, 1]` + - :math:`\begin{bmatrix} -1 & 0 \\ 0 & 1 \end{bmatrix}` + - :math:`-Z` + + Args: + labels (list): Pauli stabilizer string label(es). + + Returns: + StabilizerTable: the constructed StabilizerTable. + + Raises: + QiskitError: If the input list is empty or contains invalid + Pauli stabilizer strings. + """ + if isinstance(labels, str): + labels = [labels] + n_paulis = len(labels) + if n_paulis == 0: + raise QiskitError("Input Pauli list is empty.") + # Get size from first Pauli + pauli, phase = cls._from_label(labels[0]) + table = np.zeros((n_paulis, len(pauli)), dtype=np.bool) + phases = np.zeros(n_paulis, dtype=np.bool) + table[0], phases[0] = pauli, phase + for i in range(1, n_paulis): + table[i], phases[i] = cls._from_label(labels[i]) + return cls(table, phases) + + def to_labels(self, array=False): + r"""Convert a StabilizerTable to a list Pauli stabilizer string labels. + + For large StabilizerTables converting using the ``array=True`` + kwarg will be more efficient since it allocates memory for + the full Numpy array of labels in advance. + + .. list-table:: Stabilizer Representations + :header-rows: 1 + + * - Label + - Phase + - Symplectic + - Matrix + - Pauli + * - ``"+I"`` + - 0 + - :math:`[0, 0]` + - :math:`\begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}` + - :math:`I` + * - ``"-I"`` + - 1 + - :math:`[0, 0]` + - :math:`\begin{bmatrix} -1 & 0 \\ 0 & -1 \end{bmatrix}` + - :math:`-I` + * - ``"X"`` + - 0 + - :math:`[1, 0]` + - :math:`\begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix}` + - :math:`X` + * - ``"-X"`` + - 1 + - :math:`[1, 0]` + - :math:`\begin{bmatrix} 0 & -1 \\ -1 & 0 \end{bmatrix}` + - :math:`-X` + * - ``"Y"`` + - 0 + - :math:`[1, 1]` + - :math:`\begin{bmatrix} 0 & 1 \\ -1 & 0 \end{bmatrix}` + - :math:`iY` + * - ``"-Y"`` + - 1 + - :math:`[1, 1]` + - :math:`\begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix}` + - :math:`-iY` + * - ``"Z"`` + - 0 + - :math:`[0, 1]` + - :math:`\begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix}` + - :math:`Z` + * - ``"-Z"`` + - 1 + - :math:`[0, 1]` + - :math:`\begin{bmatrix} -1 & 0 \\ 0 & 1 \end{bmatrix}` + - :math:`-Z` + + Args: + array (bool): return a Numpy array if True, otherwise + return a list (Default: False). + + Returns: + list or array: The rows of the StabilizerTable in label form. + """ + ret = np.zeros(self.size, dtype='".format(hex(id(self))) + + def __getitem__(self, key): + return self.obj._to_label(self.obj.array[key], + self.obj.phase[key]) + return LabelIterator(self) + + def matrix_iter(self, sparse=False): + """Return a matrix representation iterator. + + This is a lazy iterator that converts each row into the Pauli matrix + representation only as it is used. To convert the entire table to + matrices use the :meth:`to_matrix` method. + + Args: + sparse (bool): optionally return sparse CSR matrices if True, + otherwise return Numpy array matrices + (Default: False) + + Returns: + MatrixIterator: matrix iterator object for the StabilizerTable. + """ + class MatrixIterator(CustomIterator): + """Matrix representation iteration and item access.""" + def __repr__(self): + return "".format(hex(id(self))) + + def __getitem__(self, key): + return self.obj._to_matrix(self.obj.array[key], + self.obj.phase[key], + sparse=sparse) + return MatrixIterator(self) diff --git a/releasenotes/notes/stabilizer-table-97cf2949ffe2cea7.yaml b/releasenotes/notes/stabilizer-table-97cf2949ffe2cea7.yaml new file mode 100644 index 000000000000..0c51b10307d6 --- /dev/null +++ b/releasenotes/notes/stabilizer-table-97cf2949ffe2cea7.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Adds :class:`qiskit.quantum_info.StabilizerTable`` class. This is a subclass + of the :class:`qiskit.quantum_info.PauliTable` class which includes a boolean + phase vector along with the Pauli table array. This represents a list of + Stabilizer operators which are real-Pauli operators with +1 or -1 coefficient. + Because the stabilizer matrices are real the ``"Y"`` label matrix is defined as + ``[[0, 1], [-1, 0]]``. See the API documentation for additional information. diff --git a/test/python/quantum_info/operators/symplectic/test_stabilizer_table.py b/test/python/quantum_info/operators/symplectic/test_stabilizer_table.py new file mode 100644 index 000000000000..ea2163e04e8c --- /dev/null +++ b/test/python/quantum_info/operators/symplectic/test_stabilizer_table.py @@ -0,0 +1,1081 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# pylint: disable=invalid-name + +"""Tests for StabilizerTable class.""" + +import unittest +import numpy as np +from scipy.sparse import csr_matrix + +from qiskit import QiskitError +from qiskit.test import QiskitTestCase +from qiskit.quantum_info.operators.symplectic import StabilizerTable +from qiskit.quantum_info.operators.symplectic import PauliTable + + +def stab_mat(label): + """Return stabilizer matrix from a stabilizer label""" + mat = np.eye(1, dtype=complex) + if label[0] == '-': + mat *= -1 + if label[0] in ['-', '+']: + label = label[1:] + for i in label: + if i == 'I': + mat = np.kron(mat, np.eye(2)) + elif i == 'X': + mat = np.kron(mat, np.array([[0, 1], [1, 0]])) + elif i == 'Y': + mat = np.kron(mat, np.array([[0, 1], [-1, 0]])) + elif i == 'Z': + mat = np.kron(mat, np.array([[1, 0], [0, -1]])) + else: + raise QiskitError('Invalid stabilizer string {}'.format(i)) + return mat + + +class TestStabilizerTableInit(QiskitTestCase): + """Tests for StabilizerTable initialization.""" + + def test_array_init(self): + """Test array initialization.""" + + with self.subTest(msg='bool array'): + target = np.array([[False, False], [True, True]]) + value = StabilizerTable(target)._array + self.assertTrue(np.all(value == target)) + + with self.subTest(msg='bool array no copy'): + target = np.array([[False, True], [True, True]]) + value = StabilizerTable(target)._array + value[0, 0] = not value[0, 0] + self.assertTrue(np.all(value == target)) + + with self.subTest(msg='bool array raises'): + array = np.array([[False, False, False], + [True, True, True]]) + self.assertRaises(QiskitError, StabilizerTable, array) + + def test_vector_init(self): + """Test vector initialization.""" + + with self.subTest(msg='bool vector'): + target = np.array([False, False, False, False]) + value = StabilizerTable(target)._array + self.assertTrue(np.all(value == target)) + + with self.subTest(msg='bool vector no copy'): + target = np.array([False, True, True, False]) + value = StabilizerTable(target)._array + value[0, 0] = not value[0, 0] + self.assertTrue(np.all(value == target)) + + def test_string_init(self): + """Test string initialization.""" + + with self.subTest(msg='str init "I"'): + value = StabilizerTable('I')._array + target = np.array([[False, False]], dtype=np.bool) + self.assertTrue(np.all(np.array(value == target))) + + with self.subTest(msg='str init "X"'): + value = StabilizerTable('X')._array + target = np.array([[True, False]], dtype=np.bool) + self.assertTrue(np.all(np.array(value == target))) + + with self.subTest(msg='str init "Y"'): + value = StabilizerTable('Y')._array + target = np.array([[True, True]], dtype=np.bool) + self.assertTrue(np.all(np.array(value == target))) + + with self.subTest(msg='str init "Z"'): + value = StabilizerTable('Z')._array + target = np.array([[False, True]], dtype=np.bool) + self.assertTrue(np.all(np.array(value == target))) + + with self.subTest(msg='str init "IX"'): + value = StabilizerTable('IX')._array + target = np.array([[True, False, False, False]], dtype=np.bool) + self.assertTrue(np.all(np.array(value == target))) + + with self.subTest(msg='str init "XI"'): + value = StabilizerTable('XI')._array + target = np.array([[False, True, False, False]], dtype=np.bool) + self.assertTrue(np.all(np.array(value == target))) + + with self.subTest(msg='str init "YZ"'): + value = StabilizerTable('YZ')._array + target = np.array([[False, True, True, True]], dtype=np.bool) + self.assertTrue(np.all(np.array(value == target))) + + with self.subTest(msg='str init "XIZ"'): + value = StabilizerTable('XIZ')._array + target = np.array([[False, False, True, True, False, False]], + dtype=np.bool) + self.assertTrue(np.all(np.array(value == target))) + + def test_table_init(self): + """Test StabilizerTable initialization.""" + + with self.subTest(msg='StabilizerTable'): + target = StabilizerTable.from_labels(['XI', 'IX', 'IZ']) + value = StabilizerTable(target) + self.assertEqual(value, target) + + with self.subTest(msg='StabilizerTable no copy'): + target = StabilizerTable.from_labels(['XI', 'IX', 'IZ']) + value = StabilizerTable(target) + value[0] = 'II' + self.assertEqual(value, target) + + +class TestStabilizerTableProperties(QiskitTestCase): + """Tests for StabilizerTable properties.""" + + def test_array_property(self): + """Test array property""" + + with self.subTest(msg='array'): + stab = StabilizerTable('II') + array = np.zeros([2, 4], dtype=np.bool) + self.assertTrue(np.all(stab.array == array)) + + with self.subTest(msg='set array'): + + def set_array(): + stab = StabilizerTable('XXX') + stab.array = np.eye(4) + return stab + + self.assertRaises(Exception, set_array) + + def test_x_property(self): + """Test X property""" + + with self.subTest(msg='X'): + stab = StabilizerTable.from_labels(['XI', 'IZ', 'YY']) + array = np.array([[False, True], [False, False], [True, True]], + dtype=np.bool) + self.assertTrue(np.all(stab.X == array)) + + with self.subTest(msg='set X'): + stab = StabilizerTable.from_labels(['XI', 'IZ']) + val = np.array([[False, False], [True, True]], dtype=np.bool) + stab.X = val + self.assertEqual(stab, StabilizerTable.from_labels(['II', 'XY'])) + + with self.subTest(msg='set X raises'): + + def set_x(): + stab = StabilizerTable.from_labels(['XI', 'IZ']) + val = np.array([[False, False, False], [True, True, True]], + dtype=np.bool) + stab.X = val + return stab + + self.assertRaises(Exception, set_x) + + def test_z_property(self): + """Test Z property""" + with self.subTest(msg='Z'): + stab = StabilizerTable.from_labels(['XI', 'IZ', 'YY']) + array = np.array([[False, False], [True, False], [True, True]], + dtype=np.bool) + self.assertTrue(np.all(stab.Z == array)) + + with self.subTest(msg='set Z'): + stab = StabilizerTable.from_labels(['XI', 'IZ']) + val = np.array([[False, False], [True, True]], dtype=np.bool) + stab.Z = val + self.assertEqual(stab, StabilizerTable.from_labels(['XI', 'ZZ'])) + + with self.subTest(msg='set Z raises'): + + def set_z(): + stab = StabilizerTable.from_labels(['XI', 'IZ']) + val = np.array([[False, False, False], [True, True, True]], + dtype=np.bool) + stab.Z = val + return stab + + self.assertRaises(Exception, set_z) + + def test_shape_property(self): + """Test shape property""" + shape = (3, 8) + stab = StabilizerTable(np.zeros(shape)) + self.assertEqual(stab.shape, shape) + + def test_size_property(self): + """Test size property""" + with self.subTest(msg='size'): + for j in range(1, 10): + shape = (j, 8) + stab = StabilizerTable(np.zeros(shape)) + self.assertEqual(stab.size, j) + + def test_num_qubits_property(self): + """Test num_qubits property""" + with self.subTest(msg='num_qubits'): + for j in range(1, 10): + shape = (5, 2 * j) + stab = StabilizerTable(np.zeros(shape)) + self.assertEqual(stab.num_qubits, j) + + def test_phase_property(self): + """Test phase property""" + with self.subTest(msg='phase'): + phase = np.array([False, True, True, False]) + array = np.eye(4, dtype=np.bool) + stab = StabilizerTable(array, phase) + self.assertTrue(np.all(stab.phase == phase)) + + with self.subTest(msg='set phase'): + phase = np.array([False, True, True, False]) + array = np.eye(4, dtype=np.bool) + stab = StabilizerTable(array) + stab.phase = phase + self.assertTrue(np.all(stab.phase == phase)) + + with self.subTest(msg='set phase raises'): + phase = np.array([False, True, False]) + array = np.eye(4, dtype=np.bool) + stab = StabilizerTable(array) + + def set_phase_raise(): + """Raise exception""" + stab.phase = phase + + self.assertRaises(ValueError, set_phase_raise) + + def test_pauli_property(self): + """Test pauli property""" + with self.subTest(msg='pauli'): + phase = np.array([False, True, True, False]) + array = np.eye(4, dtype=np.bool) + stab = StabilizerTable(array, phase) + pauli = PauliTable(array) + self.assertEqual(stab.pauli, pauli) + + with self.subTest(msg='set pauli'): + phase = np.array([False, True, True, False]) + array = np.zeros((4, 4), dtype=np.bool) + stab = StabilizerTable(array, phase) + pauli = PauliTable(np.eye(4, dtype=np.bool)) + stab.pauli = pauli + self.assertTrue(np.all(stab.array == pauli.array)) + self.assertTrue(np.all(stab.phase == phase)) + + with self.subTest(msg='set pauli'): + phase = np.array([False, True, True, False]) + array = np.zeros((4, 4), dtype=np.bool) + stab = StabilizerTable(array, phase) + pauli = PauliTable(np.eye(4, dtype=np.bool)[1:]) + + def set_pauli_raise(): + """Raise exception""" + stab.pauli = pauli + + self.assertRaises(ValueError, set_pauli_raise) + + def test_eq(self): + """Test __eq__ method.""" + stab1 = StabilizerTable.from_labels(['II', 'XI']) + stab2 = StabilizerTable.from_labels(['XI', 'II']) + self.assertEqual(stab1, stab1) + self.assertNotEqual(stab1, stab2) + + def test_len_methods(self): + """Test __len__ method.""" + for j in range(1, 10): + labels = j * ['XX'] + stab = StabilizerTable.from_labels(labels) + self.assertEqual(len(stab), j) + + def test_add_methods(self): + """Test __add__ method.""" + labels1 = ['+XXI', '-IXX'] + labels2 = ['+XXI', '-ZZI', '+ZYZ'] + stab1 = StabilizerTable.from_labels(labels1) + stab2 = StabilizerTable.from_labels(labels2) + target = StabilizerTable.from_labels(labels1 + labels2) + self.assertEqual(target, stab1 + stab2) + + def test_add_qargs(self): + """Test add method with qargs.""" + stab1 = StabilizerTable.from_labels(['+IIII', '-YYYY']) + stab2 = StabilizerTable.from_labels(['-XY', '+YZ']) + + with self.subTest(msg='qargs=[0, 1]'): + target = StabilizerTable.from_labels( + ['+IIII', '-YYYY', '-IIXY', '+IIYZ']) + self.assertEqual(stab1 + stab2([0, 1]), target) + + with self.subTest(msg='qargs=[0, 3]'): + target = StabilizerTable.from_labels( + ['+IIII', '-YYYY', '-XIIY', '+YIIZ']) + self.assertEqual(stab1 + stab2([0, 3]), target) + + with self.subTest(msg='qargs=[2, 1]'): + target = StabilizerTable.from_labels( + ['+IIII', '-YYYY', '-IYXI', '+IZYI']) + self.assertEqual(stab1 + stab2([2, 1]), target) + + with self.subTest(msg='qargs=[3, 1]'): + target = StabilizerTable.from_labels( + ['+IIII', '-YYYY', '-YIXI', '+ZIYI']) + self.assertEqual(stab1 + stab2([3, 1]), target) + + def test_getitem_methods(self): + """Test __getitem__ method.""" + with self.subTest(msg='__getitem__ single'): + labels = ['+XI', '-IY'] + stab = StabilizerTable.from_labels(labels) + self.assertEqual(stab[0], StabilizerTable(labels[0])) + self.assertEqual(stab[1], StabilizerTable(labels[1])) + + with self.subTest(msg='__getitem__ array'): + labels = np.array(['+XI', '-IY', '+IZ', '-XY', '+ZX']) + stab = StabilizerTable.from_labels(labels) + inds = [0, 3] + self.assertEqual(stab[inds], + StabilizerTable.from_labels(labels[inds])) + inds = np.array([4, 1]) + self.assertEqual(stab[inds], + StabilizerTable.from_labels(labels[inds])) + + with self.subTest(msg='__getitem__ slice'): + labels = np.array(['+XI', '-IY', '+IZ', '-XY', '+ZX']) + stab = StabilizerTable.from_labels(labels) + self.assertEqual(stab[:], stab) + self.assertEqual(stab[1:3], + StabilizerTable.from_labels(labels[1:3])) + + def test_setitem_methods(self): + """Test __setitem__ method.""" + with self.subTest(msg='__setitem__ single'): + labels = ['+XI', 'IY'] + stab = StabilizerTable.from_labels(['+XI', 'IY']) + stab[0] = '+II' + self.assertEqual(stab[0], StabilizerTable('+II')) + stab[1] = '-XX' + self.assertEqual(stab[1], StabilizerTable('-XX')) + + def raises_single(): + # Wrong size Pauli + stab[0] = '+XXX' + + self.assertRaises(Exception, raises_single) + + with self.subTest(msg='__setitem__ array'): + labels = np.array(['+XI', '-IY', '+IZ']) + stab = StabilizerTable.from_labels(labels) + target = StabilizerTable.from_labels(['+II', '-ZZ']) + inds = [2, 0] + stab[inds] = target + self.assertEqual(stab[inds], target) + + def raises_array(): + stab[inds] = StabilizerTable.from_labels(['+YY', '-ZZ', '+XX']) + self.assertRaises(Exception, raises_array) + + with self.subTest(msg='__setitem__ slice'): + labels = np.array(5 * ['+III']) + stab = StabilizerTable.from_labels(labels) + target = StabilizerTable.from_labels(5 * ['-XXX']) + stab[:] = target + self.assertEqual(stab[:], target) + target = StabilizerTable.from_labels(2 * ['+ZZZ']) + stab[1:3] = target + self.assertEqual(stab[1:3], target) + + +class TestStabilizerTableLabels(QiskitTestCase): + """Tests for StabilizerTable label converions.""" + + def test_from_labels_1q(self): + """Test 1-qubit from_labels method.""" + labels = ['I', 'X', 'Y', 'Z', + '+I', '+X', '+Y', '+Z', + '-I', '-X', '-Y', '-Z'] + array = np.vstack(3 * [np.array([[False, False], + [True, False], + [True, True], + [False, True]], + dtype=np.bool)]) + phase = np.array(8 * [False] + 4 * [True], dtype=np.bool) + target = StabilizerTable(array, phase) + value = StabilizerTable.from_labels(labels) + self.assertEqual(target, value) + + def test_from_labels_2q(self): + """Test 2-qubit from_labels method.""" + labels = ['II', '-YY', '+XZ'] + array = np.array([[False, False, False, False], + [True, True, True, True], + [False, True, True, False]], + dtype=np.bool) + phase = np.array([False, True, False]) + target = StabilizerTable(array, phase) + value = StabilizerTable.from_labels(labels) + self.assertEqual(target, value) + + def test_from_labels_5q(self): + """Test 5-qubit from_labels method.""" + labels = ['IIIII', '-XXXXX', 'YYYYY', 'ZZZZZ'] + array = np.array([10 * [False], + 5 * [True] + 5 * [False], + 10 * [True], + 5 * [False] + 5 * [True]], + dtype=np.bool) + phase = np.array([False, True, False, False]) + target = StabilizerTable(array, phase) + value = StabilizerTable.from_labels(labels) + self.assertEqual(target, value) + + def test_to_labels_1q(self): + """Test 1-qubit to_labels method.""" + array = np.vstack(2 * [np.array([[False, False], + [True, False], + [True, True], + [False, True]], + dtype=np.bool)]) + phase = np.array(4 * [False] + 4 * [True], dtype=np.bool) + value = StabilizerTable(array, phase).to_labels() + target = ['+I', '+X', '+Y', '+Z', '-I', '-X', '-Y', '-Z'] + self.assertEqual(value, target) + + def test_to_labels_1q_array(self): + """Test 1-qubit to_labels method w/ array=True.""" + array = np.vstack(2 * [np.array([[False, False], + [True, False], + [True, True], + [False, True]], + dtype=np.bool)]) + phase = np.array(4 * [False] + 4 * [True], dtype=np.bool) + value = StabilizerTable(array, phase).to_labels(array=True) + target = np.array(['+I', '+X', '+Y', '+Z', + '-I', '-X', '-Y', '-Z']) + self.assertTrue(np.all(value == target)) + + def test_labels_round_trip(self): + """Test from_labels and to_labels round trip.""" + target = ['+III', '-IXZ', '-XYI', '+ZZZ'] + value = StabilizerTable.from_labels(target).to_labels() + self.assertEqual(value, target) + + def test_labels_round_trip_array(self): + """Test from_labels and to_labels round trip w/ array=True.""" + labels = ['+III', '-IXZ', '-XYI', '+ZZZ'] + target = np.array(labels) + value = StabilizerTable.from_labels( + labels).to_labels(array=True) + self.assertTrue(np.all(value == target)) + + +class TestStabilizerTableMatrix(QiskitTestCase): + """Tests for StabilizerTable matrix converions.""" + + def test_to_matrix_1q(self): + """Test 1-qubit to_matrix method.""" + labels = ['+I', '+X', '+Y', '+Z', '-I', '-X', '-Y', '-Z'] + targets = [stab_mat(i) for i in labels] + values = StabilizerTable.from_labels(labels).to_matrix() + self.assertTrue(isinstance(values, list)) + for target, value in zip(targets, values): + self.assertTrue(np.all(value == target)) + + def test_to_matrix_1q_array(self): + """Test 1-qubit to_matrix method w/ array=True.""" + labels = ['+I', '+X', '+Y', '+Z', '-I', '-X', '-Y', '-Z'] + target = np.array([stab_mat(i) for i in labels]) + value = StabilizerTable.from_labels( + labels).to_matrix(array=True) + self.assertTrue(isinstance(value, np.ndarray)) + self.assertTrue(np.all(value == target)) + + def test_to_matrix_1q_sparse(self): + """Test 1-qubit to_matrix method w/ sparse=True.""" + labels = ['+I', '+X', '+Y', '+Z', '-I', '-X', '-Y', '-Z'] + targets = [stab_mat(i) for i in labels] + values = StabilizerTable.from_labels( + labels).to_matrix(sparse=True) + for mat, targ in zip(values, targets): + self.assertTrue(isinstance(mat, csr_matrix)) + self.assertTrue(np.all(targ == mat.toarray())) + + def test_to_matrix_2q(self): + """Test 2-qubit to_matrix method.""" + labels = ['+IX', '-YI', '-II', '+ZZ'] + targets = [stab_mat(i) for i in labels] + values = StabilizerTable.from_labels(labels).to_matrix() + self.assertTrue(isinstance(values, list)) + for target, value in zip(targets, values): + self.assertTrue(np.all(value == target)) + + def test_to_matrix_2q_array(self): + """Test 2-qubit to_matrix method w/ array=True.""" + labels = ['-ZZ', '-XY', '+YX', '-IZ'] + target = np.array([stab_mat(i) for i in labels]) + value = StabilizerTable.from_labels( + labels).to_matrix(array=True) + self.assertTrue(isinstance(value, np.ndarray)) + self.assertTrue(np.all(value == target)) + + def test_to_matrix_2q_sparse(self): + """Test 2-qubit to_matrix method w/ sparse=True.""" + labels = ['+IX', '+II', '-ZY', '-YZ'] + targets = [stab_mat(i) for i in labels] + values = StabilizerTable.from_labels( + labels).to_matrix(sparse=True) + for mat, targ in zip(values, targets): + self.assertTrue(isinstance(mat, csr_matrix)) + self.assertTrue(np.all(targ == mat.toarray())) + + def test_to_matrix_5q(self): + """Test 5-qubit to_matrix method.""" + labels = ['IXIXI', 'YZIXI', 'IIXYZ'] + targets = [stab_mat(i) for i in labels] + values = StabilizerTable.from_labels(labels).to_matrix() + self.assertTrue(isinstance(values, list)) + for target, value in zip(targets, values): + self.assertTrue(np.all(value == target)) + + def test_to_matrix_5q_sparse(self): + """Test 5-qubit to_matrix method w/ sparse=True.""" + labels = ['-XXXYY', 'IXIZY', '-ZYXIX', '+ZXIYZ'] + targets = [stab_mat(i) for i in labels] + values = StabilizerTable.from_labels( + labels).to_matrix(sparse=True) + for mat, targ in zip(values, targets): + self.assertTrue(isinstance(mat, csr_matrix)) + self.assertTrue(np.all(targ == mat.toarray())) + + +class TestStabilizerTableMethods(QiskitTestCase): + """Tests for StabilizerTable methods.""" + + def test_sort(self): + """Test sort method.""" + with self.subTest(msg='1 qubit'): + unsrt = ['X', '-Z', 'I', 'Y', '-X', 'Z'] + srt = ['I', 'X', '-X', 'Y', '-Z', 'Z'] + target = StabilizerTable.from_labels(srt) + value = StabilizerTable.from_labels(unsrt).sort() + self.assertEqual(target, value) + + with self.subTest(msg='1 qubit weight order'): + unsrt = ['X', '-Z', 'I', 'Y', '-X', 'Z'] + srt = ['I', 'X', '-X', 'Y', '-Z', 'Z'] + target = StabilizerTable.from_labels(srt) + value = StabilizerTable.from_labels(unsrt).sort(weight=True) + self.assertEqual(target, value) + + with self.subTest(msg='2 qubit standard order'): + srt_p = ['II', 'IX', 'IY', 'XI', 'XX', 'XY', 'XZ', 'YI', + 'YX', 'YY', 'YZ', 'ZI', 'ZX', 'ZY', 'ZZ'] + srt_m = ['-' + i for i in srt_p] + + unsrt_p = srt_p.copy() + np.random.shuffle(unsrt_p) + unsrt_m = srt_m.copy() + np.random.shuffle(unsrt_m) + + # Sort with + cases all first in shuffled list + srt = [val for pair in zip(srt_p, srt_m) for val in pair] + unsrt = unsrt_p + unsrt_m + target = StabilizerTable.from_labels(srt) + value = StabilizerTable.from_labels(unsrt).sort() + self.assertEqual(target, value) + + # Sort with - cases all first in shuffled list + srt = [val for pair in zip(srt_m, srt_p) for val in pair] + unsrt = unsrt_m + unsrt_p + target = StabilizerTable.from_labels(srt) + value = StabilizerTable.from_labels(unsrt).sort() + self.assertEqual(target, value) + + with self.subTest(msg='2 qubit weight order'): + srt_p = ['II', 'IX', 'IY', 'IZ', 'XI', 'YI', 'ZI', 'XX', + 'XY', 'XZ', 'YX', 'YY', 'YZ', 'ZX', 'ZY', 'ZZ'] + srt_m = ['-' + i for i in srt_p] + + unsrt_p = srt_p.copy() + np.random.shuffle(unsrt_p) + unsrt_m = srt_m.copy() + np.random.shuffle(unsrt_m) + + # Sort with + cases all first in shuffled list + srt = [val for pair in zip(srt_p, srt_m) for val in pair] + unsrt = unsrt_p + unsrt_m + target = StabilizerTable.from_labels(srt) + value = StabilizerTable.from_labels(unsrt).sort(weight=True) + self.assertEqual(target, value) + + # Sort with - cases all first in shuffled list + srt = [val for pair in zip(srt_m, srt_p) for val in pair] + unsrt = unsrt_m + unsrt_p + target = StabilizerTable.from_labels(srt) + value = StabilizerTable.from_labels(unsrt).sort(weight=True) + self.assertEqual(target, value) + + def test_unique(self): + """Test unique method.""" + with self.subTest(msg='1 qubit'): + labels = ['X', 'Z', '-I', '-X', 'X', 'I', 'Y', '-I', '-X', '-Z', 'Z', 'X', 'I'] + unique = ['X', 'Z', '-I', '-X', 'I', 'Y', '-Z'] + target = StabilizerTable.from_labels(unique) + value = StabilizerTable.from_labels(labels).unique() + self.assertEqual(target, value) + + with self.subTest(msg='2 qubit'): + labels = ['XX', 'IX', '-XX', 'XX', '-IZ', 'II', + 'IZ', 'ZI', 'YX', 'YX', 'ZZ', 'IX', 'XI'] + unique = ['XX', 'IX', '-XX', '-IZ', 'II', 'IZ', 'ZI', 'YX', 'ZZ', 'XI'] + target = StabilizerTable.from_labels(unique) + value = StabilizerTable.from_labels(labels).unique() + self.assertEqual(target, value) + + with self.subTest(msg='10 qubit'): + labels = [10 * 'X', '-' + 10 * 'X', '-' + 10 * 'X', 10 * 'I', 10 * 'X'] + unique = [10 * 'X', '-' + 10 * 'X', 10 * 'I'] + target = StabilizerTable.from_labels(unique) + value = StabilizerTable.from_labels(labels).unique() + self.assertEqual(target, value) + + def test_delete(self): + """Test delete method.""" + with self.subTest(msg='single row'): + for j in range(1, 6): + stab = StabilizerTable.from_labels([j * 'X', '-' + j * 'Y']) + self.assertEqual(stab.delete(0), StabilizerTable('-' + j * 'Y')) + self.assertEqual(stab.delete(1), StabilizerTable(j * 'X')) + + with self.subTest(msg='multiple rows'): + for j in range(1, 6): + stab = StabilizerTable.from_labels([j * 'X', '-' + j * 'Y', j * 'Z']) + self.assertEqual(stab.delete([0, 2]), StabilizerTable('-' + j * 'Y')) + self.assertEqual(stab.delete([1, 2]), StabilizerTable(j * 'X')) + self.assertEqual(stab.delete([0, 1]), StabilizerTable(j * 'Z')) + + with self.subTest(msg='single qubit'): + stab = StabilizerTable.from_labels(['IIX', 'IYI', 'ZII']) + value = stab.delete(0, qubit=True) + target = StabilizerTable.from_labels(['II', 'IY', 'ZI']) + self.assertEqual(value, target) + value = stab.delete(1, qubit=True) + target = StabilizerTable.from_labels(['IX', 'II', 'ZI']) + self.assertEqual(value, target) + value = stab.delete(2, qubit=True) + target = StabilizerTable.from_labels(['IX', 'YI', 'II']) + self.assertEqual(value, target) + + with self.subTest(msg='multiple qubits'): + stab = StabilizerTable.from_labels(['IIX', 'IYI', 'ZII']) + value = stab.delete([0, 1], qubit=True) + target = StabilizerTable.from_labels(['I', 'I', 'Z']) + self.assertEqual(value, target) + value = stab.delete([1, 2], qubit=True) + target = StabilizerTable.from_labels(['X', 'I', 'I']) + self.assertEqual(value, target) + value = stab.delete([0, 2], qubit=True) + target = StabilizerTable.from_labels(['I', 'Y', 'I']) + self.assertEqual(value, target) + + def test_insert(self): + """Test insert method.""" + # Insert single row + for j in range(1, 10): + l_px = j * 'X' + l_mi = '-' + j * 'I' + stab = StabilizerTable(l_px) + target0 = StabilizerTable.from_labels([l_mi, l_px]) + target1 = StabilizerTable.from_labels([l_px, l_mi]) + + with self.subTest(msg='single row from str ({})'.format(j)): + value0 = stab.insert(0, l_mi) + self.assertEqual(value0, target0) + value1 = stab.insert(1, l_mi) + self.assertEqual(value1, target1) + + with self.subTest(msg='single row from StabilizerTable ({})'.format(j)): + value0 = stab.insert(0, StabilizerTable(l_mi)) + self.assertEqual(value0, target0) + value1 = stab.insert(1, StabilizerTable(l_mi)) + self.assertEqual(value1, target1) + + # Insert multiple rows + for j in range(1, 10): + stab = StabilizerTable(j * 'X') + insert = StabilizerTable.from_labels(['-' + j * 'I', j * 'Y', + '-' + j * 'Z']) + target0 = insert + stab + target1 = stab + insert + + with self.subTest(msg='multiple-rows from StabilizerTable ({})'.format(j)): + value0 = stab.insert(0, insert) + self.assertEqual(value0, target0) + value1 = stab.insert(1, insert) + self.assertEqual(value1, target1) + + # Insert single column + stab = StabilizerTable.from_labels(['X', 'Y', 'Z']) + for sgn in ['+', '-']: + for i in ['I', 'X', 'Y', 'Z']: + target0 = StabilizerTable.from_labels([ + sgn + 'X' + i, sgn + 'Y' + i, sgn + 'Z' + i]) + target1 = StabilizerTable.from_labels( + [sgn + i + 'X', sgn + i + 'Y', sgn + i + 'Z']) + + with self.subTest(msg='single-column single-val from str' + ' {}'.format(sgn + i)): + value = stab.insert(0, sgn + i, qubit=True) + self.assertEqual(value, target0) + value = stab.insert(1, sgn + i, qubit=True) + self.assertEqual(value, target1) + + with self.subTest(msg='single-column single-val from' + ' StabilizerTable {}'.format(sgn + i)): + value = stab.insert(0, StabilizerTable(sgn + i), qubit=True) + self.assertEqual(value, target0) + value = stab.insert(1, StabilizerTable(sgn + i), qubit=True) + self.assertEqual(value, target1) + + # Insert single column with multiple values + stab = StabilizerTable.from_labels(['X', 'Y', 'Z']) + for i in [('I', 'X', 'Y'), ('X', 'Y', 'Z'), ('Y', 'Z', 'I')]: + target0 = StabilizerTable.from_labels(['X' + i[0], 'Y' + i[1], 'Z' + i[2]]) + target1 = StabilizerTable.from_labels([i[0] + 'X', i[1] + 'Y', i[2] + 'Z']) + + with self.subTest(msg='single-column multiple-vals from StabilizerTable'): + value = stab.insert(0, StabilizerTable.from_labels(i), qubit=True) + self.assertEqual(value, target0) + value = stab.insert(1, StabilizerTable.from_labels(i), qubit=True) + self.assertEqual(value, target1) + + with self.subTest(msg='single-column multiple-vals from array'): + value = stab.insert(0, StabilizerTable.from_labels(i).array, qubit=True) + self.assertEqual(value, target0) + value = stab.insert(1, StabilizerTable.from_labels(i).array, qubit=True) + self.assertEqual(value, target1) + + # Insert multiple columns from single + stab = StabilizerTable.from_labels(['X', 'Y', 'Z']) + for j in range(1, 5): + for i in [j * 'I', j * 'X', j * 'Y', j * 'Z']: + target0 = StabilizerTable.from_labels(['X' + i, 'Y' + i, 'Z' + i]) + target1 = StabilizerTable.from_labels([i + 'X', i + 'Y', i + 'Z']) + + with self.subTest(msg='multiple-columns single-val from str'): + value = stab.insert(0, i, qubit=True) + self.assertEqual(value, target0) + value = stab.insert(1, i, qubit=True) + self.assertEqual(value, target1) + + with self.subTest(msg='multiple-columns single-val from StabilizerTable'): + value = stab.insert(0, StabilizerTable(i), qubit=True) + self.assertEqual(value, target0) + value = stab.insert(1, StabilizerTable(i), qubit=True) + self.assertEqual(value, target1) + + with self.subTest(msg='multiple-columns single-val from array'): + value = stab.insert(0, StabilizerTable(i).array, qubit=True) + self.assertEqual(value, target0) + value = stab.insert(1, StabilizerTable(i).array, qubit=True) + self.assertEqual(value, target1) + + # Insert multiple columns multiple row values + stab = StabilizerTable.from_labels(['X', 'Y', 'Z']) + for j in range(1, 5): + for i in [(j * 'I', j * 'X', j * 'Y'), + (j * 'X', j * 'Z', j * 'Y'), + (j * 'Y', j * 'Z', j * 'I')]: + target0 = StabilizerTable.from_labels(['X' + i[0], 'Y' + i[1], 'Z' + i[2]]) + target1 = StabilizerTable.from_labels([i[0] + 'X', i[1] + 'Y', i[2] + 'Z']) + + with self.subTest(msg='multiple-column multiple-vals from StabilizerTable'): + value = stab.insert(0, StabilizerTable.from_labels(i), qubit=True) + self.assertEqual(value, target0) + value = stab.insert(1, StabilizerTable.from_labels(i), qubit=True) + self.assertEqual(value, target1) + + with self.subTest(msg='multiple-column multiple-vals from array'): + value = stab.insert(0, StabilizerTable.from_labels(i).array, qubit=True) + self.assertEqual(value, target0) + value = stab.insert(1, StabilizerTable.from_labels(i).array, qubit=True) + self.assertEqual(value, target1) + + def test_iteration(self): + """Test iteration methods.""" + + labels = ['+III', '+IXI', '-IYY', '+YIZ', '-ZIZ', '+XYZ', '-III'] + stab = StabilizerTable.from_labels(labels) + + with self.subTest(msg='enumerate'): + for idx, i in enumerate(stab): + self.assertEqual(i, StabilizerTable(labels[idx])) + + with self.subTest(msg='iter'): + for idx, i in enumerate(iter(stab)): + self.assertEqual(i, StabilizerTable(labels[idx])) + + with self.subTest(msg='zip'): + for label, i in zip(labels, stab): + self.assertEqual(i, StabilizerTable(label)) + + with self.subTest(msg='label_iter'): + for idx, i in enumerate(stab.label_iter()): + self.assertEqual(i, labels[idx]) + + with self.subTest(msg='matrix_iter (dense)'): + for idx, i in enumerate(stab.matrix_iter()): + self.assertTrue(np.all(i == stab_mat(labels[idx]))) + + with self.subTest(msg='matrix_iter (sparse)'): + for idx, i in enumerate(stab.matrix_iter(sparse=True)): + self.assertTrue(isinstance(i, csr_matrix)) + self.assertTrue(np.all(i.toarray() == stab_mat(labels[idx]))) + + def test_tensor(self): + """Test tensor and expand methods.""" + labels1 = ['-XX', 'YY'] + labels2 = ['III', '-ZZZ'] + stab1 = StabilizerTable.from_labels(labels1) + stab2 = StabilizerTable.from_labels(labels2) + + with self.subTest(msg='tensor'): + target = StabilizerTable.from_labels( + ['-XXIII', 'XXZZZ', 'YYIII', '-YYZZZ']) + value = stab1.tensor(stab2) + self.assertEqual(value, target) + + with self.subTest(msg='expand'): + target = StabilizerTable.from_labels( + ['-IIIXX', 'ZZZXX', 'IIIYY', '-ZZZYY']) + value = stab1.expand(stab2) + self.assertEqual(value, target) + + def test_compose(self): + """Test compose and dot methods.""" + + # Test single qubit Pauli dot products + stab = StabilizerTable.from_labels(['I', 'X', 'Y', 'Z']) + + # Test single qubit Pauli dot products + stab = StabilizerTable.from_labels( + ['I', 'X', 'Y', 'Z', '-I', '-X', '-Y', '-Z']) + + with self.subTest(msg='dot single I'): + value = stab.compose('I') + target = StabilizerTable.from_labels( + ['I', 'X', 'Y', 'Z', '-I', '-X', '-Y', '-Z']) + self.assertEqual(target, value) + + with self.subTest(msg='dot single -I'): + value = stab.compose('-I') + target = StabilizerTable.from_labels( + ['-I', '-X', '-Y', '-Z', 'I', 'X', 'Y', 'Z']) + self.assertEqual(target, value) + + with self.subTest(msg='dot single I'): + value = stab.dot('I') + target = StabilizerTable.from_labels( + ['I', 'X', 'Y', 'Z', '-I', '-X', '-Y', '-Z']) + self.assertEqual(target, value) + + with self.subTest(msg='dot single -I'): + value = stab.dot('-I') + target = StabilizerTable.from_labels( + ['-I', '-X', '-Y', '-Z', 'I', 'X', 'Y', 'Z']) + self.assertEqual(target, value) + + with self.subTest(msg='compose single X'): + value = stab.compose('X') + target = StabilizerTable.from_labels( + ['X', 'I', '-Z', 'Y', '-X', '-I', 'Z', '-Y']) + self.assertEqual(target, value) + + with self.subTest(msg='compose single -X'): + value = stab.compose('-X') + target = StabilizerTable.from_labels( + ['-X', '-I', 'Z', '-Y', 'X', 'I', '-Z', 'Y']) + self.assertEqual(target, value) + + with self.subTest(msg='dot single X'): + value = stab.dot('X') + target = StabilizerTable.from_labels( + ['X', 'I', 'Z', '-Y', '-X', '-I', '-Z', 'Y']) + self.assertEqual(target, value) + + with self.subTest(msg='dot single -X'): + value = stab.dot('-X') + target = StabilizerTable.from_labels( + ['-X', '-I', '-Z', 'Y', 'X', 'I', 'Z', '-Y']) + self.assertEqual(target, value) + + with self.subTest(msg='compose single Y'): + value = stab.compose('Y') + target = StabilizerTable.from_labels( + ['Y', 'Z', '-I', '-X', '-Y', '-Z', 'I', 'X']) + self.assertEqual(target, value) + + with self.subTest(msg='compose single -Y'): + value = stab.compose('-Y') + target = StabilizerTable.from_labels( + ['-Y', '-Z', 'I', 'X', 'Y', 'Z', '-I', '-X']) + self.assertEqual(target, value) + + with self.subTest(msg='dot single Y'): + value = stab.dot('Y') + target = StabilizerTable.from_labels( + ['Y', '-Z', '-I', 'X', '-Y', 'Z', 'I', '-X']) + self.assertEqual(target, value) + + with self.subTest(msg='dot single -Y'): + value = stab.dot('-Y') + target = StabilizerTable.from_labels( + ['-Y', 'Z', 'I', '-X', 'Y', '-Z', '-I', 'X']) + self.assertEqual(target, value) + + with self.subTest(msg='compose single Z'): + value = stab.compose('Z') + target = StabilizerTable.from_labels( + ['Z', '-Y', 'X', 'I', '-Z', 'Y', '-X', '-I']) + self.assertEqual(target, value) + + with self.subTest(msg='compose single -Z'): + value = stab.compose('-Z') + target = StabilizerTable.from_labels( + ['-Z', 'Y', '-X', '-I', 'Z', '-Y', 'X', 'I']) + self.assertEqual(target, value) + + with self.subTest(msg='dot single Z'): + value = stab.dot('Z') + target = StabilizerTable.from_labels( + ['Z', 'Y', '-X', 'I', '-Z', '-Y', 'X', '-I']) + self.assertEqual(target, value) + + with self.subTest(msg='dot single -Z'): + value = stab.dot('-Z') + target = StabilizerTable.from_labels( + ['-Z', '-Y', 'X', '-I', 'Z', 'Y', '-X', 'I']) + self.assertEqual(target, value) + + def test_compose_qargs(self): + """Test compose and dot methods with qargs.""" + + # Dot product with qargs + stab1 = StabilizerTable.from_labels( + ['III', '-XXX', 'YYY', '-ZZZ']) + + # 1-qubit qargs + stab2 = StabilizerTable('-Z') + + with self.subTest(msg='dot 1-qubit qargs=[0]'): + target = StabilizerTable.from_labels([ + '-IIZ', 'XXY', 'YYX', 'ZZI']) + value = stab1.dot(stab2, qargs=[0]) + self.assertEqual(value, target) + + with self.subTest(msg='compose 1-qubit qargs=[0]'): + target = StabilizerTable.from_labels([ + '-IIZ', '-XXY', '-YYX', 'ZZI']) + value = stab1.compose(stab2, qargs=[0]) + self.assertEqual(value, target) + + with self.subTest(msg='dot 1-qubit qargs=[1]'): + target = StabilizerTable.from_labels([ + '-IZI', 'XYX', 'YXY', 'ZIZ']) + value = stab1.dot(stab2, qargs=[1]) + self.assertEqual(value, target) + + with self.subTest(msg='compose 1-qubit qargs=[1]'): + value = stab1.compose(stab2, qargs=[1]) + target = StabilizerTable.from_labels([ + '-IZI', '-XYX', '-YXY', 'ZIZ']) + self.assertEqual(value, target) + + target = StabilizerTable.from_labels(['ZII', 'YXX']) + with self.subTest(msg='dot 1-qubit qargs=[2]'): + value = stab1.dot(stab2, qargs=[2]) + target = StabilizerTable.from_labels([ + '-ZII', 'YXX', 'XYY', 'IZZ']) + self.assertEqual(value, target) + + with self.subTest(msg='compose 1-qubit qargs=[2]'): + value = stab1.compose(stab2, qargs=[2]) + target = StabilizerTable.from_labels([ + '-ZII', '-YXX', '-XYY', 'IZZ']) + self.assertEqual(value, target) + + # 2-qubit qargs + stab2 = StabilizerTable('-ZY') + with self.subTest(msg='dot 2-qubit qargs=[0, 1]'): + value = stab1.dot(stab2, qargs=[0, 1]) + target = StabilizerTable.from_labels([ + '-IZY', '-XYZ', '-YXI', 'ZIX']) + self.assertEqual(value, target) + + with self.subTest(msg='compose 2-qubit qargs=[0, 1]'): + value = stab1.compose(stab2, qargs=[0, 1]) + target = StabilizerTable.from_labels([ + '-IZY', '-XYZ', 'YXI', '-ZIX']) + self.assertEqual(value, target) + + target = StabilizerTable.from_labels(['YIZ', 'ZXY']) + with self.subTest(msg='dot 2-qubit qargs=[2, 0]'): + value = stab1.dot(stab2, qargs=[2, 0]) + target = StabilizerTable.from_labels([ + '-YIZ', '-ZXY', '-IYX', 'XZI']) + self.assertEqual(value, target) + + with self.subTest(msg='compose 2-qubit qargs=[2, 0]'): + value = stab1.compose(stab2, qargs=[2, 0]) + target = StabilizerTable.from_labels([ + '-YIZ', '-ZXY', 'IYX', '-XZI']) + self.assertEqual(value, target) + + # 3-qubit qargs + stab2 = StabilizerTable('-XYZ') + + target = StabilizerTable.from_labels(['XYZ', 'IZY']) + with self.subTest(msg='dot 3-qubit qargs=None'): + value = stab1.dot(stab2, qargs=[0, 1, 2]) + target = StabilizerTable.from_labels([ + '-XYZ', '-IZY', '-ZIX', '-YXI']) + self.assertEqual(value, target) + + with self.subTest(msg='dot 3-qubit qargs=[0, 1, 2]'): + value = stab1.dot(stab2, qargs=[0, 1, 2]) + target = StabilizerTable.from_labels([ + '-XYZ', '-IZY', '-ZIX', '-YXI']) + self.assertEqual(value, target) + + with self.subTest(msg='dot 3-qubit qargs=[2, 1, 0]'): + value = stab1.dot(stab2, qargs=[2, 1, 0]) + target = StabilizerTable.from_labels([ + '-ZYX', '-YZI', '-XIZ', '-IXY']) + self.assertEqual(value, target) + + with self.subTest(msg='compose 3-qubit qargs=[2, 1, 0]'): + value = stab1.compose(stab2, qargs=[2, 1, 0]) + target = StabilizerTable.from_labels([ + '-ZYX', '-YZI', '-XIZ', '-IXY']) + self.assertEqual(value, target) + + +if __name__ == '__main__': + unittest.main()