Repository: Qiskit/qiskit

Issue:
Statevector objects are not scriptable
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected behavior?

I would expect to be able to access `Statevector` data like:

```
state[idx]
```

but this is not currently possible because `__getitem__` is not defined to grab from `state.data`.



Discussion:
Can I work on this?

Resolving patch:
diff --git a/qiskit/quantum_info/states/statevector.py b/qiskit/quantum_info/states/statevector.py
--- a/qiskit/quantum_info/states/statevector.py
+++ b/qiskit/quantum_info/states/statevector.py
@@ -190,6 +190,31 @@ def _ipython_display_(self):
 
             display(out)
 
+    def __getitem__(self, key):
+        """Return Statevector item either by index or binary label
+        Args:
+            key (int or str): index or corresponding binary label, e.g. '01' = 1.
+
+        Returns:
+            numpy.complex128: Statevector item.
+
+        Raises:
+            QiskitError: if key is not valid.
+        """
+        if isinstance(key, str):
+            try:
+                key = int(key, 2)
+            except ValueError:
+                raise QiskitError(f"Key '{key}' is not a valid binary string.") from None
+        if isinstance(key, int):
+            if key >= self.dim:
+                raise QiskitError(f"Key {key} is greater than Statevector dimension {self.dim}.")
+            if key < 0:
+                raise QiskitError(f"Key {key} is not a valid positive value.")
+            return self._data[key]
+        else:
+            raise QiskitError("Key must be int or a valid binary string.")
+
     @property
     def data(self):
         """Return data."""
@@ -244,6 +269,28 @@ def tensor(self, other):
         ret._data = np.kron(self._data, other._data)
         return ret
 
+    def inner(self, other):
+        r"""Return the inner product of self and other as
+        :math:`\langle self| other \rangle`.
+
+        Args:
+            other (Statevector): a quantum state object.
+
+        Returns:
+            np.complex128: the inner product of self and other, :math:`\langle self| other \rangle`.
+
+        Raises:
+            QiskitError: if other is not a quantum state or has different dimension.
+        """
+        if not isinstance(other, Statevector):
+            other = Statevector(other)
+        if self.dims() != other.dims():
+            raise QiskitError(
+                f"Statevector dimensions do not match: {self.dims()} and {other.dims()}."
+            )
+        inner = np.vdot(self.data, other.data)
+        return inner
+
     def expand(self, other):
         """Return the tensor product state other ⊗ self.
 
@@ -805,6 +852,7 @@ def _evolve_instruction(statevec, obj, qargs=None):
                     obj.name, type(obj.definition)
                 )
             )
+
         if obj.definition.global_phase:
             statevec._data *= np.exp(1j * float(obj.definition.global_phase))
         qubits = {qubit: i for i, qubit in enumerate(obj.definition.qubits)}


Test patch:
diff --git a/test/python/quantum_info/states/test_statevector.py b/test/python/quantum_info/states/test_statevector.py
--- a/test/python/quantum_info/states/test_statevector.py
+++ b/test/python/quantum_info/states/test_statevector.py
@@ -208,6 +208,22 @@ def test_equal(self):
             vec = self.rand_vec(4)
             self.assertEqual(Statevector(vec), Statevector(vec.tolist()))
 
+    def test_getitem(self):
+        """Test __getitem__ method"""
+        for _ in range(10):
+            vec = self.rand_vec(4)
+            state = Statevector(vec)
+            for i in range(4):
+                self.assertEqual(state[i], vec[i])
+                self.assertEqual(state[format(i, "b")], vec[i])
+
+    def test_getitem_except(self):
+        """Test __getitem__ method raises exceptions."""
+        for i in range(1, 4):
+            state = Statevector(self.rand_vec(2 ** i))
+            self.assertRaises(QiskitError, state.__getitem__, 2 ** i)
+            self.assertRaises(QiskitError, state.__getitem__, -1)
+
     def test_copy(self):
         """Test Statevector copy method"""
         for _ in range(5):
@@ -335,6 +351,29 @@ def test_tensor(self):
             self.assertEqual(state.dims(), (3, 2))
             assert_allclose(state.data, target)
 
+    def test_inner(self):
+        """Test inner method."""
+        for _ in range(10):
+            vec0 = Statevector(self.rand_vec(4))
+            vec1 = Statevector(self.rand_vec(4))
+            target = np.vdot(vec0.data, vec1.data)
+            result = vec0.inner(vec1)
+            self.assertAlmostEqual(result, target)
+            vec0 = Statevector(self.rand_vec(6), dims=(2, 3))
+            vec1 = Statevector(self.rand_vec(6), dims=(2, 3))
+            target = np.vdot(vec0.data, vec1.data)
+            result = vec0.inner(vec1)
+            self.assertAlmostEqual(result, target)
+
+    def test_inner_except(self):
+        """Test inner method raises exceptions."""
+        vec0 = Statevector(self.rand_vec(4))
+        vec1 = Statevector(self.rand_vec(3))
+        self.assertRaises(QiskitError, vec0.inner, vec1)
+        vec0 = Statevector(self.rand_vec(6), dims=(2, 3))
+        vec1 = Statevector(self.rand_vec(6), dims=(3, 2))
+        self.assertRaises(QiskitError, vec0.inner, vec1)
+
     def test_add(self):
         """Test add method."""
         for _ in range(10):