Repository: Qiskit/qiskit

Issue:
Add an equiv method to the StabilizerState class
### What should we add?

Add an `equiv` method to the `StabilizerState` class, that checks if the generating sets of two stabilizer states generate the same stabilizer group.

This can be done as follows:
The first step would be to check that each stabilizer from the first set commutes with each stabilizer from the second set.
If no, the two sets give rise to different stabilizer states. If yes, compute the expected value of each stabilizer from the first set on the stabilizer state determined by the second set. The two stabilizer states coincide iff the expected value is +1 for each stabilizer.

See also: https://github.com/Qiskit/qiskit-terra/pull/5907#discussion_r628484220


Discussion:
Could I be assigned to this issue?
> Could I be assigned to this issue?

@shashankiswara0 - thanks for your suggestion, however we already have an internal code for this, and we only need to migrate it to qiskit. 

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
@@ -18,7 +18,7 @@
 
 from qiskit.exceptions import QiskitError
 from qiskit.quantum_info.operators.op_shape import OpShape
-from qiskit.quantum_info.operators.symplectic import Clifford, Pauli
+from qiskit.quantum_info.operators.symplectic import Clifford, Pauli, PauliList
 from qiskit.quantum_info.operators.symplectic.clifford_circuits import _append_x
 from qiskit.quantum_info.states.quantum_state import QuantumState
 
@@ -266,6 +266,47 @@ def expectation_value(self, oper, qargs=None):
 
         return pauli_phase
 
+    def equiv(self, other):
+        """Return True if the two generating sets generate the same stabilizer group.
+
+        Args:
+            other (StabilizerState): another StabilizerState.
+
+        Returns:
+            bool: True if other has a generating set that generates the same StabilizerState.
+        """
+        if not isinstance(other, StabilizerState):
+            try:
+                other = StabilizerState(other)
+            except QiskitError:
+                return False
+
+        num_qubits = self.num_qubits
+        if other.num_qubits != num_qubits:
+            return False
+
+        pauli_orig = PauliList.from_symplectic(
+            self._data.stab_z, self._data.stab_x, 2 * self._data.stab_phase
+        )
+        pauli_other = PauliList.from_symplectic(
+            other._data.stab_z, other._data.stab_x, 2 * other._data.stab_phase
+        )
+
+        #  Check that each stabilizer from the original set commutes with each stabilizer
+        #  from the other set
+        if not np.all([pauli.commutes(pauli_other) for pauli in pauli_orig]):
+            return False
+
+        # Compute the expected value of each stabilizer from the original set on the stabilizer state
+        # determined by the other set. The two stabilizer states coincide if and only if the
+        # expected value is +1 for each stabilizer
+        for i in range(num_qubits):
+            exp_val = self.expectation_value(pauli_other[i])
+            if exp_val != 1:
+                return False
+
+        return True
+
     def probabilities(self, qargs=None, decimals=None):
         """Return the subsystem measurement probability vector.
 


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
@@ -936,6 +936,50 @@ def test_expval_random_subsystem(self, num_qubits):
             target = Statevector(qc).expectation_value(op, qargs)
             self.assertAlmostEqual(exp_val, target)
 
+    def test_stabilizer_bell_equiv(self):
+        """Test that two circuits produce the same stabilizer group."""
+
+        qc1 = QuantumCircuit(2)
+        qc1.h(0)
+        qc1.x(1)
+        qc1.cx(0, 1)
+
+        qc2 = QuantumCircuit(2)
+        qc2.h(0)
+        qc2.cx(0, 1)
+        qc2.sdg(0)
+        qc2.sdg(1)
+        qc2.h(0)
+        qc2.h(1)
+
+        qc3 = QuantumCircuit(2)
+        qc3.h(0)
+        qc3.cx(0, 1)
+
+        qc4 = QuantumCircuit(2)
+        qc4.h(0)
+        qc4.cx(0, 1)
+        qc4.s(0)
+        qc4.sdg(1)
+        qc4.h(0)
+        qc4.h(1)
+
+        cliff1 = StabilizerState(qc1)  # ['+XX', '-ZZ']
+        cliff2 = StabilizerState(qc2)  # ['+YY', '+XX']
+        cliff3 = StabilizerState(qc3)  # ['+XX', '+ZZ']
+        cliff4 = StabilizerState(qc4)  # ['-YY', '+XX']
+
+        # [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())
+
+        # [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())
+
+        self.assertFalse(cliff1.equiv(cliff3))
+        self.assertFalse(cliff2.equiv(cliff4))
+
 
 if __name__ == "__main__":
     unittest.main()