diff --git a/qiskit/quantum_info/__init__.py b/qiskit/quantum_info/__init__.py index 9573b1b8eeca..ce52414f54ad 100644 --- a/qiskit/quantum_info/__init__.py +++ b/qiskit/quantum_info/__init__.py @@ -28,6 +28,7 @@ ScalarOp SparsePauliOp CNOTDihedral + PauliList PauliTable StabilizerTable pauli_basis diff --git a/qiskit/quantum_info/operators/symplectic/pauli_list.py b/qiskit/quantum_info/operators/symplectic/pauli_list.py index 2490e20f7ffa..1b0fb04e2bd1 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli_list.py +++ b/qiskit/quantum_info/operators/symplectic/pauli_list.py @@ -32,39 +32,60 @@ class PauliList(BasePauli, LinearMixin, GroupMixin): returning a :class:`Pauli` for integer indexes or a :class:`PauliList` for slice or list indices. - **Group Product** - - The matrix multiplication is defined as usual. - - +-------+---+-----+-----+-----+ - | A.B | I | X | Y | Z | - +=======+===+=====+=====+=====+ - | **I** | I | X | Y | Z | - +-------+---+-----+-----+-----+ - | **X** | X | I | iZ | -iY | - +-------+---+-----+-----+-----+ - | **Y** | Y | -iZ | I | iX | - +-------+---+-----+-----+-----+ - | **Z** | Z | iY | -iX | I | - +-------+---+-----+-----+-----+ - - **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]`. + **Initialization** + + A PauliList object can be initialized in several ways. + + ``PauliList(list[str])`` + where strings are same representation with :class:`~qiskit.quantum_info.Pauli`. + + ``PauliList(Pauli) and PauliList(list[Pauli])`` + where Pauli is :class:`~qiskit.quantum_info.Pauli`. + + ``PauliList.from_symplectic(z, x, phase)`` + where ``z`` and ``x`` are 2 dimensional boolean ``numpy.ndarrays`` and ``phase`` is + an integer in ``[0, 1, 2, 3]``. + + For example, + + .. jupyter-execute:: + + import numpy as np + + from qiskit.quantum_info import Pauli, PauliList + + # 1. init from list[str] + pauli_list = PauliList(["II", "+ZI", "-iYY"]) + print("1. ", pauli_list) + + pauli1 = Pauli("iXI") + pauli2 = Pauli("iZZ") + + # 2. init from Pauli + print("2. ", PauliList(pauli1)) + + # 3. init from list[Pauli] + print("3. ", PauliList([pauli1, pauli2])) + + # 4. init from np.ndarray + z = np.array([[True, True], [False, False]]) + x = np.array([[False, True], [True, False]]) + phase = np.array([0, 1]) + pauli_list = PauliList.from_symplectic(z, x) + print("4. ", pauli_list) **Data Access** - Subsets of rows can be accessed using the list access ``[]`` operator and - will return a table view of part of the PauliList. The underlying Numpy - array can be directly accessed using the :attr:`array` property, and the - sub-arrays for only the `X` or `Z` blocks can be accessed using the - :attr:`X` and :attr:`Z` properties respectively. + The individual Paulis can be accessed and updated using the ``[]`` + operator which accepts integer, lists, or slices for selecting subsets + of PauliList. If integer is given, it returns Pauli not PauliList. + + .. jupyter-execute:: + + pauli_list = PauliList(["XX", "ZZ", "IZ"]) + print("Integer: ", repr(pauli_list[1])) + print("List: ", repr(pauli_list[[0, 2]])) + print("Slice: ", repr(pauli_list[0:2])) **Iteration** @@ -80,11 +101,8 @@ def __init__(self, data): """Initialize the PauliList. Args: - data (Pauli or list or tuple): input data for Paulis. If input is - a tuple it must be of the form ``(z, x)`` or (z, x, phase)`` where - ``z`` and ``x`` are 2D boolean Numpy arrays, and phase 1D integer - array from Z_4. If input is a list each item in the list must be - a Pauli object or Pauli str. + data (Pauli or list): input data for Paulis. If input is a list each item in the list + must be a Pauli object or Pauli str. Raises: QiskitError: if input array is invalid shape. @@ -95,13 +113,6 @@ def __init__(self, data): """ if isinstance(data, BasePauli): base_z, base_x, base_phase = data._z, data._x, data._phase - elif isinstance(data, tuple): - if len(data) not in [2, 3]: - raise QiskitError( - "Invalid input tuple for Pauli, input tuple must be" - " `(z, x, phase)` or `(z, x)`" - ) - base_z, base_x, base_phase = self._from_array(*data) elif isinstance(data, StabilizerTable): # Conversion from legacy StabilizerTable base_z, base_x, base_phase = self._from_array(data.Z, data.X, 2 * data.phase) @@ -358,7 +369,7 @@ def delete(self, ind, qubit=False): 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((z, x, self.phase)) + return PauliList.from_symplectic(z, x, self.phase) def insert(self, ind, value, qubit=False): """Insert Pauli's into the table. @@ -423,7 +434,7 @@ def insert(self, ind, value, qubit=False): z = np.hstack([self.z[:, :ind], value_z, self.z[:, ind:]]) x = np.hstack([self.x[:, :ind], value_x, self.x[:, ind:]]) - return PauliList((z, x, self.phase)) + return PauliList.from_symplectic(z, x, self.phase) def argsort(self, weight=False, phase=False): """Return indices for sorting the rows of the table. @@ -1025,3 +1036,22 @@ def __getitem__(self, key): ) return MatrixIterator(self) + + # --------------------------------------------------------------------- + # Class methods + # --------------------------------------------------------------------- + + @classmethod + 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)) diff --git a/qiskit/quantum_info/operators/symplectic/random.py b/qiskit/quantum_info/operators/symplectic/random.py index b214b5381277..eb05ad5cb73a 100644 --- a/qiskit/quantum_info/operators/symplectic/random.py +++ b/qiskit/quantum_info/operators/symplectic/random.py @@ -74,8 +74,8 @@ def random_pauli_list(num_qubits, size=1, seed=None, phase=True): x = rng.integers(2, size=(size, num_qubits)).astype(bool) if phase: _phase = rng.integers(4, size=(size)) - return PauliList(BasePauli(z, x, _phase)) - return PauliList((z, x)) + return PauliList.from_symplectic(z, x, _phase) + return PauliList.from_symplectic(z, x) def random_pauli_table(num_qubits, size=1, seed=None): diff --git a/test/python/quantum_info/operators/symplectic/test_pauli_list.py b/test/python/quantum_info/operators/symplectic/test_pauli_list.py index f1191748b409..786237be1d13 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli_list.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli_list.py @@ -33,7 +33,7 @@ YGate, ZGate, ) -from qiskit.quantum_info.operators import Operator, PauliList, PauliTable +from qiskit.quantum_info.operators import Operator, PauliList, PauliTable, StabilizerTable from qiskit.quantum_info.random import random_clifford, random_pauli_list from qiskit.test import QiskitTestCase @@ -64,68 +64,78 @@ def test_array_init(self): """Test array initialization.""" # Matrix array initialization with self.subTest(msg="bool array"): - target = (np.array([[False], [True]]), np.array([[False], [True]])) - pauli_list = PauliList(target) - value = (pauli_list.z, pauli_list.x) - self.assertTupleEqual(value, target) + z = np.array([[False], [True]]) + x = np.array([[False], [True]]) + pauli_list = PauliList.from_symplectic(z, x) + np.testing.assert_equal(pauli_list.z, z) + np.testing.assert_equal(pauli_list.x, x) with self.subTest(msg="bool array no copy"): - target = (np.array([[False], [True]]), np.array([[True], [True]])) - pauli_list = PauliList(target) - value = (pauli_list.z, pauli_list.x) - value[0][0, 0] = not value[0][0, 0] - self.assertTupleEqual(value, target) + z = np.array([[False], [True]]) + x = np.array([[True], [True]]) + pauli_list = PauliList.from_symplectic(z, x) + z[0, 0] = not z[0, 0] + np.testing.assert_equal(pauli_list.z, z) + np.testing.assert_equal(pauli_list.x, x) def test_string_init(self): """Test string initialization.""" # String initialization with self.subTest(msg='str init "I"'): pauli_list = PauliList("I") - target = (np.array([[False]]), np.array([[False]])) - np.testing.assert_equal(pauli_list.z, target[0]) - np.testing.assert_equal(pauli_list.x, target[1]) + z = np.array([[False]]) + x = np.array([[False]]) + np.testing.assert_equal(pauli_list.z, z) + np.testing.assert_equal(pauli_list.x, x) with self.subTest(msg='str init "X"'): pauli_list = PauliList("X") - target = (np.array([[False]]), np.array([[True]])) - np.testing.assert_equal(pauli_list.z, target[0]) - np.testing.assert_equal(pauli_list.x, target[1]) + z = np.array([[False]]) + x = np.array([[True]]) + np.testing.assert_equal(pauli_list.z, z) + np.testing.assert_equal(pauli_list.x, x) with self.subTest(msg='str init "Y"'): pauli_list = PauliList("Y") - target = (np.array([[True]]), np.array([[True]])) - np.testing.assert_equal(pauli_list.z, target[0]) - np.testing.assert_equal(pauli_list.x, target[1]) + z = np.array([[True]]) + x = np.array([[True]]) + np.testing.assert_equal(pauli_list.z, z) + np.testing.assert_equal(pauli_list.x, x) with self.subTest(msg='str init "Z"'): pauli_list = PauliList("Z") - target = (np.array([[True]]), np.array([[False]])) - np.testing.assert_equal(pauli_list.z, target[0]) - np.testing.assert_equal(pauli_list.x, target[1]) + z = np.array([[True]]) + x = np.array([[False]]) + np.testing.assert_equal(pauli_list.z, z) + np.testing.assert_equal(pauli_list.x, x) with self.subTest(msg='str init "IX"'): pauli_list = PauliList("IX") - target = (np.array([[False, False]]), np.array([[True, False]])) - np.testing.assert_equal(pauli_list.z, target[0]) - np.testing.assert_equal(pauli_list.x, target[1]) + z = np.array([[False, False]]) + x = np.array([[True, False]]) + np.testing.assert_equal(pauli_list.z, z) + np.testing.assert_equal(pauli_list.x, x) with self.subTest(msg='str init "XI"'): pauli_list = PauliList("XI") - target = (np.array([[False, False]]), np.array([[False, True]])) - np.testing.assert_equal(pauli_list.z, target[0]) - np.testing.assert_equal(pauli_list.x, target[1]) + z = np.array([[False, False]]) + x = np.array([[False, True]]) + np.testing.assert_equal(pauli_list.z, z) + np.testing.assert_equal(pauli_list.x, x) with self.subTest(msg='str init "YZ"'): pauli_list = PauliList("YZ") - target = (np.array([[True, True]]), np.array([[False, True]])) - np.testing.assert_equal(pauli_list.z, target[0]) - np.testing.assert_equal(pauli_list.x, target[1]) + z = np.array([[True, True]]) + x = np.array([[False, True]]) + np.testing.assert_equal(pauli_list.z, z) + np.testing.assert_equal(pauli_list.x, x) with self.subTest(msg='str init "XIZ"'): pauli_list = PauliList("XIZ") - target = (np.array([[True, False, False]]), np.array([[False, False, True]])) - np.testing.assert_equal(pauli_list.z, target[0]) - np.testing.assert_equal(pauli_list.x, target[1]) + z = np.array([[True, False, False]]) + x = np.array([[False, False, True]]) + np.testing.assert_equal(pauli_list.z, z) + np.testing.assert_equal(pauli_list.x, x) def test_list_init(self): """Test list initialization.""" @@ -140,7 +150,7 @@ def test_list_init(self): value[0] = "II" self.assertEqual(value, target) - def test_table_init(self): + def test_pauli_table_init(self): """Test table initialization.""" with self.subTest(msg="PauliTable"): target = PauliTable.from_labels(["XI", "IX", "IZ"]) @@ -153,6 +163,19 @@ def test_table_init(self): value[0] = "II" self.assertEqual(value, target) + def test_stabilizer_table_init(self): + """Test table initialization.""" + with self.subTest(msg="PauliTable"): + target = StabilizerTable.from_labels(["+II", "-XZ"]) + value = PauliList(target) + self.assertEqual(value, target) + + with self.subTest(msg="PauliTable no copy"): + target = StabilizerTable.from_labels(["+YY", "-XZ", "XI"]) + value = PauliList(target) + value[0] = "II" + self.assertEqual(value, target) + @ddt class TestPauliListProperties(QiskitTestCase): @@ -172,14 +195,10 @@ def test_x_property(self): self.assertEqual(pauli, PauliList(["II", "iXY"])) with self.subTest(msg="set X raises"): - - def set_x(): + with self.assertRaises(Exception): pauli = PauliList(["XI", "IZ"]) val = np.array([[False, False, False], [True, True, True]], dtype=bool) pauli.x = val - return pauli - - self.assertRaises(Exception, set_x) def test_z_property(self): """Test Z property""" @@ -195,37 +214,48 @@ def test_z_property(self): self.assertEqual(pauli, PauliList(["XI", "ZZ"])) with self.subTest(msg="set Z raises"): - - def set_z(): + with self.assertRaises(Exception): pauli = PauliList(["XI", "IZ"]) val = np.array([[False, False, False], [True, True, True]], dtype=bool) pauli.z = val - return pauli - - self.assertRaises(Exception, set_z) def test_phase_property(self): """Test phase property""" - pass + with self.subTest(msg="phase"): + pauli = PauliList(["XI", "IZ", "YY", "YI"]) + array = np.array([0, 0, 0, 0], dtype=int) + np.testing.assert_equal(pauli.phase, array) + + with self.subTest(msg="set phase"): + pauli = PauliList(["XI", "IZ"]) + val = np.array([2, 3], dtype=int) + pauli.phase = val + self.assertEqual(pauli, PauliList(["-XI", "iIZ"])) + + with self.subTest(msg="set Z raises"): + with self.assertRaises(Exception): + pauli = PauliList(["XI", "IZ"]) + val = np.array([1, 2, 3], dtype=int) + pauli.phase = val def test_shape_property(self): """Test shape property""" shape = (3, 4) - pauli = PauliList((np.zeros(shape), np.zeros(shape))) + pauli = PauliList.from_symplectic(np.zeros(shape), np.zeros(shape)) self.assertEqual(pauli.shape, shape) @combine(j=range(1, 10)) def test_size_property(self, j): """Test size property""" shape = (j, 4) - pauli = PauliList((np.zeros(shape), np.zeros(shape))) + pauli = PauliList.from_symplectic(np.zeros(shape), np.zeros(shape)) self.assertEqual(len(pauli), j) @combine(j=range(1, 10)) def test_n_qubit_property(self, j): """Test n_qubit property""" shape = (5, j) - pauli = PauliList((np.zeros(shape), np.zeros(shape))) + pauli = PauliList.from_symplectic(np.zeros(shape), np.zeros(shape)) self.assertEqual(pauli.num_qubits, j) def test_eq(self): @@ -304,12 +334,10 @@ def test_setitem_methods(self): pauli[1] = "XX" self.assertEqual(pauli[1], PauliList("XX")) - def raises_single(): + with self.assertRaises(Exception): # Wrong size Pauli pauli[0] = "XXX" - self.assertRaises(Exception, raises_single) - with self.subTest(msg="__setitem__ array"): labels = np.array(["XI", "IY", "IZ"]) pauli = PauliList(labels) @@ -318,11 +346,9 @@ def raises_single(): pauli[inds] = target self.assertEqual(pauli[inds], target) - def raises_array(): + with self.assertRaises(Exception): pauli[inds] = PauliList(["YY", "ZZ", "XX"]) - self.assertRaises(Exception, raises_array) - with self.subTest(msg="__setitem__ slice"): labels = np.array(5 * ["III"]) pauli = PauliList(labels) @@ -344,7 +370,7 @@ def test_from_labels_1q(self): np.array([[False], [True], [True], [False], [True]]), np.array([[False], [False], [False], [True], [True]]), ) - target = PauliList(array) + target = PauliList.from_symplectic(*array) value = PauliList(labels) self.assertEqual(target, value) @@ -355,7 +381,7 @@ def test_from_labels_2q(self): np.array([[False, False], [True, True], [True, False]]), np.array([[False, False], [True, True], [False, True]]), ) - target = PauliList(array) + target = PauliList.from_symplectic(*array) value = PauliList(labels) self.assertEqual(target, value) @@ -370,17 +396,15 @@ def test_from_labels_5q(self): np.array([[False] * 5, [False] * 5, [True] * 5, [True] * 5]), np.array([[False] * 5, [True] * 5, [True] * 5, [False] * 5]), ) - target = PauliList(array) + target = PauliList.from_symplectic(*array) value = PauliList(labels) self.assertEqual(target, value) def test_to_labels_1q(self): """Test 1-qubit to_labels method.""" - pauli = PauliList( - ( - np.array([[False], [True], [True], [False], [True]]), - np.array([[False], [False], [False], [True], [True]]), - ) + pauli = PauliList.from_symplectic( + np.array([[False], [True], [True], [False], [True]]), + np.array([[False], [False], [False], [True], [True]]), ) target = ["I", "Z", "Z", "X", "Y"] value = pauli.to_labels() @@ -388,11 +412,9 @@ def test_to_labels_1q(self): def test_to_labels_1q_array(self): """Test 1-qubit to_labels method w/ array=True.""" - pauli = PauliList( - ( - np.array([[False], [True], [True], [False], [True]]), - np.array([[False], [False], [False], [True], [True]]), - ) + pauli = PauliList.from_symplectic( + np.array([[False], [True], [True], [False], [True]]), + np.array([[False], [False], [False], [True], [True]]), ) target = np.array(["I", "Z", "Z", "X", "Y"]) value = pauli.to_labels(array=True) diff --git a/test/python/quantum_info/operators/test_random.py b/test/python/quantum_info/operators/test_random.py index 962b42dd56c0..e716a3d02636 100644 --- a/test/python/quantum_info/operators/test_random.py +++ b/test/python/quantum_info/operators/test_random.py @@ -14,19 +14,22 @@ import unittest from test import combine -from ddt import ddt + import numpy as np +from ddt import ddt -from qiskit.test import QiskitTestCase -from qiskit.quantum_info import Operator, Stinespring, Choi -from qiskit.quantum_info import Clifford, PauliTable, StabilizerTable -from qiskit.quantum_info.random import random_unitary -from qiskit.quantum_info.random import random_hermitian -from qiskit.quantum_info.random import random_quantum_channel -from qiskit.quantum_info.random import random_clifford -from qiskit.quantum_info.random import random_pauli_table -from qiskit.quantum_info.random import random_stabilizer_table +from qiskit.quantum_info import Choi, Clifford, Operator, PauliTable, StabilizerTable, Stinespring from qiskit.quantum_info.operators.predicates import is_hermitian_matrix +from qiskit.quantum_info.random import ( + random_clifford, + random_hermitian, + random_pauli_list, + random_pauli_table, + random_quantum_channel, + random_stabilizer_table, + random_unitary, +) +from qiskit.test import QiskitTestCase @ddt