Repository: Qiskit/qiskit-terra (python)

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

Pull request:
Add an equiv method to the StabilizerState class
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->

### Summary

Add an `equiv` method to the `StabilizerState` class, that checks if the generating sets of two stabilizer states generate the same stabilizer group.

close #9338 

### Details and comments




Patch:
diff --git a/qiskit/quantum_info/states/stabilizerstate.py b/qiskit/quantum_info/states/stabilizerstate.py
index 0fc62d6b3..18aa1d541 100644
--- a/qiskit/quantum_info/states/stabilizerstate.py
+++ b/qiskit/quantum_info/states/stabilizerstate.py
@@ -18,7 +18,7 @@ import numpy as np
 
 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 @@ class StabilizerState(QuantumState):
 
         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.
 
diff --git a/releasenotes/notes/add-equiv-stabilizerstate-6ef8790c765690c1.yaml b/releasenotes/notes/add-equiv-stabilizerstate-6ef8790c765690c1.yaml
new file mode 100644
index 000000000..93f5bdce2
--- /dev/null
+++ b/releasenotes/notes/add-equiv-stabilizerstate-6ef8790c765690c1.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Added the method :class:`.StabilizerState.equiv`,
+    that checks if the generating sets of two stabilizer states generate the same stabilizer group.
+    For example, the stabilizer group of the two-qubit Bell state contains the four elements
+    :math:`\{II, XX, -YY, ZZ\}` and hence can be generated by either :math:`[XX, ZZ]`,
+    :math:`[XX, -YY]` or :math:`[-YY, ZZ]`.


Test patch:
diff --git a/test/python/quantum_info/states/test_stabilizerstate.py b/test/python/quantum_info/states/test_stabilizerstate.py
index 80ab7f2c5..c6a9e6bb7 100644
--- a/test/python/quantum_info/states/test_stabilizerstate.py
+++ b/test/python/quantum_info/states/test_stabilizerstate.py
@@ -936,6 +936,50 @@ class TestStabilizerStateExpectationValue(QiskitTestCase):
             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()