From 7495d59e309244e6eb96d37fb612eced64358528 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Fri, 28 Jun 2024 16:31:55 -0400 Subject: [PATCH 01/45] define BitArray.postselect() --- qiskit/primitives/containers/bit_array.py | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 24d52ca4e85a..99280d4d2267 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -464,6 +464,47 @@ def slice_shots(self, indices: int | Sequence[int]) -> "BitArray": arr = arr[..., indices, :] return BitArray(arr, self.num_bits) + def postselect(self, indices: Iterable[int], selection: Iterable[bool]) -> BitArray: + """Post-select this bit array based on sliced equality with a given bitstring. + + .. note:: + If this bit array contains any shape axes, it is first flattened into a long list of shots before + applying post-selection. This is done because :class:`~BitArray` cannot handle ragged + numbers of shots across axes. + + .. note:: + + The convention used by this method is that the index ``0`` corresponds to + the least-significant bit in the :attr:`~array`, or equivalently + the right-most bitstring entry as returned by + :meth:`~get_counts` or :meth:`~get_bitstrings`, etc. + + If this bit array was produced by a sampler, then an index ``i`` corresponds to the + :class:`~.ClassicalRegister` location ``creg[i]``. + + Args: + indices: A list of the indices of the cbits on which to postselect. + This matches the indexing used by BitArray.slice_bits(). + If this bit array was produced by a sampler, then an index ``i`` corresponds to the + :class:`~.ClassicalRegister` location ``creg[i]``. + + selection: A list of bools of length matching ``indices``, with `indices[i]` corresponding to `selection[i]`. + Shots will be discarded unless all cbits specified by `indices` have the values given by `selection`. + + Returns: + A new bit array with ``shape=(), num_bits=data.num_bits, num_shots<=data.num_shots``. + + Raises: + ValueError: If ``max(indices)`` is greater than :attr:`num_bits``. + ValueError: If the lengths of ``selection`` and ``indices`` do not match. + """ + + selection = BitArray.from_bool_array([selection], order='little') + + flattened = self.reshape((), self.size*self.num_shots) + + return flattened[(flattened.slice_bits(indices).array == selection.array).all(axis=-1)] + def expectation_values(self, observables: ObservablesArrayLike) -> NDArray[np.float64]: """Compute the expectation values of the provided observables, broadcasted against this bit array. From 757eda55e27d9f993c331a0f25f21cbb1136f46a Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Fri, 28 Jun 2024 19:02:10 -0400 Subject: [PATCH 02/45] add test for BitArray.postselect() --- .../primitives/containers/test_bit_array.py | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 69f02fd46daa..ee53ec51bdb7 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -695,3 +695,204 @@ def test_expectation_values(self): _ = ba.expectation_values("Z") with self.assertRaisesRegex(ValueError, "is not diagonal"): _ = ba.expectation_values("X" * ba.num_bits) + + def test_postselection(self): + + # import random + # import numpy as np + # from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister + # from qiskit_aer import AerSimulator + # from qiskit_ibm_runtime import SamplerV2 + # from qiskit.primitives.containers.bit_array import BitArray + # from qiskit.circuit.library import IGate, XGate + # from qiskit_aer.noise import QuantumError + # error = QuantumError(noise_ops=[(IGate(), 0.5),(XGate(),0.5)]) + # num_shots = 97 + # num_cregs = 3 + # bits_per_creg = 17 + # depth = 19 # < num_bits/2 + + # seed = 0 + # num_bits = num_cregs * bits_per_creg + # cregs = [ClassicalRegister(bits_per_creg) for _ in range(num_cregs)] + # qc = QuantumCircuit(QuantumRegister(1), *cregs) + # bitpairs = [] + # shuffled_bits = np.arange(num_bits) + # np.random.seed(seed) + # np.random.shuffle(shuffled_bits) + # for i in range(depth): + # qc.append(error,[0]) + # bit1 = shuffled_bits[2*i] + # bit2 = shuffled_bits[2*i+1] + # qc.measure(0,bit1) + # qc.measure(0,bit2) + # bitpairs.append([bit1,bit2]) + # bitpairs = np.array(bitpairs) + + # print(qc.draw(fold=-1)) + + # backend = AerSimulator(seed_simulator=seed) + # sampler = SamplerV2(mode=backend) + + # result = sampler.run([qc], shots=num_shots).result() + # data = result[0].data + # barry = BitArray.concatenate_bits(list(data.values())) + + num_bits = 51 + barry = BitArray( + np.array([ + [0, 66, 98, 212, 66, 84, 20] , + [5, 14, 46, 10, 23, 161, 160] , + [1, 0, 32, 240, 6, 8, 4] , + [1, 79, 2, 36, 69, 204, 18] , + [4, 79, 34, 230, 83, 204, 50] , + [0, 8, 68, 240, 65, 28, 132] , + [1, 4, 104, 56, 6, 185, 4] , + [0, 103, 7, 38, 0, 233, 178] , + [4, 109, 97, 6, 83, 245, 50] , + [1, 102, 35, 36, 70, 204, 16] , + [1, 101, 101, 36, 6, 249, 146] , + [4, 73, 40, 26, 83, 101, 38] , + [1, 72, 0, 214, 69, 68, 52] , + [1, 41, 77, 252, 69, 61, 150] , + [0, 36, 109, 60, 66, 156, 148] , + [4, 78, 66, 52, 81, 220, 20] , + [4, 6, 106, 26, 82, 148, 36] , + [1, 32, 65, 38, 68, 28, 48] , + [0, 34, 11, 216, 64, 4, 4] , + [5, 66, 106, 232, 22, 121, 0] , + [1, 98, 79, 248, 4, 121, 132] , + [1, 41, 69, 210, 69, 20, 166] , + [1, 103, 35, 242, 70, 204, 38] , + [0, 73, 64, 16, 1, 80, 6] , + [1, 65, 8, 236, 4, 105, 18] , + [0, 72, 36, 228, 3, 72, 144] , + [5, 71, 102, 34, 86, 253, 162] , + [5, 41, 101, 50, 23, 57, 166] , + [1, 38, 39, 214, 70, 132, 180] , + [1, 71, 70, 32, 68, 220, 130] , + [5, 5, 72, 26, 84, 181, 38] , + [4, 69, 32, 198, 18, 225, 50] , + [5, 44, 37, 38, 23, 169, 176] , + [5, 108, 105, 24, 23, 241, 4] , + [0, 108, 1, 36, 1, 200, 16] , + [4, 14, 74, 248, 17, 185, 4] , + [1, 77, 32, 54, 7, 200, 54] , + [5, 102, 79, 46, 84, 220, 176] , + [0, 70, 106, 62, 2, 216, 52] , + [1, 15, 74, 24, 69, 181, 6] , + [4, 74, 102, 2, 83, 117, 160] , + [5, 12, 32, 36, 23, 136, 16] , + [1, 109, 109, 56, 7, 249, 134] , + [4, 72, 100, 4, 19, 113, 144] , + [1, 13, 12, 56, 69, 173, 134] , + [1, 45, 37, 38, 7, 136, 178] , + [5, 11, 98, 244, 87, 28, 22] , + [5, 76, 100, 48, 87, 220, 132] , + [5, 70, 46, 10, 86, 196, 160] , + [1, 67, 74, 216, 68, 117, 6] , + [1, 109, 37, 214, 7, 225, 182] , + [5, 14, 78, 58, 21, 185, 164] , + [5, 45, 97, 210, 87, 181, 38] , + [4, 38, 111, 24, 82, 181, 132] , + [0, 76, 4, 224, 65, 204, 128] , + [5, 104, 97, 194, 23, 80, 32] , + [5, 8, 68, 2, 21, 16, 160] , + [0, 40, 45, 62, 3, 8, 180] , + [4, 65, 0, 20, 16, 97, 22] , + [4, 42, 43, 222, 83, 4, 52] , + [1, 8, 0, 212, 69, 4, 20] , + [0, 73, 36, 50, 67, 109, 166] , + [4, 40, 41, 220, 19, 0, 20] , + [0, 110, 103, 196, 67, 245, 144] , + [0, 99, 71, 4, 64, 117, 146] , + [1, 79, 34, 34, 7, 200, 34] , + [1, 72, 32, 2, 71, 101, 32] , + [1, 106, 111, 42, 7, 121, 160] , + [1, 70, 102, 20, 6, 208, 148] , + [0, 32, 33, 228, 66, 45, 16] , + [5, 68, 4, 208, 84, 229, 132] , + [0, 102, 71, 194, 64, 245, 160] , + [4, 0, 76, 234, 80, 28, 160] , + [0, 111, 43, 232, 67, 237, 2] , + [1, 41, 105, 234, 7, 57, 34] , + [0, 46, 99, 50, 3, 185, 36] , + [1, 110, 79, 58, 5, 249, 164] , + [1, 104, 97, 2, 7, 113, 32] , + [5, 69, 108, 62, 86, 253, 182] , + [0, 44, 97, 194, 3, 144, 32] , + [1, 69, 100, 212, 70, 212, 150] , + [5, 79, 14, 238, 85, 204, 178] , + [1, 8, 100, 6, 71, 20, 176] , + [1, 74, 98, 198, 71, 117, 48] , + [4, 77, 68, 242, 17, 216, 166] , + [0, 76, 76, 58, 65, 253, 164] , + [4, 35, 107, 248, 82, 61, 6] , + [1, 98, 43, 46, 70, 109, 48] , + [0, 98, 107, 44, 2, 88, 16] , + [0, 7, 6, 54, 0, 169, 182] , + [4, 8, 100, 52, 19, 24, 148] , + [1, 106, 99, 34, 71, 125, 32] , + [5, 32, 101, 22, 86, 20, 180] , + [5, 5, 40, 10, 86, 132, 34] , + [0, 10, 2, 192, 65, 37, 0] , + [5, 12, 76, 56, 85, 189, 132] , + [5, 1, 32, 214, 22, 0, 54] , + ], dtype='uint8'), + num_bits=num_bits + ) + + bitpairs = np.array([[16, 43], + [27, 35], + [17, 37], + [10, 22], + [31, 30], + [12, 38], + [20, 50], + [33, 41], + [14, 46], + [29, 11], + [ 8, 13], + [26, 4], + [ 2, 28], + [34, 7], + [45, 32], + [40, 1], + [18, 48], + [42, 15], + [25, 5]], dtype=int) + + np.random.seed(0) + + id = Pauli('I'*num_bits) + + expectations_ps0 = [] + expectations_ps1 = [] + for _ in range(50): + np.random.shuffle(bitpairs) + num_bitpairs = np.random.randint(1,10) + bitpair_subset = bitpairs[:num_bitpairs] + print(f'{num_bitpairs = }') + + obs = id.copy() + obs[bitpair_subset[:,1]] = 'Z' + + selection = np.random.randint(0,2,size=num_bitpairs,dtype=bool) + + barry_ps0 = barry.postselect(indices=bitpair_subset[:,0], selection=selection) + if barry_ps0.num_shots > 0: + expt = barry_ps0.expectation_values(obs) + thy = (-1)**np.sum(selection) + expectations_ps0.append([thy, expt]) + + barry_ps1 = barry.postselect(indices=bitpair_subset[:,0], selection=np.logical_not(selection)) + if barry_ps1.num_shots > 0: + expt = barry_ps1.expectation_values(obs) + thy = (-1)**np.sum(np.logical_not(selection)) + expectations_ps1.append([thy, expt]) + + expectations_ps0 = np.array(expectations_ps0, dtype=float) + expectations_ps1 = np.array(expectations_ps1, dtype=float) + + np.testing.assert_allclose(expectations_ps0[:,0], expectations_ps0[:,1]) + np.testing.assert_allclose(expectations_ps1[:,0], expectations_ps1[:,1]) \ No newline at end of file From cb0bb695a722ad9d462bd9d01b372e19886fecec Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Sat, 29 Jun 2024 12:15:03 -0400 Subject: [PATCH 03/45] lint --- qiskit/primitives/containers/bit_array.py | 35 ++- .../primitives/containers/test_bit_array.py | 294 +++++++++--------- 2 files changed, 170 insertions(+), 159 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 99280d4d2267..869be4fb3856 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -468,28 +468,29 @@ def postselect(self, indices: Iterable[int], selection: Iterable[bool]) -> BitAr """Post-select this bit array based on sliced equality with a given bitstring. .. note:: - If this bit array contains any shape axes, it is first flattened into a long list of shots before - applying post-selection. This is done because :class:`~BitArray` cannot handle ragged - numbers of shots across axes. + If this bit array contains any shape axes, it is first flattened into a long list of shots + before applying post-selection. This is done because :class:`~BitArray` cannot handle + ragged numbers of shots across axes. - .. note:: - - The convention used by this method is that the index ``0`` corresponds to - the least-significant bit in the :attr:`~array`, or equivalently - the right-most bitstring entry as returned by - :meth:`~get_counts` or :meth:`~get_bitstrings`, etc. + .. note:: + + The convention used by this method is that the index ``0`` corresponds to + the least-significant bit in the :attr:`~array`, or equivalently + the right-most bitstring entry as returned by + :meth:`~get_counts` or :meth:`~get_bitstrings`, etc. - If this bit array was produced by a sampler, then an index ``i`` corresponds to the - :class:`~.ClassicalRegister` location ``creg[i]``. + If this bit array was produced by a sampler, then an index ``i`` corresponds to the + :class:`~.ClassicalRegister` location ``creg[i]``. Args: indices: A list of the indices of the cbits on which to postselect. This matches the indexing used by BitArray.slice_bits(). - If this bit array was produced by a sampler, then an index ``i`` corresponds to the - :class:`~.ClassicalRegister` location ``creg[i]``. + If this bit array was produced by a sampler, then an index ``i`` corresponds to the + :class:`~.ClassicalRegister` location ``creg[i]``. - selection: A list of bools of length matching ``indices``, with `indices[i]` corresponding to `selection[i]`. - Shots will be discarded unless all cbits specified by `indices` have the values given by `selection`. + selection: A list of bools of length matching ``indices``, with `indices[i]` corresponding + to `selection[i]`. Shots will be discarded unless all cbits specified by `indices` have + the values given by `selection`. Returns: A new bit array with ``shape=(), num_bits=data.num_bits, num_shots<=data.num_shots``. @@ -499,9 +500,9 @@ def postselect(self, indices: Iterable[int], selection: Iterable[bool]) -> BitAr ValueError: If the lengths of ``selection`` and ``indices`` do not match. """ - selection = BitArray.from_bool_array([selection], order='little') + selection = BitArray.from_bool_array([selection], order="little") - flattened = self.reshape((), self.size*self.num_shots) + flattened = self.reshape((), self.size * self.num_shots) return flattened[(flattened.slice_bits(indices).array == selection.array).all(axis=-1)] diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index ee53ec51bdb7..3b5afa015f33 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -697,7 +697,7 @@ def test_expectation_values(self): _ = ba.expectation_values("X" * ba.num_bits) def test_postselection(self): - + """Test the postselection method. (Commented code was used to generate test data).""" # import random # import numpy as np # from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister @@ -737,162 +737,172 @@ def test_postselection(self): # result = sampler.run([qc], shots=num_shots).result() # data = result[0].data # barry = BitArray.concatenate_bits(list(data.values())) - + num_bits = 51 barry = BitArray( - np.array([ - [0, 66, 98, 212, 66, 84, 20] , - [5, 14, 46, 10, 23, 161, 160] , - [1, 0, 32, 240, 6, 8, 4] , - [1, 79, 2, 36, 69, 204, 18] , - [4, 79, 34, 230, 83, 204, 50] , - [0, 8, 68, 240, 65, 28, 132] , - [1, 4, 104, 56, 6, 185, 4] , - [0, 103, 7, 38, 0, 233, 178] , - [4, 109, 97, 6, 83, 245, 50] , - [1, 102, 35, 36, 70, 204, 16] , - [1, 101, 101, 36, 6, 249, 146] , - [4, 73, 40, 26, 83, 101, 38] , - [1, 72, 0, 214, 69, 68, 52] , - [1, 41, 77, 252, 69, 61, 150] , - [0, 36, 109, 60, 66, 156, 148] , - [4, 78, 66, 52, 81, 220, 20] , - [4, 6, 106, 26, 82, 148, 36] , - [1, 32, 65, 38, 68, 28, 48] , - [0, 34, 11, 216, 64, 4, 4] , - [5, 66, 106, 232, 22, 121, 0] , - [1, 98, 79, 248, 4, 121, 132] , - [1, 41, 69, 210, 69, 20, 166] , - [1, 103, 35, 242, 70, 204, 38] , - [0, 73, 64, 16, 1, 80, 6] , - [1, 65, 8, 236, 4, 105, 18] , - [0, 72, 36, 228, 3, 72, 144] , - [5, 71, 102, 34, 86, 253, 162] , - [5, 41, 101, 50, 23, 57, 166] , - [1, 38, 39, 214, 70, 132, 180] , - [1, 71, 70, 32, 68, 220, 130] , - [5, 5, 72, 26, 84, 181, 38] , - [4, 69, 32, 198, 18, 225, 50] , - [5, 44, 37, 38, 23, 169, 176] , - [5, 108, 105, 24, 23, 241, 4] , - [0, 108, 1, 36, 1, 200, 16] , - [4, 14, 74, 248, 17, 185, 4] , - [1, 77, 32, 54, 7, 200, 54] , - [5, 102, 79, 46, 84, 220, 176] , - [0, 70, 106, 62, 2, 216, 52] , - [1, 15, 74, 24, 69, 181, 6] , - [4, 74, 102, 2, 83, 117, 160] , - [5, 12, 32, 36, 23, 136, 16] , - [1, 109, 109, 56, 7, 249, 134] , - [4, 72, 100, 4, 19, 113, 144] , - [1, 13, 12, 56, 69, 173, 134] , - [1, 45, 37, 38, 7, 136, 178] , - [5, 11, 98, 244, 87, 28, 22] , - [5, 76, 100, 48, 87, 220, 132] , - [5, 70, 46, 10, 86, 196, 160] , - [1, 67, 74, 216, 68, 117, 6] , - [1, 109, 37, 214, 7, 225, 182] , - [5, 14, 78, 58, 21, 185, 164] , - [5, 45, 97, 210, 87, 181, 38] , - [4, 38, 111, 24, 82, 181, 132] , - [0, 76, 4, 224, 65, 204, 128] , - [5, 104, 97, 194, 23, 80, 32] , - [5, 8, 68, 2, 21, 16, 160] , - [0, 40, 45, 62, 3, 8, 180] , - [4, 65, 0, 20, 16, 97, 22] , - [4, 42, 43, 222, 83, 4, 52] , - [1, 8, 0, 212, 69, 4, 20] , - [0, 73, 36, 50, 67, 109, 166] , - [4, 40, 41, 220, 19, 0, 20] , - [0, 110, 103, 196, 67, 245, 144] , - [0, 99, 71, 4, 64, 117, 146] , - [1, 79, 34, 34, 7, 200, 34] , - [1, 72, 32, 2, 71, 101, 32] , - [1, 106, 111, 42, 7, 121, 160] , - [1, 70, 102, 20, 6, 208, 148] , - [0, 32, 33, 228, 66, 45, 16] , - [5, 68, 4, 208, 84, 229, 132] , - [0, 102, 71, 194, 64, 245, 160] , - [4, 0, 76, 234, 80, 28, 160] , - [0, 111, 43, 232, 67, 237, 2] , - [1, 41, 105, 234, 7, 57, 34] , - [0, 46, 99, 50, 3, 185, 36] , - [1, 110, 79, 58, 5, 249, 164] , - [1, 104, 97, 2, 7, 113, 32] , - [5, 69, 108, 62, 86, 253, 182] , - [0, 44, 97, 194, 3, 144, 32] , - [1, 69, 100, 212, 70, 212, 150] , - [5, 79, 14, 238, 85, 204, 178] , - [1, 8, 100, 6, 71, 20, 176] , - [1, 74, 98, 198, 71, 117, 48] , - [4, 77, 68, 242, 17, 216, 166] , - [0, 76, 76, 58, 65, 253, 164] , - [4, 35, 107, 248, 82, 61, 6] , - [1, 98, 43, 46, 70, 109, 48] , - [0, 98, 107, 44, 2, 88, 16] , - [0, 7, 6, 54, 0, 169, 182] , - [4, 8, 100, 52, 19, 24, 148] , - [1, 106, 99, 34, 71, 125, 32] , - [5, 32, 101, 22, 86, 20, 180] , - [5, 5, 40, 10, 86, 132, 34] , - [0, 10, 2, 192, 65, 37, 0] , - [5, 12, 76, 56, 85, 189, 132] , - [5, 1, 32, 214, 22, 0, 54] , - ], dtype='uint8'), - num_bits=num_bits - ) - - bitpairs = np.array([[16, 43], - [27, 35], - [17, 37], - [10, 22], - [31, 30], - [12, 38], - [20, 50], - [33, 41], - [14, 46], - [29, 11], - [ 8, 13], - [26, 4], - [ 2, 28], - [34, 7], - [45, 32], - [40, 1], - [18, 48], - [42, 15], - [25, 5]], dtype=int) - - np.random.seed(0) - - id = Pauli('I'*num_bits) + np.array( + [ + [0, 66, 98, 212, 66, 84, 20], + [5, 14, 46, 10, 23, 161, 160], + [1, 0, 32, 240, 6, 8, 4], + [1, 79, 2, 36, 69, 204, 18], + [4, 79, 34, 230, 83, 204, 50], + [0, 8, 68, 240, 65, 28, 132], + [1, 4, 104, 56, 6, 185, 4], + [0, 103, 7, 38, 0, 233, 178], + [4, 109, 97, 6, 83, 245, 50], + [1, 102, 35, 36, 70, 204, 16], + [1, 101, 101, 36, 6, 249, 146], + [4, 73, 40, 26, 83, 101, 38], + [1, 72, 0, 214, 69, 68, 52], + [1, 41, 77, 252, 69, 61, 150], + [0, 36, 109, 60, 66, 156, 148], + [4, 78, 66, 52, 81, 220, 20], + [4, 6, 106, 26, 82, 148, 36], + [1, 32, 65, 38, 68, 28, 48], + [0, 34, 11, 216, 64, 4, 4], + [5, 66, 106, 232, 22, 121, 0], + [1, 98, 79, 248, 4, 121, 132], + [1, 41, 69, 210, 69, 20, 166], + [1, 103, 35, 242, 70, 204, 38], + [0, 73, 64, 16, 1, 80, 6], + [1, 65, 8, 236, 4, 105, 18], + [0, 72, 36, 228, 3, 72, 144], + [5, 71, 102, 34, 86, 253, 162], + [5, 41, 101, 50, 23, 57, 166], + [1, 38, 39, 214, 70, 132, 180], + [1, 71, 70, 32, 68, 220, 130], + [5, 5, 72, 26, 84, 181, 38], + [4, 69, 32, 198, 18, 225, 50], + [5, 44, 37, 38, 23, 169, 176], + [5, 108, 105, 24, 23, 241, 4], + [0, 108, 1, 36, 1, 200, 16], + [4, 14, 74, 248, 17, 185, 4], + [1, 77, 32, 54, 7, 200, 54], + [5, 102, 79, 46, 84, 220, 176], + [0, 70, 106, 62, 2, 216, 52], + [1, 15, 74, 24, 69, 181, 6], + [4, 74, 102, 2, 83, 117, 160], + [5, 12, 32, 36, 23, 136, 16], + [1, 109, 109, 56, 7, 249, 134], + [4, 72, 100, 4, 19, 113, 144], + [1, 13, 12, 56, 69, 173, 134], + [1, 45, 37, 38, 7, 136, 178], + [5, 11, 98, 244, 87, 28, 22], + [5, 76, 100, 48, 87, 220, 132], + [5, 70, 46, 10, 86, 196, 160], + [1, 67, 74, 216, 68, 117, 6], + [1, 109, 37, 214, 7, 225, 182], + [5, 14, 78, 58, 21, 185, 164], + [5, 45, 97, 210, 87, 181, 38], + [4, 38, 111, 24, 82, 181, 132], + [0, 76, 4, 224, 65, 204, 128], + [5, 104, 97, 194, 23, 80, 32], + [5, 8, 68, 2, 21, 16, 160], + [0, 40, 45, 62, 3, 8, 180], + [4, 65, 0, 20, 16, 97, 22], + [4, 42, 43, 222, 83, 4, 52], + [1, 8, 0, 212, 69, 4, 20], + [0, 73, 36, 50, 67, 109, 166], + [4, 40, 41, 220, 19, 0, 20], + [0, 110, 103, 196, 67, 245, 144], + [0, 99, 71, 4, 64, 117, 146], + [1, 79, 34, 34, 7, 200, 34], + [1, 72, 32, 2, 71, 101, 32], + [1, 106, 111, 42, 7, 121, 160], + [1, 70, 102, 20, 6, 208, 148], + [0, 32, 33, 228, 66, 45, 16], + [5, 68, 4, 208, 84, 229, 132], + [0, 102, 71, 194, 64, 245, 160], + [4, 0, 76, 234, 80, 28, 160], + [0, 111, 43, 232, 67, 237, 2], + [1, 41, 105, 234, 7, 57, 34], + [0, 46, 99, 50, 3, 185, 36], + [1, 110, 79, 58, 5, 249, 164], + [1, 104, 97, 2, 7, 113, 32], + [5, 69, 108, 62, 86, 253, 182], + [0, 44, 97, 194, 3, 144, 32], + [1, 69, 100, 212, 70, 212, 150], + [5, 79, 14, 238, 85, 204, 178], + [1, 8, 100, 6, 71, 20, 176], + [1, 74, 98, 198, 71, 117, 48], + [4, 77, 68, 242, 17, 216, 166], + [0, 76, 76, 58, 65, 253, 164], + [4, 35, 107, 248, 82, 61, 6], + [1, 98, 43, 46, 70, 109, 48], + [0, 98, 107, 44, 2, 88, 16], + [0, 7, 6, 54, 0, 169, 182], + [4, 8, 100, 52, 19, 24, 148], + [1, 106, 99, 34, 71, 125, 32], + [5, 32, 101, 22, 86, 20, 180], + [5, 5, 40, 10, 86, 132, 34], + [0, 10, 2, 192, 65, 37, 0], + [5, 12, 76, 56, 85, 189, 132], + [5, 1, 32, 214, 22, 0, 54], + ], + dtype="uint8", + ), + num_bits=num_bits, + ) + + bitpairs = np.array( + [ + [16, 43], + [27, 35], + [17, 37], + [10, 22], + [31, 30], + [12, 38], + [20, 50], + [33, 41], + [14, 46], + [29, 11], + [8, 13], + [26, 4], + [2, 28], + [34, 7], + [45, 32], + [40, 1], + [18, 48], + [42, 15], + [25, 5], + ], + dtype=int, + ) + + np.random.seed(0) + + iden = Pauli("I" * num_bits) expectations_ps0 = [] expectations_ps1 = [] for _ in range(50): np.random.shuffle(bitpairs) - num_bitpairs = np.random.randint(1,10) + num_bitpairs = np.random.randint(1, 10) bitpair_subset = bitpairs[:num_bitpairs] - print(f'{num_bitpairs = }') - - obs = id.copy() - obs[bitpair_subset[:,1]] = 'Z' - - selection = np.random.randint(0,2,size=num_bitpairs,dtype=bool) - - barry_ps0 = barry.postselect(indices=bitpair_subset[:,0], selection=selection) + print(f"{num_bitpairs = }") + + obs = iden.copy() + obs[bitpair_subset[:, 1]] = "Z" + + selection = np.random.randint(0, 2, size=num_bitpairs, dtype=bool) + + barry_ps0 = barry.postselect(indices=bitpair_subset[:, 0], selection=selection) if barry_ps0.num_shots > 0: expt = barry_ps0.expectation_values(obs) - thy = (-1)**np.sum(selection) + thy = (-1) ** np.sum(selection) expectations_ps0.append([thy, expt]) - barry_ps1 = barry.postselect(indices=bitpair_subset[:,0], selection=np.logical_not(selection)) + barry_ps1 = barry.postselect( + indices=bitpair_subset[:, 0], selection=np.logical_not(selection) + ) if barry_ps1.num_shots > 0: expt = barry_ps1.expectation_values(obs) - thy = (-1)**np.sum(np.logical_not(selection)) + thy = (-1) ** np.sum(np.logical_not(selection)) expectations_ps1.append([thy, expt]) - + expectations_ps0 = np.array(expectations_ps0, dtype=float) expectations_ps1 = np.array(expectations_ps1, dtype=float) - np.testing.assert_allclose(expectations_ps0[:,0], expectations_ps0[:,1]) - np.testing.assert_allclose(expectations_ps1[:,0], expectations_ps1[:,1]) \ No newline at end of file + np.testing.assert_allclose(expectations_ps0[:, 0], expectations_ps0[:, 1]) + np.testing.assert_allclose(expectations_ps1[:, 0], expectations_ps1[:, 1]) From 60799063924624ffe17ec0f7e759d5969b3f96cb Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Sat, 29 Jun 2024 12:20:55 -0400 Subject: [PATCH 04/45] remove redundant docstring text --- qiskit/primitives/containers/bit_array.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 869be4fb3856..4c75ef1b11a9 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -472,16 +472,6 @@ def postselect(self, indices: Iterable[int], selection: Iterable[bool]) -> BitAr before applying post-selection. This is done because :class:`~BitArray` cannot handle ragged numbers of shots across axes. - .. note:: - - The convention used by this method is that the index ``0`` corresponds to - the least-significant bit in the :attr:`~array`, or equivalently - the right-most bitstring entry as returned by - :meth:`~get_counts` or :meth:`~get_bitstrings`, etc. - - If this bit array was produced by a sampler, then an index ``i`` corresponds to the - :class:`~.ClassicalRegister` location ``creg[i]``. - Args: indices: A list of the indices of the cbits on which to postselect. This matches the indexing used by BitArray.slice_bits(). From 2e79728664c761c585f3fb4503195761def5761a Mon Sep 17 00:00:00 2001 From: aeddins-ibm <60495383+aeddins-ibm@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:57:21 -0400 Subject: [PATCH 05/45] Update qiskit/primitives/containers/bit_array.py Co-authored-by: Ian Hincks --- qiskit/primitives/containers/bit_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 4c75ef1b11a9..b90f523c82f2 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -474,7 +474,7 @@ def postselect(self, indices: Iterable[int], selection: Iterable[bool]) -> BitAr Args: indices: A list of the indices of the cbits on which to postselect. - This matches the indexing used by BitArray.slice_bits(). + This matches the indexing used by :meth:`~slice_bits`. If this bit array was produced by a sampler, then an index ``i`` corresponds to the :class:`~.ClassicalRegister` location ``creg[i]``. From 033aa637a1f0cf64c10b2f74df0f7a046925a739 Mon Sep 17 00:00:00 2001 From: aeddins-ibm <60495383+aeddins-ibm@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:58:55 -0400 Subject: [PATCH 06/45] docstring ticks (BitArray.postselect()) Co-authored-by: Ian Hincks --- qiskit/primitives/containers/bit_array.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index b90f523c82f2..c4847f90b1ee 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -478,9 +478,9 @@ def postselect(self, indices: Iterable[int], selection: Iterable[bool]) -> BitAr If this bit array was produced by a sampler, then an index ``i`` corresponds to the :class:`~.ClassicalRegister` location ``creg[i]``. - selection: A list of bools of length matching ``indices``, with `indices[i]` corresponding - to `selection[i]`. Shots will be discarded unless all cbits specified by `indices` have - the values given by `selection`. + selection: A list of bools of length matching ``indices``, with ``indices[i]`` corresponding + to ``selection[i]``. Shots will be discarded unless all cbits specified by ``indices`` have + the values given by ``selection``. Returns: A new bit array with ``shape=(), num_bits=data.num_bits, num_shots<=data.num_shots``. From efdad57e366e49480a9aac36ed3124f53e3701ff Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 2 Jul 2024 17:00:06 -0400 Subject: [PATCH 07/45] Simpler tests for BitArray.postselect --- .../primitives/containers/test_bit_array.py | 251 ++++-------------- 1 file changed, 46 insertions(+), 205 deletions(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 3b5afa015f33..a7bec6339047 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -697,212 +697,53 @@ def test_expectation_values(self): _ = ba.expectation_values("X" * ba.num_bits) def test_postselection(self): - """Test the postselection method. (Commented code was used to generate test data).""" - # import random - # import numpy as np - # from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister - # from qiskit_aer import AerSimulator - # from qiskit_ibm_runtime import SamplerV2 - # from qiskit.primitives.containers.bit_array import BitArray - # from qiskit.circuit.library import IGate, XGate - # from qiskit_aer.noise import QuantumError - # error = QuantumError(noise_ops=[(IGate(), 0.5),(XGate(),0.5)]) - # num_shots = 97 - # num_cregs = 3 - # bits_per_creg = 17 - # depth = 19 # < num_bits/2 - - # seed = 0 - # num_bits = num_cregs * bits_per_creg - # cregs = [ClassicalRegister(bits_per_creg) for _ in range(num_cregs)] - # qc = QuantumCircuit(QuantumRegister(1), *cregs) - # bitpairs = [] - # shuffled_bits = np.arange(num_bits) - # np.random.seed(seed) - # np.random.shuffle(shuffled_bits) - # for i in range(depth): - # qc.append(error,[0]) - # bit1 = shuffled_bits[2*i] - # bit2 = shuffled_bits[2*i+1] - # qc.measure(0,bit1) - # qc.measure(0,bit2) - # bitpairs.append([bit1,bit2]) - # bitpairs = np.array(bitpairs) - - # print(qc.draw(fold=-1)) - - # backend = AerSimulator(seed_simulator=seed) - # sampler = SamplerV2(mode=backend) - - # result = sampler.run([qc], shots=num_shots).result() - # data = result[0].data - # barry = BitArray.concatenate_bits(list(data.values())) - - num_bits = 51 - barry = BitArray( - np.array( - [ - [0, 66, 98, 212, 66, 84, 20], - [5, 14, 46, 10, 23, 161, 160], - [1, 0, 32, 240, 6, 8, 4], - [1, 79, 2, 36, 69, 204, 18], - [4, 79, 34, 230, 83, 204, 50], - [0, 8, 68, 240, 65, 28, 132], - [1, 4, 104, 56, 6, 185, 4], - [0, 103, 7, 38, 0, 233, 178], - [4, 109, 97, 6, 83, 245, 50], - [1, 102, 35, 36, 70, 204, 16], - [1, 101, 101, 36, 6, 249, 146], - [4, 73, 40, 26, 83, 101, 38], - [1, 72, 0, 214, 69, 68, 52], - [1, 41, 77, 252, 69, 61, 150], - [0, 36, 109, 60, 66, 156, 148], - [4, 78, 66, 52, 81, 220, 20], - [4, 6, 106, 26, 82, 148, 36], - [1, 32, 65, 38, 68, 28, 48], - [0, 34, 11, 216, 64, 4, 4], - [5, 66, 106, 232, 22, 121, 0], - [1, 98, 79, 248, 4, 121, 132], - [1, 41, 69, 210, 69, 20, 166], - [1, 103, 35, 242, 70, 204, 38], - [0, 73, 64, 16, 1, 80, 6], - [1, 65, 8, 236, 4, 105, 18], - [0, 72, 36, 228, 3, 72, 144], - [5, 71, 102, 34, 86, 253, 162], - [5, 41, 101, 50, 23, 57, 166], - [1, 38, 39, 214, 70, 132, 180], - [1, 71, 70, 32, 68, 220, 130], - [5, 5, 72, 26, 84, 181, 38], - [4, 69, 32, 198, 18, 225, 50], - [5, 44, 37, 38, 23, 169, 176], - [5, 108, 105, 24, 23, 241, 4], - [0, 108, 1, 36, 1, 200, 16], - [4, 14, 74, 248, 17, 185, 4], - [1, 77, 32, 54, 7, 200, 54], - [5, 102, 79, 46, 84, 220, 176], - [0, 70, 106, 62, 2, 216, 52], - [1, 15, 74, 24, 69, 181, 6], - [4, 74, 102, 2, 83, 117, 160], - [5, 12, 32, 36, 23, 136, 16], - [1, 109, 109, 56, 7, 249, 134], - [4, 72, 100, 4, 19, 113, 144], - [1, 13, 12, 56, 69, 173, 134], - [1, 45, 37, 38, 7, 136, 178], - [5, 11, 98, 244, 87, 28, 22], - [5, 76, 100, 48, 87, 220, 132], - [5, 70, 46, 10, 86, 196, 160], - [1, 67, 74, 216, 68, 117, 6], - [1, 109, 37, 214, 7, 225, 182], - [5, 14, 78, 58, 21, 185, 164], - [5, 45, 97, 210, 87, 181, 38], - [4, 38, 111, 24, 82, 181, 132], - [0, 76, 4, 224, 65, 204, 128], - [5, 104, 97, 194, 23, 80, 32], - [5, 8, 68, 2, 21, 16, 160], - [0, 40, 45, 62, 3, 8, 180], - [4, 65, 0, 20, 16, 97, 22], - [4, 42, 43, 222, 83, 4, 52], - [1, 8, 0, 212, 69, 4, 20], - [0, 73, 36, 50, 67, 109, 166], - [4, 40, 41, 220, 19, 0, 20], - [0, 110, 103, 196, 67, 245, 144], - [0, 99, 71, 4, 64, 117, 146], - [1, 79, 34, 34, 7, 200, 34], - [1, 72, 32, 2, 71, 101, 32], - [1, 106, 111, 42, 7, 121, 160], - [1, 70, 102, 20, 6, 208, 148], - [0, 32, 33, 228, 66, 45, 16], - [5, 68, 4, 208, 84, 229, 132], - [0, 102, 71, 194, 64, 245, 160], - [4, 0, 76, 234, 80, 28, 160], - [0, 111, 43, 232, 67, 237, 2], - [1, 41, 105, 234, 7, 57, 34], - [0, 46, 99, 50, 3, 185, 36], - [1, 110, 79, 58, 5, 249, 164], - [1, 104, 97, 2, 7, 113, 32], - [5, 69, 108, 62, 86, 253, 182], - [0, 44, 97, 194, 3, 144, 32], - [1, 69, 100, 212, 70, 212, 150], - [5, 79, 14, 238, 85, 204, 178], - [1, 8, 100, 6, 71, 20, 176], - [1, 74, 98, 198, 71, 117, 48], - [4, 77, 68, 242, 17, 216, 166], - [0, 76, 76, 58, 65, 253, 164], - [4, 35, 107, 248, 82, 61, 6], - [1, 98, 43, 46, 70, 109, 48], - [0, 98, 107, 44, 2, 88, 16], - [0, 7, 6, 54, 0, 169, 182], - [4, 8, 100, 52, 19, 24, 148], - [1, 106, 99, 34, 71, 125, 32], - [5, 32, 101, 22, 86, 20, 180], - [5, 5, 40, 10, 86, 132, 34], - [0, 10, 2, 192, 65, 37, 0], - [5, 12, 76, 56, 85, 189, 132], - [5, 1, 32, 214, 22, 0, 54], - ], - dtype="uint8", - ), - num_bits=num_bits, - ) - - bitpairs = np.array( - [ - [16, 43], - [27, 35], - [17, 37], - [10, 22], - [31, 30], - [12, 38], - [20, 50], - [33, 41], - [14, 46], - [29, 11], - [8, 13], - [26, 4], - [2, 28], - [34, 7], - [45, 32], - [40, 1], - [18, 48], - [42, 15], - [25, 5], - ], - dtype=int, - ) - - np.random.seed(0) - - iden = Pauli("I" * num_bits) - - expectations_ps0 = [] - expectations_ps1 = [] - for _ in range(50): - np.random.shuffle(bitpairs) - num_bitpairs = np.random.randint(1, 10) - bitpair_subset = bitpairs[:num_bitpairs] - print(f"{num_bitpairs = }") - - obs = iden.copy() - obs[bitpair_subset[:, 1]] = "Z" - - selection = np.random.randint(0, 2, size=num_bitpairs, dtype=bool) + """Test the postselection method.""" + + bool_array = np.array([ + [ + [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 1, 1], + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + ], + [ + [1, 0, 1, 0, 1, 0, 1, 0, 1, 0], + [1, 1, 1, 1, 1, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + ], + ] + ], dtype=bool) - barry_ps0 = barry.postselect(indices=bitpair_subset[:, 0], selection=selection) - if barry_ps0.num_shots > 0: - expt = barry_ps0.expectation_values(obs) - thy = (-1) ** np.sum(selection) - expectations_ps0.append([thy, expt]) + bit_array = BitArray.from_bool_array(bool_array, order='little') + # indices[i] <-> creg[i] <-> bool_array[..., i] - barry_ps1 = barry.postselect( - indices=bitpair_subset[:, 0], selection=np.logical_not(selection) - ) - if barry_ps1.num_shots > 0: - expt = barry_ps1.expectation_values(obs) - thy = (-1) ** np.sum(np.logical_not(selection)) - expectations_ps1.append([thy, expt]) + num_bits = bool_array.shape[-1] + bool_array = bool_array.reshape(-1, num_bits) - expectations_ps0 = np.array(expectations_ps0, dtype=float) - expectations_ps1 = np.array(expectations_ps1, dtype=float) + test_cases = [ + ("basic", [0,1], [0,0]), + ("multibyte", [0,9], [0,1]), + ("repeated", [5,5,5], [0,0,0]), + ("contradict", [5,5,5], [0,0,1]), + ("unsorted", [5,0,9,3], [1, 0, 1, 0]) + ] - np.testing.assert_allclose(expectations_ps0[:, 0], expectations_ps0[:, 1]) - np.testing.assert_allclose(expectations_ps1[:, 0], expectations_ps1[:, 1]) + for (name, indices, selection) in test_cases: + with self.subTest(name): + answer = bool_array[np.all(bool_array[:,indices] == selection, axis=-1)] + postselected_bools = np.unpackbits( + bit_array.postselect(indices, selection).array[:, ::-1], + count=num_bits, + axis=-1, + bitorder='little').astype(bool) + self.assertTrue((postselected_bools==answer).all()) + + error_cases = [ + ("negative", [-1, 0, 6], [1, 1]), + ("out_of_range", [0, 6, 14], [1, 1]), + ] + for (name, indices, selection) in error_cases: + with self.subTest(name): + with self.assertRaises(ValueError): + bit_array.postselect(indices, selection) + \ No newline at end of file From 8cb49208abd9fce73ea1dc9db17daec8a71056ee Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 2 Jul 2024 17:03:27 -0400 Subject: [PATCH 08/45] lint --- .../primitives/containers/test_bit_array.py | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index a7bec6339047..0f8e8ceb4f03 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -699,51 +699,54 @@ def test_expectation_values(self): def test_postselection(self): """Test the postselection method.""" - bool_array = np.array([ - [ - [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 1, 1, 1, 1], - [0, 1, 0, 1, 0, 1, 0, 1, 0, 1], - ], - [ - [1, 0, 1, 0, 1, 0, 1, 0, 1, 0], - [1, 1, 1, 1, 1, 0, 0, 0, 0, 0], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - ], - ] - ], dtype=bool) + bool_array = np.array( + [ + [ + [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 1, 1], + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + ], + [ + [1, 0, 1, 0, 1, 0, 1, 0, 1, 0], + [1, 1, 1, 1, 1, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + ], + ] + ], + dtype=bool, + ) - bit_array = BitArray.from_bool_array(bool_array, order='little') + bit_array = BitArray.from_bool_array(bool_array, order="little") # indices[i] <-> creg[i] <-> bool_array[..., i] num_bits = bool_array.shape[-1] bool_array = bool_array.reshape(-1, num_bits) test_cases = [ - ("basic", [0,1], [0,0]), - ("multibyte", [0,9], [0,1]), - ("repeated", [5,5,5], [0,0,0]), - ("contradict", [5,5,5], [0,0,1]), - ("unsorted", [5,0,9,3], [1, 0, 1, 0]) + ("basic", [0, 1], [0, 0]), + ("multibyte", [0, 9], [0, 1]), + ("repeated", [5, 5, 5], [0, 0, 0]), + ("contradict", [5, 5, 5], [0, 0, 1]), + ("unsorted", [5, 0, 9, 3], [1, 0, 1, 0]), ] - for (name, indices, selection) in test_cases: + for name, indices, selection in test_cases: with self.subTest(name): - answer = bool_array[np.all(bool_array[:,indices] == selection, axis=-1)] + answer = bool_array[np.all(bool_array[:, indices] == selection, axis=-1)] postselected_bools = np.unpackbits( bit_array.postselect(indices, selection).array[:, ::-1], count=num_bits, axis=-1, - bitorder='little').astype(bool) - self.assertTrue((postselected_bools==answer).all()) - + bitorder="little", + ).astype(bool) + self.assertTrue((postselected_bools == answer).all()) + error_cases = [ ("negative", [-1, 0, 6], [1, 1]), ("out_of_range", [0, 6, 14], [1, 1]), ] - for (name, indices, selection) in error_cases: + for name, indices, selection in error_cases: with self.subTest(name): with self.assertRaises(ValueError): bit_array.postselect(indices, selection) - \ No newline at end of file From b2b0a5429027830f313b7e883401bc5d3e707c7a Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 2 Jul 2024 17:59:46 -0400 Subject: [PATCH 09/45] add release note --- releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml diff --git a/releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml b/releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml new file mode 100644 index 000000000000..50119483a227 --- /dev/null +++ b/releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml @@ -0,0 +1,4 @@ +--- +features_primitives: + - | + Added a new method :meth:`.BitArray.postselect` that returns all shots containing specified bit values. \ No newline at end of file From e080cf5a2676391b025cae8a8f414f9da3f759ce Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 2 Jul 2024 18:40:55 -0400 Subject: [PATCH 10/45] check postselect() arg lengths match --- qiskit/primitives/containers/bit_array.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index c4847f90b1ee..bb536e65545a 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -490,6 +490,9 @@ def postselect(self, indices: Iterable[int], selection: Iterable[bool]) -> BitAr ValueError: If the lengths of ``selection`` and ``indices`` do not match. """ + if len(selection) != len(indices): + raise ValueError("Lenghts of indices and selection do not match.") + selection = BitArray.from_bool_array([selection], order="little") flattened = self.reshape((), self.size * self.num_shots) From 9760e1d3841753014c6ec1d508c204f84fb9ebea Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 2 Jul 2024 18:43:52 -0400 Subject: [PATCH 11/45] fix postselect tests - fix bugs with checking that ValueError is raised. - addtionally run all tests on a "flat" data input --- .../primitives/containers/test_bit_array.py | 78 +++++++++++-------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 0f8e8ceb4f03..0dc036e70574 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -699,7 +699,13 @@ def test_expectation_values(self): def test_postselection(self): """Test the postselection method.""" - bool_array = np.array( + flat_data = np.array([ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 1, 1], + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + ]) + + shaped_data = np.array( [ [ [ @@ -717,36 +723,40 @@ def test_postselection(self): dtype=bool, ) - bit_array = BitArray.from_bool_array(bool_array, order="little") - # indices[i] <-> creg[i] <-> bool_array[..., i] - - num_bits = bool_array.shape[-1] - bool_array = bool_array.reshape(-1, num_bits) - - test_cases = [ - ("basic", [0, 1], [0, 0]), - ("multibyte", [0, 9], [0, 1]), - ("repeated", [5, 5, 5], [0, 0, 0]), - ("contradict", [5, 5, 5], [0, 0, 1]), - ("unsorted", [5, 0, 9, 3], [1, 0, 1, 0]), - ] - - for name, indices, selection in test_cases: - with self.subTest(name): - answer = bool_array[np.all(bool_array[:, indices] == selection, axis=-1)] - postselected_bools = np.unpackbits( - bit_array.postselect(indices, selection).array[:, ::-1], - count=num_bits, - axis=-1, - bitorder="little", - ).astype(bool) - self.assertTrue((postselected_bools == answer).all()) - - error_cases = [ - ("negative", [-1, 0, 6], [1, 1]), - ("out_of_range", [0, 6, 14], [1, 1]), - ] - for name, indices, selection in error_cases: - with self.subTest(name): - with self.assertRaises(ValueError): - bit_array.postselect(indices, selection) + for dataname, bool_array in zip(['flat','shaped'], [flat_data, shaped_data]): + + bit_array = BitArray.from_bool_array(bool_array, order="little") + # indices[i] <-> creg[i] <-> bool_array[..., i] + + num_bits = bool_array.shape[-1] + bool_array = bool_array.reshape(-1, num_bits) + + test_cases = [ + ("basic", [0, 1], [0, 0]), + ("multibyte", [0, 9], [0, 1]), + ("repeated", [5, 5, 5], [0, 0, 0]), + ("contradict", [5, 5, 5], [0, 0, 1]), + ("unsorted", [5, 0, 9, 3], [1, 0, 1, 0]), + ] + + for name, indices, selection in test_cases: + with self.subTest(dataname+'_'+name): + answer = bool_array[np.all(bool_array[:, indices] == selection, axis=-1)] + postselected_bools = np.unpackbits( + bit_array.postselect(indices, selection).array[:, ::-1], + count=num_bits, + axis=-1, + bitorder="little", + ).astype(bool) + self.assertTrue((postselected_bools == answer).all()) + + error_cases = [ + ("negative", [-1, 0, 6], [1, 1, 1], ValueError), + ("out_of_range", [0, 6, 14], [1, 1, 0], ValueError), + ("mismatch", [0, 1, 2], [0, 0], ValueError), + ] + for name, indices, selection, error in error_cases: + with self.subTest(dataname+'_'+name): + with self.assertRaises(error) as raised: + bit_array.postselect(indices, selection) + print(name, type(raised.exception)) From 07a2ede05acefa8d4e6e65f313fc91e910479c97 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 2 Jul 2024 18:45:06 -0400 Subject: [PATCH 12/45] lint --- .../primitives/containers/test_bit_array.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 0dc036e70574..e366a3de74ab 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -699,11 +699,13 @@ def test_expectation_values(self): def test_postselection(self): """Test the postselection method.""" - flat_data = np.array([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 1, 1, 1, 1], - [0, 1, 0, 1, 0, 1, 0, 1, 0, 1], - ]) + flat_data = np.array( + [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 1, 1], + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + ] + ) shaped_data = np.array( [ @@ -723,7 +725,7 @@ def test_postselection(self): dtype=bool, ) - for dataname, bool_array in zip(['flat','shaped'], [flat_data, shaped_data]): + for dataname, bool_array in zip(["flat", "shaped"], [flat_data, shaped_data]): bit_array = BitArray.from_bool_array(bool_array, order="little") # indices[i] <-> creg[i] <-> bool_array[..., i] @@ -740,7 +742,7 @@ def test_postselection(self): ] for name, indices, selection in test_cases: - with self.subTest(dataname+'_'+name): + with self.subTest(dataname + "_" + name): answer = bool_array[np.all(bool_array[:, indices] == selection, axis=-1)] postselected_bools = np.unpackbits( bit_array.postselect(indices, selection).array[:, ::-1], @@ -756,7 +758,7 @@ def test_postselection(self): ("mismatch", [0, 1, 2], [0, 0], ValueError), ] for name, indices, selection, error in error_cases: - with self.subTest(dataname+'_'+name): + with self.subTest(dataname + "_" + name): with self.assertRaises(error) as raised: bit_array.postselect(indices, selection) print(name, type(raised.exception)) From fd317bf69a284a65039e7188845d60243494aa22 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 2 Jul 2024 19:00:12 -0400 Subject: [PATCH 13/45] Fix type-hint We immediately check the lengths of these args, so they should be Sequences, not Iterables. --- qiskit/primitives/containers/bit_array.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index bb536e65545a..6224f6cd3ec7 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -464,7 +464,7 @@ def slice_shots(self, indices: int | Sequence[int]) -> "BitArray": arr = arr[..., indices, :] return BitArray(arr, self.num_bits) - def postselect(self, indices: Iterable[int], selection: Iterable[bool]) -> BitArray: + def postselect(self, indices: Sequence[int], selection: Sequence[bool]) -> BitArray: """Post-select this bit array based on sliced equality with a given bitstring. .. note:: @@ -491,7 +491,7 @@ def postselect(self, indices: Iterable[int], selection: Iterable[bool]) -> BitAr """ if len(selection) != len(indices): - raise ValueError("Lenghts of indices and selection do not match.") + raise ValueError("Lengths of indices and selection do not match.") selection = BitArray.from_bool_array([selection], order="little") From 124e567519af59a770ee30a45affedc525f3c58e Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 2 Jul 2024 19:02:22 -0400 Subject: [PATCH 14/45] remove spurious print() --- test/python/primitives/containers/test_bit_array.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index e366a3de74ab..2c8e9ea75089 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -760,5 +760,4 @@ def test_postselection(self): for name, indices, selection, error in error_cases: with self.subTest(dataname + "_" + name): with self.assertRaises(error) as raised: - bit_array.postselect(indices, selection) - print(name, type(raised.exception)) + bit_array.postselect(indices, selection) \ No newline at end of file From 82c50110663e570afbc9299e7012082ffad1859b Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 2 Jul 2024 19:04:14 -0400 Subject: [PATCH 15/45] lint --- test/python/primitives/containers/test_bit_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 2c8e9ea75089..1839c5de0fe1 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -760,4 +760,4 @@ def test_postselection(self): for name, indices, selection, error in error_cases: with self.subTest(dataname + "_" + name): with self.assertRaises(error) as raised: - bit_array.postselect(indices, selection) \ No newline at end of file + bit_array.postselect(indices, selection) From ab87d6644933222cfbf2f703c57e854db6014879 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 2 Jul 2024 23:24:29 -0400 Subject: [PATCH 16/45] lint --- test/python/primitives/containers/test_bit_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 1839c5de0fe1..720ef31fc2a4 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -759,5 +759,5 @@ def test_postselection(self): ] for name, indices, selection, error in error_cases: with self.subTest(dataname + "_" + name): - with self.assertRaises(error) as raised: + with self.assertRaises(error): bit_array.postselect(indices, selection) From 09ec38a670cb1e18923a837c69098dff788d3fb4 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Wed, 3 Jul 2024 17:35:51 -0400 Subject: [PATCH 17/45] use bitwise operations for faster postselect - Also added support for negative indices - Also updated tests --- qiskit/primitives/containers/bit_array.py | 71 +++++++++++++++++-- .../primitives/containers/test_bit_array.py | 52 +++++++++----- 2 files changed, 99 insertions(+), 24 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 6224f6cd3ec7..899ab02225cf 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -464,7 +464,12 @@ def slice_shots(self, indices: int | Sequence[int]) -> "BitArray": arr = arr[..., indices, :] return BitArray(arr, self.num_bits) - def postselect(self, indices: Sequence[int], selection: Sequence[bool]) -> BitArray: + def postselect( + self, + indices: Sequence[int] | int, + selection: Sequence[bool] | bool, + may_have_contradiction: bool = True, + ) -> BitArray: """Post-select this bit array based on sliced equality with a given bitstring. .. note:: @@ -474,14 +479,21 @@ def postselect(self, indices: Sequence[int], selection: Sequence[bool]) -> BitAr Args: indices: A list of the indices of the cbits on which to postselect. - This matches the indexing used by :meth:`~slice_bits`. If this bit array was produced by a sampler, then an index ``i`` corresponds to the - :class:`~.ClassicalRegister` location ``creg[i]``. + :class:`~.ClassicalRegister` location ``creg[i]`` (as in :meth:`~slice_bits`). + Negative indices are allowed. selection: A list of bools of length matching ``indices``, with ``indices[i]`` corresponding to ``selection[i]``. Shots will be discarded unless all cbits specified by ``indices`` have the values given by ``selection``. + may_have_contradiction: If the args ``indices`` and ``selection`` are known not to contain + contradictions, one can set this to False for a moderate speed-up, depending on the problem + size. Here "contradiction" means requiring that a single bit has two different values, e.g. + ``indices = [5, 5]`` and ``selection = [0, 1]``. If a contradiction is present, then setting + to ``False`` may silently produce incorrect results. (If a contradiction is present, the + correct result is to return an empty BitArray). + Returns: A new bit array with ``shape=(), num_bits=data.num_bits, num_shots<=data.num_shots``. @@ -489,15 +501,62 @@ def postselect(self, indices: Sequence[int], selection: Sequence[bool]) -> BitAr ValueError: If ``max(indices)`` is greater than :attr:`num_bits``. ValueError: If the lengths of ``selection`` and ``indices`` do not match. """ + if isinstance(indices, int): + indices = (indices,) + if isinstance(selection, bool): + selection = (selection,) + selection = np.asarray(selection, dtype=bool) + + num_indices = len(indices) - if len(selection) != len(indices): + if len(selection) != num_indices: raise ValueError("Lengths of indices and selection do not match.") - selection = BitArray.from_bool_array([selection], order="little") + num_bytes = self._array.shape[-1] + indices = np.asarray(indices) + + if num_indices > 0 and np.max(indices) >= self.num_bits: + raise ValueError( + f"index {int(np.max(indices))} is out of bounds for the number of bits {self.num_bits}." + ) flattened = self.reshape((), self.size * self.num_shots) - return flattened[(flattened.slice_bits(indices).array == selection.array).all(axis=-1)] + # If no conditions, keep all data, but flatten as promised: + if num_indices == 0: + return flattened + + # Allow for negative bit indices: + is_negative = indices < 0 + indices[is_negative] = np.mod(indices[is_negative], self.num_bits, dtype=int) + + # Check for contradictory conditions: + if may_have_contradiction: + if np.intersect1d(indices[selection], indices[np.logical_not(selection)]).size > 0: + return BitArray(np.empty((0, num_bytes), dtype="uint8"), num_bits=self.num_bits) + + # Recall that creg[0] is the LSb: + byte_significance, bit_significance = np.divmod(indices, 8) + # least-significant byte is at last position: + byte_idx = (num_bytes - 1) - byte_significance + # least-significant bit is at position 0: + bit_offset = bit_significance.astype("uint8") + + # Get bitpacked representation of `indices` (bitmask): + bitmask = np.zeros(num_bytes, dtype="uint8") + np.bitwise_or.at(bitmask, byte_idx, np.uint8(1) << bit_offset) + + # Get bitpacked representation of `selection` (desired bitstring): + selection_bytes = np.zeros(num_bytes, dtype="uint8") + ## This will produce incorrect result if we did not check for contradictions, but there is one: + np.bitwise_or.at( + selection_bytes, byte_idx, np.asarray(selection, dtype="uint8") << bit_offset + ) + + return BitArray( + flattened._array[((flattened._array & bitmask) == selection_bytes).all(axis=-1)], + num_bits=self.num_bits, + ) def expectation_values(self, observables: ObservablesArrayLike) -> NDArray[np.float64]: """Compute the expectation values of the provided observables, broadcasted against diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 720ef31fc2a4..2f8436fe88ef 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -704,7 +704,8 @@ def test_postselection(self): [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 1, 1, 1], [0, 1, 0, 1, 0, 1, 0, 1, 0, 1], - ] + ], + dtype=bool, ) shaped_data = np.array( @@ -734,28 +735,43 @@ def test_postselection(self): bool_array = bool_array.reshape(-1, num_bits) test_cases = [ - ("basic", [0, 1], [0, 0]), - ("multibyte", [0, 9], [0, 1]), - ("repeated", [5, 5, 5], [0, 0, 0]), - ("contradict", [5, 5, 5], [0, 0, 1]), - ("unsorted", [5, 0, 9, 3], [1, 0, 1, 0]), + ("basic", [0, 1], [False, False]), + ("multibyte", [0, 9], [False, True]), + ("repeated", [5, 5, 5], [False, False, False]), + ("contradict", [5, 5, 5], [True, False, False]), + ("unsorted", [5, 0, 9, 3], [True, False, True, False]), + ("negative", [-5, 1, -2, -5], [True, False, True, True]), + ("trivial", [], []), + ("bareindex", 6, False), ] for name, indices, selection in test_cases: - with self.subTest(dataname + "_" + name): - answer = bool_array[np.all(bool_array[:, indices] == selection, axis=-1)] - postselected_bools = np.unpackbits( - bit_array.postselect(indices, selection).array[:, ::-1], - count=num_bits, - axis=-1, - bitorder="little", - ).astype(bool) - self.assertTrue((postselected_bools == answer).all()) + for check_for_contradiction in (True, False): + with self.subTest("_".join([dataname, name, str(check_for_contradiction)])): + print(dataname + "_" + name) + postselected_bools = np.unpackbits( + bit_array.postselect(indices, selection, check_for_contradiction).array[ + :, ::-1 + ], + count=num_bits, + axis=-1, + bitorder="little", + ).astype(bool) + if isinstance(indices, int): + indices = (indices,) + if isinstance(selection, bool): + selection = (selection,) + answer = bool_array[np.all(bool_array[:, indices] == selection, axis=-1)] + if name == "contradict": + if check_for_contradiction: + np.testing.assert_equal(postselected_bools, answer) + else: + self.assertTrue(len(answer) > 0) # avoiding trivial test case + np.testing.assert_equal(postselected_bools, answer) error_cases = [ - ("negative", [-1, 0, 6], [1, 1, 1], ValueError), - ("out_of_range", [0, 6, 14], [1, 1, 0], ValueError), - ("mismatch", [0, 1, 2], [0, 0], ValueError), + ("out_of_range", [0, 6, 14], [True, True, False], ValueError), + ("mismatch", [0, 1, 2], [False, False], ValueError), ] for name, indices, selection, error in error_cases: with self.subTest(dataname + "_" + name): From d688a2abe2120bc0bab9f8c847122a84aa1246e0 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Wed, 3 Jul 2024 17:43:05 -0400 Subject: [PATCH 18/45] remove spurious print() --- test/python/primitives/containers/test_bit_array.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 2f8436fe88ef..f1a79c0f0d0e 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -748,7 +748,6 @@ def test_postselection(self): for name, indices, selection in test_cases: for check_for_contradiction in (True, False): with self.subTest("_".join([dataname, name, str(check_for_contradiction)])): - print(dataname + "_" + name) postselected_bools = np.unpackbits( bit_array.postselect(indices, selection, check_for_contradiction).array[ :, ::-1 From ff580dfa6ca449427028ca78613bd9c3897fcfa5 Mon Sep 17 00:00:00 2001 From: aeddins-ibm <60495383+aeddins-ibm@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:08:00 -0400 Subject: [PATCH 19/45] end final line of release note --- releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml b/releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml index 50119483a227..c9adf7561196 100644 --- a/releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml +++ b/releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml @@ -1,4 +1,4 @@ --- features_primitives: - | - Added a new method :meth:`.BitArray.postselect` that returns all shots containing specified bit values. \ No newline at end of file + Added a new method :meth:`.BitArray.postselect` that returns all shots containing specified bit values. From 17ac5ecaf7498f0b9e8e200ca4adae76105421df Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Wed, 3 Jul 2024 19:11:54 -0400 Subject: [PATCH 20/45] try to fix docstring formatting --- qiskit/primitives/containers/bit_array.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 899ab02225cf..7ed25f4d23bb 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -488,11 +488,11 @@ def postselect( the values given by ``selection``. may_have_contradiction: If the args ``indices`` and ``selection`` are known not to contain - contradictions, one can set this to False for a moderate speed-up, depending on the problem - size. Here "contradiction" means requiring that a single bit has two different values, e.g. - ``indices = [5, 5]`` and ``selection = [0, 1]``. If a contradiction is present, then setting - to ``False`` may silently produce incorrect results. (If a contradiction is present, the - correct result is to return an empty BitArray). + contradictions, one can set this to False for a moderate speed-up, depending on the problem + size. Here "contradiction" means requiring that a single bit has two different values, e.g. + ``indices=[5,5]`` and ``selection=[0,1]``. If a contradiction is present, then setting + to ``False`` may silently produce incorrect results. (If a contradiction is present, the + correct result is to return an empty BitArray). Returns: A new bit array with ``shape=(), num_bits=data.num_bits, num_shots<=data.num_shots``. From 29615444e9dd941d2b84d81847de096d236804b3 Mon Sep 17 00:00:00 2001 From: aeddins-ibm <60495383+aeddins-ibm@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:24:14 -0400 Subject: [PATCH 21/45] fix bitarray test assertion Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> --- test/python/primitives/containers/test_bit_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index f1a79c0f0d0e..7fb362dff3e8 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -765,7 +765,7 @@ def test_postselection(self): if check_for_contradiction: np.testing.assert_equal(postselected_bools, answer) else: - self.assertTrue(len(answer) > 0) # avoiding trivial test case + self.assertGreater(len(answer), 0) # avoiding trivial test case np.testing.assert_equal(postselected_bools, answer) error_cases = [ From 745130f204df9a70ff13ed97f0543bb2368adc90 Mon Sep 17 00:00:00 2001 From: aeddins-ibm <60495383+aeddins-ibm@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:35:04 -0400 Subject: [PATCH 22/45] disallow postselect positional kwarg Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> --- qiskit/primitives/containers/bit_array.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 7ed25f4d23bb..38204c9cb4c0 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -468,6 +468,7 @@ def postselect( self, indices: Sequence[int] | int, selection: Sequence[bool] | bool, + *, may_have_contradiction: bool = True, ) -> BitArray: """Post-select this bit array based on sliced equality with a given bitstring. From 803765badb408c6a2db5efef929878f4853390c7 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Fri, 5 Jul 2024 10:37:54 -0400 Subject: [PATCH 23/45] fix numpy dtype args --- qiskit/primitives/containers/bit_array.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 38204c9cb4c0..fef65badd1ca 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -534,24 +534,24 @@ def postselect( # Check for contradictory conditions: if may_have_contradiction: if np.intersect1d(indices[selection], indices[np.logical_not(selection)]).size > 0: - return BitArray(np.empty((0, num_bytes), dtype="uint8"), num_bits=self.num_bits) + return BitArray(np.empty((0, num_bytes), dtype=np.uint8), num_bits=self.num_bits) # Recall that creg[0] is the LSb: byte_significance, bit_significance = np.divmod(indices, 8) # least-significant byte is at last position: byte_idx = (num_bytes - 1) - byte_significance # least-significant bit is at position 0: - bit_offset = bit_significance.astype("uint8") + bit_offset = bit_significance.astype(np.uint8) # Get bitpacked representation of `indices` (bitmask): - bitmask = np.zeros(num_bytes, dtype="uint8") + bitmask = np.zeros(num_bytes, dtype=np.uint8) np.bitwise_or.at(bitmask, byte_idx, np.uint8(1) << bit_offset) # Get bitpacked representation of `selection` (desired bitstring): - selection_bytes = np.zeros(num_bytes, dtype="uint8") + selection_bytes = np.zeros(num_bytes, dtype=np.uint8) ## This will produce incorrect result if we did not check for contradictions, but there is one: np.bitwise_or.at( - selection_bytes, byte_idx, np.asarray(selection, dtype="uint8") << bit_offset + selection_bytes, byte_idx, np.asarray(selection, dtype=np.uint8) << bit_offset ) return BitArray( From 99824f38cd6190dfbf491d6a8da1ba3e9215b705 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Fri, 5 Jul 2024 13:00:11 -0400 Subject: [PATCH 24/45] Simpler kwarg: "assume_unique" --- qiskit/primitives/containers/bit_array.py | 17 +++++++---------- .../primitives/containers/test_bit_array.py | 12 ++++++------ 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index fef65badd1ca..e53dccc36d03 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -469,7 +469,7 @@ def postselect( indices: Sequence[int] | int, selection: Sequence[bool] | bool, *, - may_have_contradiction: bool = True, + assume_unique: bool = False, ) -> BitArray: """Post-select this bit array based on sliced equality with a given bitstring. @@ -488,12 +488,9 @@ def postselect( to ``selection[i]``. Shots will be discarded unless all cbits specified by ``indices`` have the values given by ``selection``. - may_have_contradiction: If the args ``indices`` and ``selection`` are known not to contain - contradictions, one can set this to False for a moderate speed-up, depending on the problem - size. Here "contradiction" means requiring that a single bit has two different values, e.g. - ``indices=[5,5]`` and ``selection=[0,1]``. If a contradiction is present, then setting - to ``False`` may silently produce incorrect results. (If a contradiction is present, the - correct result is to return an empty BitArray). + assume_unique: If True, ``indices`` is assumed to contain no repeated elements, which can give + a speed-up depending on the problem size. If True but ``indices`` does contain repeated + elements, incorrect results may be produced. Returns: A new bit array with ``shape=(), num_bits=data.num_bits, num_shots<=data.num_shots``. @@ -531,8 +528,8 @@ def postselect( is_negative = indices < 0 indices[is_negative] = np.mod(indices[is_negative], self.num_bits, dtype=int) - # Check for contradictory conditions: - if may_have_contradiction: + # Handle special-case of contradictory conditions: + if not assume_unique: if np.intersect1d(indices[selection], indices[np.logical_not(selection)]).size > 0: return BitArray(np.empty((0, num_bytes), dtype=np.uint8), num_bits=self.num_bits) @@ -549,7 +546,7 @@ def postselect( # Get bitpacked representation of `selection` (desired bitstring): selection_bytes = np.zeros(num_bytes, dtype=np.uint8) - ## This will produce incorrect result if we did not check for contradictions, but there is one: + ## This can produce incorrect result if we did not check for contradictions, but there is one: np.bitwise_or.at( selection_bytes, byte_idx, np.asarray(selection, dtype=np.uint8) << bit_offset ) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 7fb362dff3e8..dfcfd5f771a6 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -746,12 +746,12 @@ def test_postselection(self): ] for name, indices, selection in test_cases: - for check_for_contradiction in (True, False): - with self.subTest("_".join([dataname, name, str(check_for_contradiction)])): + for assume_unique in (False, True): + with self.subTest("_".join([dataname, name, str(assume_unique)])): postselected_bools = np.unpackbits( - bit_array.postselect(indices, selection, check_for_contradiction).array[ - :, ::-1 - ], + bit_array.postselect( + indices, selection, assume_unique=assume_unique + ).array[:, ::-1], count=num_bits, axis=-1, bitorder="little", @@ -762,7 +762,7 @@ def test_postselection(self): selection = (selection,) answer = bool_array[np.all(bool_array[:, indices] == selection, axis=-1)] if name == "contradict": - if check_for_contradiction: + if not assume_unique: np.testing.assert_equal(postselected_bools, answer) else: self.assertGreater(len(answer), 0) # avoiding trivial test case From bdd4edea451e8527481942867c97bc6abd6728fe Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Fri, 5 Jul 2024 13:02:28 -0400 Subject: [PATCH 25/45] lint (line too long) --- qiskit/primitives/containers/bit_array.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index e53dccc36d03..6ca14597ad47 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -488,9 +488,9 @@ def postselect( to ``selection[i]``. Shots will be discarded unless all cbits specified by ``indices`` have the values given by ``selection``. - assume_unique: If True, ``indices`` is assumed to contain no repeated elements, which can give - a speed-up depending on the problem size. If True but ``indices`` does contain repeated - elements, incorrect results may be produced. + assume_unique: If True, ``indices`` is assumed to contain no repeated elements, which can + give a speed-up depending on the problem size. If True but ``indices`` does contain + repeated elements, incorrect results may be produced. Returns: A new bit array with ``shape=(), num_bits=data.num_bits, num_shots<=data.num_shots``. From 695068754245609c6abdfed4a514639e8d6cf20a Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Fri, 5 Jul 2024 13:24:26 -0400 Subject: [PATCH 26/45] simplification: remove assume_unique kwarg --- qiskit/primitives/containers/bit_array.py | 13 ++----- .../primitives/containers/test_bit_array.py | 36 ++++++++----------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 6ca14597ad47..58b64d703dfb 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -468,8 +468,6 @@ def postselect( self, indices: Sequence[int] | int, selection: Sequence[bool] | bool, - *, - assume_unique: bool = False, ) -> BitArray: """Post-select this bit array based on sliced equality with a given bitstring. @@ -488,10 +486,6 @@ def postselect( to ``selection[i]``. Shots will be discarded unless all cbits specified by ``indices`` have the values given by ``selection``. - assume_unique: If True, ``indices`` is assumed to contain no repeated elements, which can - give a speed-up depending on the problem size. If True but ``indices`` does contain - repeated elements, incorrect results may be produced. - Returns: A new bit array with ``shape=(), num_bits=data.num_bits, num_shots<=data.num_shots``. @@ -529,9 +523,8 @@ def postselect( indices[is_negative] = np.mod(indices[is_negative], self.num_bits, dtype=int) # Handle special-case of contradictory conditions: - if not assume_unique: - if np.intersect1d(indices[selection], indices[np.logical_not(selection)]).size > 0: - return BitArray(np.empty((0, num_bytes), dtype=np.uint8), num_bits=self.num_bits) + if np.intersect1d(indices[selection], indices[np.logical_not(selection)]).size > 0: + return BitArray(np.empty((0, num_bytes), dtype=np.uint8), num_bits=self.num_bits) # Recall that creg[0] is the LSb: byte_significance, bit_significance = np.divmod(indices, 8) @@ -546,7 +539,7 @@ def postselect( # Get bitpacked representation of `selection` (desired bitstring): selection_bytes = np.zeros(num_bytes, dtype=np.uint8) - ## This can produce incorrect result if we did not check for contradictions, but there is one: + ## This assumes no contradictions present, since those were already checked for: np.bitwise_or.at( selection_bytes, byte_idx, np.asarray(selection, dtype=np.uint8) << bit_offset ) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index dfcfd5f771a6..d4899d40e7b4 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -746,27 +746,21 @@ def test_postselection(self): ] for name, indices, selection in test_cases: - for assume_unique in (False, True): - with self.subTest("_".join([dataname, name, str(assume_unique)])): - postselected_bools = np.unpackbits( - bit_array.postselect( - indices, selection, assume_unique=assume_unique - ).array[:, ::-1], - count=num_bits, - axis=-1, - bitorder="little", - ).astype(bool) - if isinstance(indices, int): - indices = (indices,) - if isinstance(selection, bool): - selection = (selection,) - answer = bool_array[np.all(bool_array[:, indices] == selection, axis=-1)] - if name == "contradict": - if not assume_unique: - np.testing.assert_equal(postselected_bools, answer) - else: - self.assertGreater(len(answer), 0) # avoiding trivial test case - np.testing.assert_equal(postselected_bools, answer) + with self.subTest("_".join([dataname, name])): + postselected_bools = np.unpackbits( + bit_array.postselect(indices, selection).array[:, ::-1], + count=num_bits, + axis=-1, + bitorder="little", + ).astype(bool) + if isinstance(indices, int): + indices = (indices,) + if isinstance(selection, bool): + selection = (selection,) + answer = bool_array[np.all(bool_array[:, indices] == selection, axis=-1)] + if name != "contradict": + self.assertGreater(len(answer), 0) # avoiding trivial test case + np.testing.assert_equal(postselected_bools, answer) error_cases = [ ("out_of_range", [0, 6, 14], [True, True, False], ValueError), From 88be7a63c16455b4c5607462532ccab4d78f83dc Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Fri, 5 Jul 2024 13:33:29 -0400 Subject: [PATCH 27/45] improve misleading comment --- test/python/primitives/containers/test_bit_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index d4899d40e7b4..fc6e582a6c95 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -729,7 +729,7 @@ def test_postselection(self): for dataname, bool_array in zip(["flat", "shaped"], [flat_data, shaped_data]): bit_array = BitArray.from_bool_array(bool_array, order="little") - # indices[i] <-> creg[i] <-> bool_array[..., i] + # indices value of i <-> creg[i] <-> bool_array[..., i] num_bits = bool_array.shape[-1] bool_array = bool_array.reshape(-1, num_bits) From 036fdf8de2f26934faf5da7fa6f1581039dc75e2 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 9 Jul 2024 10:44:31 -0400 Subject: [PATCH 28/45] raise IndexError if indices out of range - Change ValueError to IndexError. - Add check for out-of-range negative indices. - Simplify use of mod - Update test conditions (include checks for off-by-one errors) --- qiskit/primitives/containers/bit_array.py | 18 +++++++++++------- .../primitives/containers/test_bit_array.py | 5 +++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 58b64d703dfb..63739b88a1f2 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -507,10 +507,15 @@ def postselect( num_bytes = self._array.shape[-1] indices = np.asarray(indices) - if num_indices > 0 and np.max(indices) >= self.num_bits: - raise ValueError( - f"index {int(np.max(indices))} is out of bounds for the number of bits {self.num_bits}." - ) + if num_indices > 0: + if indices.max() >= self.num_bits: + raise IndexError( + f"index {int(indices.max())} is out of bounds for the number of bits {self.num_bits}." + ) + elif indices.min() < -self.num_bits: + raise IndexError( + f"index {int(indices.min())} is out of bounds for the number of bits {self.num_bits}." + ) flattened = self.reshape((), self.size * self.num_shots) @@ -518,9 +523,8 @@ def postselect( if num_indices == 0: return flattened - # Allow for negative bit indices: - is_negative = indices < 0 - indices[is_negative] = np.mod(indices[is_negative], self.num_bits, dtype=int) + # Make negative bit indices positive: + indices %= self.num_bits # Handle special-case of contradictory conditions: if np.intersect1d(indices[selection], indices[np.logical_not(selection)]).size > 0: diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index fc6e582a6c95..f0cf6128ffe8 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -740,7 +740,7 @@ def test_postselection(self): ("repeated", [5, 5, 5], [False, False, False]), ("contradict", [5, 5, 5], [True, False, False]), ("unsorted", [5, 0, 9, 3], [True, False, True, False]), - ("negative", [-5, 1, -2, -5], [True, False, True, True]), + ("negative", [-5, 1, -2, -10], [True, False, True, False]), ("trivial", [], []), ("bareindex", 6, False), ] @@ -763,7 +763,8 @@ def test_postselection(self): np.testing.assert_equal(postselected_bools, answer) error_cases = [ - ("out_of_range", [0, 6, 14], [True, True, False], ValueError), + ("aboverange", [0, 6, 10], [True, True, False], IndexError), + ("belowrange", [0, 6, -11], [True, True, False], IndexError), ("mismatch", [0, 1, 2], [False, False], ValueError), ] for name, indices, selection, error in error_cases: From 3e02934f113c33a95bdb0e45c3e45a3777574794 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 9 Jul 2024 10:46:38 -0400 Subject: [PATCH 29/45] lint --- qiskit/primitives/containers/bit_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 63739b88a1f2..d58227415558 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -508,7 +508,7 @@ def postselect( indices = np.asarray(indices) if num_indices > 0: - if indices.max() >= self.num_bits: + if indices.max() >= self.num_bits: raise IndexError( f"index {int(indices.max())} is out of bounds for the number of bits {self.num_bits}." ) From da959ff8c5537e692e4ece2ab14459653afb3c70 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 9 Jul 2024 11:09:34 -0400 Subject: [PATCH 30/45] add negative-contradiction test --- test/python/primitives/containers/test_bit_array.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index f0cf6128ffe8..2265ce8d8a2f 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -741,6 +741,7 @@ def test_postselection(self): ("contradict", [5, 5, 5], [True, False, False]), ("unsorted", [5, 0, 9, 3], [True, False, True, False]), ("negative", [-5, 1, -2, -10], [True, False, True, False]), + ("negcontradict", [4, -6], [True, False]), ("trivial", [], []), ("bareindex", 6, False), ] @@ -758,7 +759,7 @@ def test_postselection(self): if isinstance(selection, bool): selection = (selection,) answer = bool_array[np.all(bool_array[:, indices] == selection, axis=-1)] - if name != "contradict": + if name not in ["contradict", "negcontradict"]: self.assertGreater(len(answer), 0) # avoiding trivial test case np.testing.assert_equal(postselected_bools, answer) From 8f76f97d58ca80ffabf5d650caeea39b90249a01 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 9 Jul 2024 12:38:32 -0400 Subject: [PATCH 31/45] Update docstring with IndexErrors --- qiskit/primitives/containers/bit_array.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index d58227415558..8ff5ee080f46 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -490,7 +490,8 @@ def postselect( A new bit array with ``shape=(), num_bits=data.num_bits, num_shots<=data.num_shots``. Raises: - ValueError: If ``max(indices)`` is greater than :attr:`num_bits``. + IndexError: If ``max(indices)`` is greater than or equal to :attr:`num_bits``. + IndexError: If ``min(indices)`` is less than negative :attr:`num_bits``. ValueError: If the lengths of ``selection`` and ``indices`` do not match. """ if isinstance(indices, int): From bfca1bd5cfe1db49847be1196380d728c3789f58 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 9 Jul 2024 12:38:56 -0400 Subject: [PATCH 32/45] lint --- qiskit/primitives/containers/bit_array.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 8ff5ee080f46..b72b10a842db 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -511,11 +511,11 @@ def postselect( if num_indices > 0: if indices.max() >= self.num_bits: raise IndexError( - f"index {int(indices.max())} is out of bounds for the number of bits {self.num_bits}." + f"index {int(indices.max())} out of bounds for the number of bits {self.num_bits}." ) - elif indices.min() < -self.num_bits: + if indices.min() < -self.num_bits: raise IndexError( - f"index {int(indices.min())} is out of bounds for the number of bits {self.num_bits}." + f"index {int(indices.min())} out of bounds for the number of bits {self.num_bits}." ) flattened = self.reshape((), self.size * self.num_shots) From 8f3217838c6632d30ef300445fcca1590454b536 Mon Sep 17 00:00:00 2001 From: aeddins-ibm <60495383+aeddins-ibm@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:03:48 -0400 Subject: [PATCH 33/45] change slice_bits error from ValueError to IndexError --- qiskit/primitives/containers/bit_array.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index b72b10a842db..78b0eabe64f7 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -424,13 +424,13 @@ def slice_bits(self, indices: int | Sequence[int]) -> "BitArray": A bit array sliced along the bit axis. Raises: - ValueError: If there are any invalid indices of the bit axis. + IndexError: If there are any invalid indices of the bit axis. """ if isinstance(indices, int): indices = (indices,) for index in indices: if index < 0 or index >= self.num_bits: - raise ValueError( + raise IndexError( f"index {index} is out of bounds for the number of bits {self.num_bits}." ) # This implementation introduces a temporary 8x memory overhead due to bit From c2b00390da40b12d5821476de850803da2b72a69 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Wed, 10 Jul 2024 18:17:19 -0400 Subject: [PATCH 34/45] update slice_bits test to use IndexError --- test/python/primitives/containers/test_bit_array.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 2265ce8d8a2f..4958fb086121 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -557,9 +557,9 @@ def test_slice_bits(self): self.assertEqual(ba2.get_counts((i, j, k)), expect) with self.subTest("errors"): - with self.assertRaisesRegex(ValueError, "index -1 is out of bounds"): + with self.assertRaisesRegex(IndexError, "index -1 is out of bounds"): _ = ba.slice_bits(-1) - with self.assertRaisesRegex(ValueError, "index 9 is out of bounds"): + with self.assertRaisesRegex(IndexError, "index 9 is out of bounds"): _ = ba.slice_bits(9) def test_slice_shots(self): From c4becd9b0e4363797331157b176c1603dabc40c9 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Wed, 10 Jul 2024 21:34:47 -0400 Subject: [PATCH 35/45] change ValueError to IndexError in slice_shots also update tests for this error --- qiskit/primitives/containers/bit_array.py | 2 +- test/python/primitives/containers/test_bit_array.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 78b0eabe64f7..d4e0734842f4 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -457,7 +457,7 @@ def slice_shots(self, indices: int | Sequence[int]) -> "BitArray": indices = (indices,) for index in indices: if index < 0 or index >= self.num_shots: - raise ValueError( + raise IndexError( f"index {index} is out of bounds for the number of shots {self.num_shots}." ) arr = self._array diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 4958fb086121..2081935954be 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -607,9 +607,9 @@ def test_slice_shots(self): self.assertEqual(ba2.get_bitstrings((i, j, k)), expected) with self.subTest("errors"): - with self.assertRaisesRegex(ValueError, "index -1 is out of bounds"): + with self.assertRaisesRegex(IndexError, "index -1 is out of bounds"): _ = ba.slice_shots(-1) - with self.assertRaisesRegex(ValueError, "index 10 is out of bounds"): + with self.assertRaisesRegex(IndexError, "index 10 is out of bounds"): _ = ba.slice_shots(10) def test_expectation_values(self): From 50545efbf26f6fac72c7c00919ae0995b8464ba6 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Wed, 10 Jul 2024 21:37:43 -0400 Subject: [PATCH 36/45] update error type in slice_shots docstring --- qiskit/primitives/containers/bit_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index d4e0734842f4..c700ad059714 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -451,7 +451,7 @@ def slice_shots(self, indices: int | Sequence[int]) -> "BitArray": A bit array sliced along the shots axis. Raises: - ValueError: If there are any invalid indices of the shots axis. + IndexError: If there are any invalid indices of the shots axis. """ if isinstance(indices, int): indices = (indices,) From 95bb321805b9b6d41e68d40e749218e41487d76a Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Thu, 11 Jul 2024 10:16:53 -0400 Subject: [PATCH 37/45] Revert ValueError to IndexError changes Reverting these changes as they will instead be made in a separate PR. This reverts commit 8f3217838c6632d30ef300445fcca1590454b536. Revert "update error type in slice_shots docstring" This reverts commit 50545efbf26f6fac72c7c00919ae0995b8464ba6. Revert "change ValueError to IndexError in slice_shots" This reverts commit c4becd9b0e4363797331157b176c1603dabc40c9. Revert "update slice_bits test to use IndexError" This reverts commit c2b00390da40b12d5821476de850803da2b72a69. --- qiskit/primitives/containers/bit_array.py | 8 ++++---- test/python/primitives/containers/test_bit_array.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index c700ad059714..b72b10a842db 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -424,13 +424,13 @@ def slice_bits(self, indices: int | Sequence[int]) -> "BitArray": A bit array sliced along the bit axis. Raises: - IndexError: If there are any invalid indices of the bit axis. + ValueError: If there are any invalid indices of the bit axis. """ if isinstance(indices, int): indices = (indices,) for index in indices: if index < 0 or index >= self.num_bits: - raise IndexError( + raise ValueError( f"index {index} is out of bounds for the number of bits {self.num_bits}." ) # This implementation introduces a temporary 8x memory overhead due to bit @@ -451,13 +451,13 @@ def slice_shots(self, indices: int | Sequence[int]) -> "BitArray": A bit array sliced along the shots axis. Raises: - IndexError: If there are any invalid indices of the shots axis. + ValueError: If there are any invalid indices of the shots axis. """ if isinstance(indices, int): indices = (indices,) for index in indices: if index < 0 or index >= self.num_shots: - raise IndexError( + raise ValueError( f"index {index} is out of bounds for the number of shots {self.num_shots}." ) arr = self._array diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 2081935954be..2265ce8d8a2f 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -557,9 +557,9 @@ def test_slice_bits(self): self.assertEqual(ba2.get_counts((i, j, k)), expect) with self.subTest("errors"): - with self.assertRaisesRegex(IndexError, "index -1 is out of bounds"): + with self.assertRaisesRegex(ValueError, "index -1 is out of bounds"): _ = ba.slice_bits(-1) - with self.assertRaisesRegex(IndexError, "index 9 is out of bounds"): + with self.assertRaisesRegex(ValueError, "index 9 is out of bounds"): _ = ba.slice_bits(9) def test_slice_shots(self): @@ -607,9 +607,9 @@ def test_slice_shots(self): self.assertEqual(ba2.get_bitstrings((i, j, k)), expected) with self.subTest("errors"): - with self.assertRaisesRegex(IndexError, "index -1 is out of bounds"): + with self.assertRaisesRegex(ValueError, "index -1 is out of bounds"): _ = ba.slice_shots(-1) - with self.assertRaisesRegex(IndexError, "index 10 is out of bounds"): + with self.assertRaisesRegex(ValueError, "index 10 is out of bounds"): _ = ba.slice_shots(10) def test_expectation_values(self): From c140f8ddb228b9e117241d59039d4992d3ee6a21 Mon Sep 17 00:00:00 2001 From: aeddins-ibm <60495383+aeddins-ibm@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:23:40 -0400 Subject: [PATCH 38/45] fix docstring formatting Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> --- qiskit/primitives/containers/bit_array.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index b72b10a842db..98fa21d28752 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -490,8 +490,8 @@ def postselect( A new bit array with ``shape=(), num_bits=data.num_bits, num_shots<=data.num_shots``. Raises: - IndexError: If ``max(indices)`` is greater than or equal to :attr:`num_bits``. - IndexError: If ``min(indices)`` is less than negative :attr:`num_bits``. + IndexError: If ``max(indices)`` is greater than or equal to :attr:`num_bits`. + IndexError: If ``min(indices)`` is less than negative :attr:`num_bits`. ValueError: If the lengths of ``selection`` and ``indices`` do not match. """ if isinstance(indices, int): From 38155e4781a4b1bd08d2c61704ddd9b98de10ce4 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Fri, 12 Jul 2024 11:35:01 -0400 Subject: [PATCH 39/45] allow selection to be int instead of bool --- qiskit/primitives/containers/bit_array.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index b72b10a842db..d09e2b90bf5f 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -467,7 +467,7 @@ def slice_shots(self, indices: int | Sequence[int]) -> "BitArray": def postselect( self, indices: Sequence[int] | int, - selection: Sequence[bool] | bool, + selection: Sequence[bool | int] | bool | int, ) -> BitArray: """Post-select this bit array based on sliced equality with a given bitstring. @@ -482,9 +482,10 @@ def postselect( :class:`~.ClassicalRegister` location ``creg[i]`` (as in :meth:`~slice_bits`). Negative indices are allowed. - selection: A list of bools of length matching ``indices``, with ``indices[i]`` corresponding - to ``selection[i]``. Shots will be discarded unless all cbits specified by ``indices`` have - the values given by ``selection``. + selection: A list of binary values (will be cast to ``bool``) of length matching + ``indices``, with ``indices[i]`` corresponding to ``selection[i]``. Shots will be + discarded unless all cbits specified by ``indices`` have the values given by + ``selection``. Returns: A new bit array with ``shape=(), num_bits=data.num_bits, num_shots<=data.num_shots``. @@ -496,7 +497,7 @@ def postselect( """ if isinstance(indices, int): indices = (indices,) - if isinstance(selection, bool): + if isinstance(selection, (bool,int)): selection = (selection,) selection = np.asarray(selection, dtype=bool) From c12964281509f20f7d229190564485b6ba50859f Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Fri, 12 Jul 2024 11:40:13 -0400 Subject: [PATCH 40/45] In tests, give selection as type int --- .../primitives/containers/test_bit_array.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 2265ce8d8a2f..fb3a42765db3 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -735,15 +735,15 @@ def test_postselection(self): bool_array = bool_array.reshape(-1, num_bits) test_cases = [ - ("basic", [0, 1], [False, False]), - ("multibyte", [0, 9], [False, True]), - ("repeated", [5, 5, 5], [False, False, False]), - ("contradict", [5, 5, 5], [True, False, False]), - ("unsorted", [5, 0, 9, 3], [True, False, True, False]), - ("negative", [-5, 1, -2, -10], [True, False, True, False]), - ("negcontradict", [4, -6], [True, False]), + ("basic", [0, 1], [0, 0]), + ("multibyte", [0, 9], [0, 1]), + ("repeated", [5, 5, 5], [0, 0, 0]), + ("contradict", [5, 5, 5], [0, 0, 0]), + ("unsorted", [5, 0, 9, 3], [1, 0, 1, 0]), + ("negative", [-5, 1, -2, -10], [1, 0, 1, 0]), + ("negcontradict", [4, -6], [1, 0]), ("trivial", [], []), - ("bareindex", 6, False), + ("bareindex", 6, 0), ] for name, indices, selection in test_cases: From af39354c48e4f3ebb97dfa6b64dea3b50a4774af Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Fri, 12 Jul 2024 12:18:52 -0400 Subject: [PATCH 41/45] lint --- qiskit/primitives/containers/bit_array.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py index 3cf8b3f51e2d..8c88de6f12a5 100644 --- a/qiskit/primitives/containers/bit_array.py +++ b/qiskit/primitives/containers/bit_array.py @@ -483,8 +483,8 @@ def postselect( Negative indices are allowed. selection: A list of binary values (will be cast to ``bool``) of length matching - ``indices``, with ``indices[i]`` corresponding to ``selection[i]``. Shots will be - discarded unless all cbits specified by ``indices`` have the values given by + ``indices``, with ``indices[i]`` corresponding to ``selection[i]``. Shots will be + discarded unless all cbits specified by ``indices`` have the values given by ``selection``. Returns: @@ -497,7 +497,7 @@ def postselect( """ if isinstance(indices, int): indices = (indices,) - if isinstance(selection, (bool,int)): + if isinstance(selection, (bool, int)): selection = (selection,) selection = np.asarray(selection, dtype=bool) From 9c5edcddd3c4456d9694ddb1fa6ce56562e7e220 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Fri, 12 Jul 2024 12:19:09 -0400 Subject: [PATCH 42/45] add example to release note --- .../notes/bitarray-postselect-659b8f7801ccaa60.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml b/releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml index c9adf7561196..33ce17bafa8d 100644 --- a/releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml +++ b/releasenotes/notes/bitarray-postselect-659b8f7801ccaa60.yaml @@ -2,3 +2,10 @@ features_primitives: - | Added a new method :meth:`.BitArray.postselect` that returns all shots containing specified bit values. + Example usage:: + + from qiskit.primitives.containers import BitArray + + ba = BitArray.from_counts({'110': 2, '100': 4, '000': 3}) + print(ba.postselect([0,2], [0,1]).get_counts()) + # {'110': 2, '100': 4} From 3e9684e63be2dc40c8c9533bb720259f08e213d0 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Mon, 22 Jul 2024 16:58:14 -0400 Subject: [PATCH 43/45] fix typo in test case --- test/python/primitives/containers/test_bit_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index fb3a42765db3..63b22461c0d6 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -738,7 +738,7 @@ def test_postselection(self): ("basic", [0, 1], [0, 0]), ("multibyte", [0, 9], [0, 1]), ("repeated", [5, 5, 5], [0, 0, 0]), - ("contradict", [5, 5, 5], [0, 0, 0]), + ("contradict", [5, 5, 5], [1, 0, 0]), ("unsorted", [5, 0, 9, 3], [1, 0, 1, 0]), ("negative", [-5, 1, -2, -10], [1, 0, 1, 0]), ("negcontradict", [4, -6], [1, 0]), From 47502500c6976b21cc9a673737caff1105f638f9 Mon Sep 17 00:00:00 2001 From: aeddins-ibm <60495383+aeddins-ibm@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:51:33 -0400 Subject: [PATCH 44/45] add check of test Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> --- test/python/primitives/containers/test_bit_array.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 63b22461c0d6..2e87f8b241f6 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -759,8 +759,10 @@ def test_postselection(self): if isinstance(selection, bool): selection = (selection,) answer = bool_array[np.all(bool_array[:, indices] == selection, axis=-1)] - if name not in ["contradict", "negcontradict"]: - self.assertGreater(len(answer), 0) # avoiding trivial test case + if name in ["contradict", "negcontradict"]: + self.assertEqual(len(answer), 0) + else: + self.assertGreater(len(answer), 0) np.testing.assert_equal(postselected_bools, answer) error_cases = [ From d5d0009aa645cf8b38e5ff2e52e11ab37c70bd57 Mon Sep 17 00:00:00 2001 From: Andrew Eddins Date: Tue, 23 Jul 2024 10:32:51 -0400 Subject: [PATCH 45/45] lint --- test/python/primitives/containers/test_bit_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py index 2e87f8b241f6..22fb27e0df43 100644 --- a/test/python/primitives/containers/test_bit_array.py +++ b/test/python/primitives/containers/test_bit_array.py @@ -762,7 +762,7 @@ def test_postselection(self): if name in ["contradict", "negcontradict"]: self.assertEqual(len(answer), 0) else: - self.assertGreater(len(answer), 0) + self.assertGreater(len(answer), 0) np.testing.assert_equal(postselected_bools, answer) error_cases = [