Repository: Qiskit/qiskit

Issue:
qiskit.quantum_info.StabilizerState needs get_probability() method
### What should we add?

qiskit.quantum_info.StabilizerState has a method .probabilities_dict() , which recurses through all 2^n possibilities so time complexity scales exponentially in the number of qubits.
For a variety of purposes (including classical shadows), it is necessary to calculate only a single probability for some target bitstring via .get_probability(target).
I've added that method to my own installation, by adapting from the _get_probablities helper function (note misspelling in function name). i pass it an argument target, and instead of iterating
   for single_qubit_outcome in range(0, 2):
I use instead
   single_qubit_outcome = int(target[qubit_for_branching])
and it returns only the outcome of interest (or zero if there's a deterministic outcome that doesn't agree with the target string)

so this is an extremely useful method that entirely uses existing code, just eliminates unnecessary branches.
instead of a separate method, you could also add an argument to the .probabilities_dict() method that would permit specifying any restricted subset of strings to return in the dict (and not calculate the rest)



Discussion:
Putting in a method to restrict the set of returned probabilities sounds useful to me.  @ShellyGarion: I think you were the original author - do you have any thoughts?
I agree that adding a new method to the `StabilizerState` class that returns a restricted set of probabilities will be very useful.
@ginsparg - would you like to contribute and open a PR?

Hi, I am part of the IBM OpenSource Coalition for getting started with open source. @psschwei thought this was a good first issue, this issue looks interesting to me and I would like implement it
Hi @DerekMiner - Thanks for your willing to contribute to qiskit. You have been assigned on this issue. When you open a PR please tag me, so I'll look at it and review it.

> Hi @DerekMiner - Thanks for your willing to contribute to qiskit. You have been assigned on this issue. When you open a PR please tag me, so I'll look at it and review it.

Sounds great, thank you!
regarding: `"you could also add an argument to the .probabilities_dict() method that would permit specifying any restricted subset of strings to return in the dict (and not calculate the rest)"`

does anyone in the community have a preference of adding a parm to `probabilities_dict()` method, otherwise I could create a method of something like `probabilities_dict_using_target() `

@ShellyGarion you mentioned `"I agree that adding a new method to the StabilizerState class that returns a restricted set of probabilities will be very  #useful."` any preference?

I am leaning towards using the same method with a new target parm default of None, if not provided then will operate as before, if provided it will rely on the target
> regarding: `"you could also add an argument to the .probabilities_dict() method that would permit specifying any restricted subset of strings to return in the dict (and not calculate the rest)"`
> 
> does anyone in the community have a preference of adding a parm to `probabilities_dict()` method, otherwise I could create a method of something like `probabilities_dict_using_target() `
> 
> @ShellyGarion you mentioned `"I agree that adding a new method to the StabilizerState class that returns a restricted set of probabilities will be very #useful."` any preference?
> 
> I am leaning towards using the same method with a new target parm default of None, if not provided then will operate as before, if provided it will rely on the target

My preference would be to add a separate method, as the `probabilities` and `probabilities_dict` methods also exist in the `Statevector` class, and we would like to keep the same API of both classes:
https://docs.quantum.ibm.com/api/qiskit/qiskit.quantum_info.Statevector#probabilities

But since "target" has a different meaning in Qiskit, I would suggest to call it something like: 
`probabilities_dict_from_bitstrings`
> > regarding: `"you could also add an argument to the .probabilities_dict() method that would permit specifying any restricted subset of strings to return in the dict (and not calculate the rest)"`
> > does anyone in the community have a preference of adding a parm to `probabilities_dict()` method, otherwise I could create a method of something like `probabilities_dict_using_target() `
> > @ShellyGarion you mentioned `"I agree that adding a new method to the StabilizerState class that returns a restricted set of probabilities will be very #useful."` any preference?
> > I am leaning towards using the same method with a new target parm default of None, if not provided then will operate as before, if provided it will rely on the target
> 
> My preference would be to add a separate method, as the `probabilities` and `probabilities_dict` methods also exist in the `Statevector` class, and we would like to keep the same API of both classes: https://docs.quantum.ibm.com/api/qiskit/qiskit.quantum_info.Statevector#probabilities
> 
> But since "target" has a different meaning in Qiskit, I would suggest to call it something like: `probabilities_dict_from_bitstrings`

I will go with that, thank you!
thanks all for looking into this ... (and for suggesting i open a PR -- forgot to respond that never having recovered from the experience of starting with fortran programming in the 1960s unfortunately my code in any language is not ordinarily fit for human consumption).

adding a parameter to the existing method would have been conceptually much cleaner, so it's unfortunate that turns out not to be as practical. (we've all had the experience of being forced to go through multiple methods in documentation of some class, only to be mystified by why there are two methods with 90% overlap -- or worse, we stop at the first and never notice the later one which has the extra 10% we need.) it does make sense to keep the methods synchronized with the Statevector class, so perhaps all could be updated simultaneously.

regardless, if something works out here, i have some additional code that may help inform the architectural considerations. i also used qiskit to sample from say Clifford evolution of W-states with large number n of qubits. While W states are not themselves stabilizer states, they are low-rank linear combinations of stabilizer states, so the amplitudes can be efficiently simulated independently and then added, presuming one keeps track of the phases +/- 1, +/- i, which i added as well to my local installation. e.g., to simulate W states (or any other O(n) rank combination), it's still just low polynomial in n (rather than exponential). that method was slightly less trivial but more fun to write. i use a .get_amplitudes() method for a set of bitstrings, then superpose and abs val square for a probability, or sequence through qubits to sample. am mentioning just in case the possibility of accommodating something along those lines in the future as another epsilon extension would affect current decision-making.

(and of course the spelling of '_get_probablities' should be corrected)
tks, pg
I will make sure to fix the probabilities typo as well, I created an issue for it just to document that #11688 
I am continuing to work this issue, having some problems getting the baseline tests to run without any code changes, but this issue is still on my list of priorities to work on
@ginsparg do you have any examples of inputs and targets I could use to add for the tests that you have used?
the program i was running to calculate classical shadows found squared amplitudes for specific basis elements after applying random cliffords, so that would not be reproducible. but is see qiskit.quantum_info.random_clifford has an optional seed, so i'll run a set of reproducible inputs and targets to benchmark against (probably over weekend) .

Resolving patch:
diff --git a/qiskit/quantum_info/states/stabilizerstate.py b/qiskit/quantum_info/states/stabilizerstate.py
--- a/qiskit/quantum_info/states/stabilizerstate.py
+++ b/qiskit/quantum_info/states/stabilizerstate.py
@@ -386,8 +386,17 @@ def probabilities(self, qargs: None | list = None, decimals: None | int = None)
 
         return probs
 
-    def probabilities_dict(self, qargs: None | list = None, decimals: None | int = None) -> dict:
-        """Return the subsystem measurement probability dictionary.
+    def probabilities_dict_from_bitstring(
+        self,
+        outcome_bitstring: str,
+        qargs: None | list = None,
+        decimals: None | int = None,
+    ) -> dict[str, float]:
+        """Return the subsystem measurement probability dictionary utilizing
+        a targeted outcome_bitstring to perform the measurement for. This
+        will calculate a probability for only a single targeted
+        outcome_bitstring value, giving a performance boost over calculating
+        all possible outcomes.
 
         Measurement probabilities are with respect to measurement in the
         computation (diagonal) basis.
@@ -398,30 +407,44 @@ def probabilities_dict(self, qargs: None | list = None, decimals: None | int = N
         inserted between integers so that subsystems can be distinguished.
 
         Args:
+            outcome_bitstring (None or str): targeted outcome bitstring
+                to perform a measurement calculation for, this will significantly
+                reduce the number of calculation performed (Default: None)
             qargs (None or list): subsystems to return probabilities for,
-                if None return for all subsystems (Default: None).
+                    if None return for all subsystems (Default: None).
             decimals (None or int): the number of decimal places to round
-                values. If None no rounding is done (Default: None).
+                    values. If None no rounding is done (Default: None)
 
         Returns:
-            dict: The measurement probabilities in dict (ket) form.
+            dict[str, float]: The measurement probabilities in dict (ket) form.
         """
-        if qargs is None:
-            qubits = range(self.clifford.num_qubits)
-        else:
-            qubits = qargs
+        return self._get_probabilities_dict(
+            outcome_bitstring=outcome_bitstring, qargs=qargs, decimals=decimals
+        )
 
-        outcome = ["X"] * len(qubits)
-        outcome_prob = 1.0
-        probs = {}  # probabilities dictionary
+    def probabilities_dict(
+        self, qargs: None | list = None, decimals: None | int = None
+    ) -> dict[str, float]:
+        """Return the subsystem measurement probability dictionary.
 
-        self._get_probabilities(qubits, outcome, outcome_prob, probs)
+        Measurement probabilities are with respect to measurement in the
+        computation (diagonal) basis.
 
-        if decimals is not None:
-            for key, value in probs.items():
-                probs[key] = round(value, decimals)
+        This dictionary representation uses a Ket-like notation where the
+        dictionary keys are qudit strings for the subsystem basis vectors.
+        If any subsystem has a dimension greater than 10 comma delimiters are
+        inserted between integers so that subsystems can be distinguished.
 
-        return probs
+        Args:
+            qargs (None or list): subsystems to return probabilities for,
+                if None return for all subsystems (Default: None).
+            decimals (None or int): the number of decimal places to round
+                values. If None no rounding is done (Default: None).
+
+        Returns:
+            dict: The measurement probabilities in dict (key) form.
+        """
+        return self._get_probabilities_dict(outcome_bitstring=None, qargs=qargs, decimals=decimals)
 
     def reset(self, qargs: list | None = None) -> StabilizerState:
         """Reset state or subsystems to the 0-state.
@@ -644,22 +667,48 @@ def _rowsum_deterministic(clifford, aux_pauli, row):
     # -----------------------------------------------------------------------
     # Helper functions for calculating the probabilities
     # -----------------------------------------------------------------------
-    def _get_probabilities(self, qubits, outcome, outcome_prob, probs):
-        """Recursive helper function for calculating the probabilities"""
+    def _get_probabilities(
+        self,
+        qubits: range,
+        outcome: list[str],
+        outcome_prob: float,
+        probs: dict[str, float],
+        outcome_bitstring: str = None,
+    ):
+        """Recursive helper function for calculating the probabilities
 
-        qubit_for_branching = -1
-        ret = self.copy()
+        Args:
+            qubits (range): range of qubits
+            outcome (list[str]): outcome being built
+            outcome_prob (float): probabilitiy of the outcome
+            probs (dict[str, float]): holds the outcomes and probabilitiy results
+            outcome_bitstring (str): target outcome to measure which reduces measurements, None
+                if not targeting a specific target
+        """
+        qubit_for_branching: int = -1
 
+        ret: StabilizerState = self.copy()
+
+        # Find outcomes for each qubit
         for i in range(len(qubits)):
-            qubit = qubits[len(qubits) - i - 1]
             if outcome[i] == "X":
-                is_deterministic = not any(ret.clifford.stab_x[:, qubit])
-                if is_deterministic:
-                    single_qubit_outcome = ret._measure_and_update(qubit, 0)
-                    if single_qubit_outcome:
-                        outcome[i] = "1"
+                # Retrieve the qubit for the current measurement
+                qubit = qubits[(len(qubits) - i - 1)]
+                # Determine if the probabilitiy is deterministic
+                if not any(ret.clifford.stab_x[:, qubit]):
+                    single_qubit_outcome: np.int64 = ret._measure_and_update(qubit, 0)
+                    if outcome_bitstring is None or (
+                        int(outcome_bitstring[i]) == single_qubit_outcome
+                    ):
+                        # No outcome_bitstring target, or using outcome_bitstring target and
+                        # the single_qubit_outcome equals the desired outcome_bitstring target value,
+                        # then use current outcome_prob value
+                        outcome[i] = str(single_qubit_outcome)
                     else:
-                        outcome[i] = "0"
+                        # If the single_qubit_outcome does not equal the outcome_bitsring target
+                        # then we know that the probability will be 0
+                        outcome[i] = str(outcome_bitstring[i])
+                        outcome_prob = 0
                 else:
                     qubit_for_branching = i
 
@@ -668,15 +717,57 @@ def _get_probabilities(self, qubits, outcome, outcome_prob, probs):
             probs[str_outcome] = outcome_prob
             return
 
-        for single_qubit_outcome in range(0, 2):
+        for single_qubit_outcome in (
+            range(0, 2)
+            if (outcome_bitstring is None)
+            else [int(outcome_bitstring[qubit_for_branching])]
+        ):
             new_outcome = outcome.copy()
-            if single_qubit_outcome:
-                new_outcome[qubit_for_branching] = "1"
-            else:
-                new_outcome[qubit_for_branching] = "0"
+            new_outcome[qubit_for_branching] = str(single_qubit_outcome)
 
             stab_cpy = ret.copy()
             stab_cpy._measure_and_update(
-                qubits[len(qubits) - qubit_for_branching - 1], single_qubit_outcome
+                qubits[(len(qubits) - qubit_for_branching - 1)], single_qubit_outcome
+            )
+            stab_cpy._get_probabilities(
+                qubits, new_outcome, (0.5 * outcome_prob), probs, outcome_bitstring
             )
-            stab_cpy._get_probabilities(qubits, new_outcome, 0.5 * outcome_prob, probs)
+
+    def _get_probabilities_dict(
+        self,
+        outcome_bitstring: None | str = None,
+        qargs: None | list = None,
+        decimals: None | int = None,
+    ) -> dict[str, float]:
+        """Helper Function for calculating the subsystem measurement probability dictionary.
+        When the targeted outcome_bitstring value is set, then only the single outcome_bitstring
+        probability will be calculated.
+
+        Args:
+            outcome_bitstring (None or str): targeted outcome bitstring
+                to perform a measurement calculation for, this will significantly
+                reduce the number of calculation performed (Default: None)
+            qargs (None or list): subsystems to return probabilities for,
+                if None return for all subsystems (Default: None).
+            decimals (None or int): the number of decimal places to round
+                values. If None no rounding is done (Default: None).
+
+        Returns:
+            dict: The measurement probabilities in dict (key) form.
+        """
+        if qargs is None:
+            qubits = range(self.clifford.num_qubits)
+        else:
+            qubits = qargs
+
+        outcome = ["X"] * len(qubits)
+        outcome_prob = 1.0
+        probs: dict[str, float] = {}  # Probabilities dict to return with the measured values
+
+        self._get_probabilities(qubits, outcome, outcome_prob, probs, outcome_bitstring)
+
+        if decimals is not None:
+            for key, value in probs.items():
+                probs[key] = round(value, decimals)
+
+        return probs


Test patch:
diff --git a/test/python/quantum_info/states/test_stabilizerstate.py b/test/python/quantum_info/states/test_stabilizerstate.py
--- a/test/python/quantum_info/states/test_stabilizerstate.py
+++ b/test/python/quantum_info/states/test_stabilizerstate.py
@@ -13,6 +13,7 @@
 
 """Tests for Stabilizerstate quantum state class."""
 
+from itertools import product
 import unittest
 import logging
 from ddt import ddt, data, unpack
@@ -32,6 +33,61 @@
 logger = logging.getLogger(__name__)
 
 
+class StabilizerStateTestingTools:
+    """Test tools for verifying test cases in StabilizerState"""
+
+    @staticmethod
+    def _bitstring_product_dict(bitstring_length: int, skip_entries: dict = None) -> dict:
+        """Retrieves a dict of every possible product of '0', '1' for length bitstring_length
+        pass in a dict to use the keys as entries to skip adding to the dict
+
+        Args:
+            bitstring_length (int): length of the bitstring product
+            skip_entries (dict[str, float], optional): dict entries to skip adding to the dict based
+                on existing keys in the dict passed in. Defaults to {}.
+
+        Returns:
+            dict[str, float]: dict with entries, all set to 0
+        """
+        if skip_entries is None:
+            skip_entries = {}
+        return {
+            result: 0
+            for result in ["".join(x) for x in product(["0", "1"], repeat=bitstring_length)]
+            if result not in skip_entries
+        }
+
+    @staticmethod
+    def _verify_individual_bitstrings(
+        testcase: QiskitTestCase,
+        target_dict: dict,
+        stab: StabilizerState,
+        qargs: list = None,
+        decimals: int = None,
+        dict_almost_equal: bool = False,
+    ) -> None:
+        """Helper that iterates through the target_dict and checks all probabilities by
+        running the value through the probabilities_dict_from_bitstring method for
+        retrieving a single measurement
+
+        Args:
+            target_dict (dict[str, float]): dict to check probabilities for
+            stab (StabilizerState): stabilizerstate object to run probabilities_dict_from_bitstring on
+            qargs (None or list): subsystems to return probabilities for,
+                    if None return for all subsystems (Default: None).
+            decimals (None or int): the number of decimal places to round
+                    values. If None no rounding is done (Default: None)
+            dict_almost_equal (bool): utilize assertDictAlmostEqual when true, assertDictEqual when false
+        """
+        for outcome_bitstring in target_dict:
+            (testcase.assertDictAlmostEqual if (dict_almost_equal) else testcase.assertDictEqual)(
+                stab.probabilities_dict_from_bitstring(
+                    outcome_bitstring=outcome_bitstring, qargs=qargs, decimals=decimals
+                ),
+                {outcome_bitstring: target_dict[outcome_bitstring]},
+            )
+
+
 @ddt
 class TestStabilizerState(QiskitTestCase):
     """Tests for StabilizerState class."""
@@ -315,6 +371,8 @@ def test_probabilities_dict_single_qubit(self):
                 value = stab.probabilities_dict()
                 target = {"0": 1}
                 self.assertEqual(value, target)
+                target.update({"1": 0.0})
+                StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab)
                 probs = stab.probabilities()
                 target = np.array([1, 0])
                 self.assertTrue(np.allclose(probs, target))
@@ -326,6 +384,8 @@ def test_probabilities_dict_single_qubit(self):
                 value = stab.probabilities_dict()
                 target = {"1": 1}
                 self.assertEqual(value, target)
+                target.update({"0": 0.0})
+                StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab)
                 probs = stab.probabilities()
                 target = np.array([0, 1])
                 self.assertTrue(np.allclose(probs, target))
@@ -338,6 +398,7 @@ def test_probabilities_dict_single_qubit(self):
                 value = stab.probabilities_dict()
                 target = {"0": 0.5, "1": 0.5}
                 self.assertEqual(value, target)
+                StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab)
                 probs = stab.probabilities()
                 target = np.array([0.5, 0.5])
                 self.assertTrue(np.allclose(probs, target))
@@ -355,43 +416,56 @@ def test_probabilities_dict_two_qubits(self):
                 value = stab.probabilities_dict()
                 target = {"00": 0.5, "01": 0.5}
                 self.assertEqual(value, target)
+                target.update({"10": 0.0, "11": 0.0})
+                StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab)
                 probs = stab.probabilities()
                 target = np.array([0.5, 0.5, 0, 0])
                 self.assertTrue(np.allclose(probs, target))
 
+        qargs: list = [0, 1]
         for _ in range(self.samples):
             with self.subTest(msg="P([0, 1])"):
-                value = stab.probabilities_dict([0, 1])
+                value = stab.probabilities_dict(qargs)
                 target = {"00": 0.5, "01": 0.5}
                 self.assertEqual(value, target)
-                probs = stab.probabilities([0, 1])
+                target.update({"10": 0.0, "11": 0.0})
+                StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab, qargs)
+                probs = stab.probabilities(qargs)
                 target = np.array([0.5, 0.5, 0, 0])
                 self.assertTrue(np.allclose(probs, target))
 
+        qargs: list = [1, 0]
         for _ in range(self.samples):
             with self.subTest(msg="P([1, 0])"):
-                value = stab.probabilities_dict([1, 0])
+                value = stab.probabilities_dict(qargs)
                 target = {"00": 0.5, "10": 0.5}
                 self.assertEqual(value, target)
-                probs = stab.probabilities([1, 0])
+                target.update({"01": 0.0, "11": 0.0})
+                StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab, qargs)
+                probs = stab.probabilities(qargs)
                 target = np.array([0.5, 0, 0.5, 0])
                 self.assertTrue(np.allclose(probs, target))
 
+        qargs: list = [0]
         for _ in range(self.samples):
             with self.subTest(msg="P[0]"):
-                value = stab.probabilities_dict([0])
+                value = stab.probabilities_dict(qargs)
                 target = {"0": 0.5, "1": 0.5}
                 self.assertEqual(value, target)
-                probs = stab.probabilities([0])
+                StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab, qargs)
+                probs = stab.probabilities(qargs)
                 target = np.array([0.5, 0.5])
                 self.assertTrue(np.allclose(probs, target))
 
+        qargs: list = [1]
         for _ in range(self.samples):
             with self.subTest(msg="P([1])"):
-                value = stab.probabilities_dict([1])
+                value = stab.probabilities_dict(qargs)
                 target = {"0": 1.0}
                 self.assertEqual(value, target)
-                probs = stab.probabilities([1])
+                target.update({"1": 0.0})
+                StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab, qargs)
+                probs = stab.probabilities(qargs)
                 target = np.array([1, 0])
                 self.assertTrue(np.allclose(probs, target))
 
@@ -405,9 +479,10 @@ def test_probabilities_dict_qubits(self):
         qc.h(2)
         stab = StabilizerState(qc)
 
+        decimals: int = 1
         for _ in range(self.samples):
             with self.subTest(msg="P(None), decimals=1"):
-                value = stab.probabilities_dict(decimals=1)
+                value = stab.probabilities_dict(decimals=decimals)
                 target = {
                     "000": 0.1,
                     "001": 0.1,
@@ -419,13 +494,17 @@ def test_probabilities_dict_qubits(self):
                     "111": 0.1,
                 }
                 self.assertEqual(value, target)
-                probs = stab.probabilities(decimals=1)
+                StabilizerStateTestingTools._verify_individual_bitstrings(
+                    self, target, stab, decimals=decimals
+                )
+                probs = stab.probabilities(decimals=decimals)
                 target = np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1])
                 self.assertTrue(np.allclose(probs, target))
 
+        decimals: int = 2
         for _ in range(self.samples):
             with self.subTest(msg="P(None), decimals=2"):
-                value = stab.probabilities_dict(decimals=2)
+                value = stab.probabilities_dict(decimals=decimals)
                 target = {
                     "000": 0.12,
                     "001": 0.12,
@@ -437,13 +516,17 @@ def test_probabilities_dict_qubits(self):
                     "111": 0.12,
                 }
                 self.assertEqual(value, target)
-                probs = stab.probabilities(decimals=2)
+                StabilizerStateTestingTools._verify_individual_bitstrings(
+                    self, target, stab, decimals=decimals
+                )
+                probs = stab.probabilities(decimals=decimals)
                 target = np.array([0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12])
                 self.assertTrue(np.allclose(probs, target))
 
+        decimals: int = 3
         for _ in range(self.samples):
             with self.subTest(msg="P(None), decimals=3"):
-                value = stab.probabilities_dict(decimals=3)
+                value = stab.probabilities_dict(decimals=decimals)
                 target = {
                     "000": 0.125,
                     "001": 0.125,
@@ -455,10 +538,72 @@ def test_probabilities_dict_qubits(self):
                     "111": 0.125,
                 }
                 self.assertEqual(value, target)
+                StabilizerStateTestingTools._verify_individual_bitstrings(
+                    self, target, stab, decimals=decimals
+                )
                 probs = stab.probabilities(decimals=3)
                 target = np.array([0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125])
                 self.assertTrue(np.allclose(probs, target))
 
+    @combine(num_qubits=[5, 6, 7, 8, 9])
+    def test_probabilities_dict_from_bitstring(self, num_qubits):
+        """Test probabilities_dict_from_bitstring methods with medium number of qubits that are still
+        reasonable to calculate the full dict with probabilities_dict of all possible outcomes"""
+
+        qc: QuantumCircuit = QuantumCircuit(num_qubits)
+        for qubit_num in range(0, num_qubits):
+            qc.h(qubit_num)
+        stab = StabilizerState(qc)
+
+        expected_result: float = float(1 / (2**num_qubits))
+        target_dict: dict = StabilizerStateTestingTools._bitstring_product_dict(num_qubits)
+        target_dict.update((k, expected_result) for k in target_dict)
+
+        for _ in range(self.samples):
+            with self.subTest(msg="P(None)"):
+                value = stab.probabilities_dict()
+                self.assertDictEqual(value, target_dict)
+                StabilizerStateTestingTools._verify_individual_bitstrings(self, target_dict, stab)
+                probs = stab.probabilities()
+                target = np.array(([expected_result] * (2**num_qubits)))
+                self.assertTrue(np.allclose(probs, target))
+
+        # H gate at qubit 0, Every gate after is an X gate
+        # will result in 2 outcomes with 0.5
+        qc = QuantumCircuit(num_qubits)
+        qc.h(0)
+        for qubit_num in range(1, num_qubits):
+            qc.x(qubit_num)
+        stab = StabilizerState(qc)
+
+        # Build the 2 expected outcome bitstrings for
+        # 0.5 probability based on h and x gates
+        target_1: str = "".join(["1" * (num_qubits - 1)] + ["0"])
+        target_2: str = "".join(["1" * num_qubits])
+        target: dict = {target_1: 0.5, target_2: 0.5}
+        target_all_bitstrings: dict = StabilizerStateTestingTools._bitstring_product_dict(
+            num_qubits, target
+        )
+        target_all_bitstrings.update(target_all_bitstrings)
+
+        # Numpy Array to verify stab.probabilities()
+        target_np_dict: dict = StabilizerStateTestingTools._bitstring_product_dict(
+            num_qubits, [target_1, target_2]
+        )
+        target_np_dict.update(target)
+        target_np_array: np.ndarray = np.array(list(target_np_dict.values()))
+
+        for _ in range(self.samples):
+            with self.subTest(msg="P(None)"):
+                stab = StabilizerState(qc)
+                value = stab.probabilities_dict()
+                self.assertEqual(value, target)
+                StabilizerStateTestingTools._verify_individual_bitstrings(
+                    self, target_all_bitstrings, stab
+                )
+                probs = stab.probabilities()
+                self.assertTrue(np.allclose(probs, target_np_array))
+
     def test_probabilities_dict_ghz(self):
         """Test probabilities and probabilities_dict method of a subsystem of qubits"""
 
@@ -473,6 +618,8 @@ def test_probabilities_dict_ghz(self):
             value = stab.probabilities_dict()
             target = {"000": 0.5, "111": 0.5}
             self.assertEqual(value, target)
+            target.update(StabilizerStateTestingTools._bitstring_product_dict(num_qubits, target))
+            StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab)
             probs = stab.probabilities()
             target = np.array([0.5, 0, 0, 0, 0, 0, 0, 0.5])
             self.assertTrue(np.allclose(probs, target))
@@ -483,6 +630,10 @@ def test_probabilities_dict_ghz(self):
                 probs = stab.probabilities_dict(qargs)
                 target = {"000": 0.5, "111": 0.5}
                 self.assertDictAlmostEqual(probs, target)
+                target.update(
+                    StabilizerStateTestingTools._bitstring_product_dict(num_qubits, target)
+                )
+                StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab, qargs)
                 probs = stab.probabilities(qargs)
                 target = np.array([0.5, 0, 0, 0, 0, 0, 0, 0.5])
                 self.assertTrue(np.allclose(probs, target))
@@ -493,6 +644,10 @@ def test_probabilities_dict_ghz(self):
                 probs = stab.probabilities_dict(qargs)
                 target = {"00": 0.5, "11": 0.5}
                 self.assertDictAlmostEqual(probs, target)
+                target.update(StabilizerStateTestingTools._bitstring_product_dict(2, target))
+                StabilizerStateTestingTools._verify_individual_bitstrings(
+                    self, target, stab, qargs, dict_almost_equal=True
+                )
                 probs = stab.probabilities(qargs)
                 target = np.array([0.5, 0, 0, 0.5])
                 self.assertTrue(np.allclose(probs, target))
@@ -503,6 +658,9 @@ def test_probabilities_dict_ghz(self):
                 probs = stab.probabilities_dict(qargs)
                 target = {"0": 0.5, "1": 0.5}
                 self.assertDictAlmostEqual(probs, target)
+                StabilizerStateTestingTools._verify_individual_bitstrings(
+                    self, target, stab, qargs, dict_almost_equal=True
+                )
                 probs = stab.probabilities(qargs)
                 target = np.array([0.5, 0.5])
                 self.assertTrue(np.allclose(probs, target))
@@ -520,10 +678,17 @@ def test_probs_random_subsystem(self, num_qubits):
                 stab = StabilizerState(cliff)
                 probs = stab.probabilities(qargs)
                 probs_dict = stab.probabilities_dict(qargs)
+                StabilizerStateTestingTools._verify_individual_bitstrings(
+                    self, probs_dict, stab, qargs
+                )
                 target = Statevector(qc).probabilities(qargs)
                 target_dict = Statevector(qc).probabilities_dict(qargs)
+                Statevector(qc).probabilities_dict()
                 self.assertTrue(np.allclose(probs, target))
                 self.assertDictAlmostEqual(probs_dict, target_dict)
+                StabilizerStateTestingTools._verify_individual_bitstrings(
+                    self, target_dict, stab, qargs, dict_almost_equal=True
+                )
 
     @combine(num_qubits=[2, 3, 4, 5])
     def test_expval_from_random_clifford(self, num_qubits):
@@ -972,10 +1137,22 @@ def test_stabilizer_bell_equiv(self):
         # [XX, -ZZ] and [XX, YY] both generate the stabilizer group {II, XX, YY, -ZZ}
         self.assertTrue(cliff1.equiv(cliff2))
         self.assertEqual(cliff1.probabilities_dict(), cliff2.probabilities_dict())
+        StabilizerStateTestingTools._verify_individual_bitstrings(
+            self, cliff1.probabilities_dict(), cliff2
+        )
+        StabilizerStateTestingTools._verify_individual_bitstrings(
+            self, cliff2.probabilities_dict(), cliff1
+        )
 
         # [XX, ZZ] and [XX, -YY] both generate the stabilizer group {II, XX, -YY, ZZ}
         self.assertTrue(cliff3.equiv(cliff4))
         self.assertEqual(cliff3.probabilities_dict(), cliff4.probabilities_dict())
+        StabilizerStateTestingTools._verify_individual_bitstrings(
+            self, cliff3.probabilities_dict(), cliff4
+        )
+        StabilizerStateTestingTools._verify_individual_bitstrings(
+            self, cliff4.probabilities_dict(), cliff3
+        )
 
         self.assertFalse(cliff1.equiv(cliff3))
         self.assertFalse(cliff2.equiv(cliff4))