Repository: Qiskit/qiskit

Issue:
Sampler fails on gates with unhashable parameters
### Environment

- **Qiskit Terra version**: fbff44bfe9ebc9d97203929b1bff5483fe06028a
- **Python version**: Python 3.10.8
- **Operating system**: Arch Linux


### What is happening?

See title.

### How can we reproduce the issue?

```python
import numpy as np

from qiskit.circuit import QuantumCircuit
from qiskit.extensions.unitary import UnitaryGate
from qiskit.primitives import Sampler

gate = UnitaryGate(np.eye(2))

circuit = QuantumCircuit(1)
circuit.append(gate, [0])
circuit.measure_all()

sampler = Sampler()
sampler_result = sampler.run([circuit]).result()
quasi_dists = sampler_result.quasi_dists
```
```
Traceback (most recent call last):
  File "/home/kjs/projects/qiskit-terra/scratch/sampler_hash_bug.py", line 14, in <module>
    sampler_result = sampler.run([circuit]).result()
  File "/home/kjs/projects/qiskit-terra/qiskit/primitives/base/base_sampler.py", line 184, in run
    return self._run(
  File "/home/kjs/projects/qiskit-terra/qiskit/primitives/sampler.py", line 146, in _run
    index = self._circuit_ids.get(key)
TypeError: unhashable type: 'numpy.ndarray'
```

### What should happen?

It should work.

### Any suggestions?

It appears the primitives rely on associating a hashable key with each circuit https://github.com/Qiskit/qiskit-terra/blob/514e38394657802e6cf4aefbf65fe3be3b23b783/qiskit/primitives/utils.py#L127. This is not working.


Discussion:

Resolving patch:
diff --git a/qiskit/primitives/utils.py b/qiskit/primitives/utils.py
--- a/qiskit/primitives/utils.py
+++ b/qiskit/primitives/utils.py
@@ -12,9 +12,10 @@
 """
 Utility functions for primitives
 """
-
 from __future__ import annotations
 
+import numpy as np
+
 from qiskit.circuit import Instruction, ParameterExpression, QuantumCircuit
 from qiskit.circuit.bit import Bit
 from qiskit.extensions.quantum_initializer.initializer import Initialize
@@ -146,7 +147,10 @@ def _circuit_key(circuit: QuantumCircuit, functional: bool = True) -> tuple:
                 _bits_key(data.qubits, circuit),  # qubits
                 _bits_key(data.clbits, circuit),  # clbits
                 data.operation.name,  # operation.name
-                tuple(data.operation.params),  # operation.params
+                tuple(
+                    param.data.tobytes() if isinstance(param, np.ndarray) else param
+                    for param in data.operation.params
+                ),  # operation.params
             )
             for data in circuit.data
         ),


Test patch:
diff --git a/test/python/primitives/test_sampler.py b/test/python/primitives/test_sampler.py
--- a/test/python/primitives/test_sampler.py
+++ b/test/python/primitives/test_sampler.py
@@ -22,6 +22,7 @@
 from qiskit.circuit import Parameter
 from qiskit.circuit.library import RealAmplitudes
 from qiskit.exceptions import QiskitError
+from qiskit.extensions.unitary import UnitaryGate
 from qiskit.primitives import Sampler, SamplerResult
 from qiskit.primitives.utils import _circuit_key
 from qiskit.providers import JobStatus, JobV1
@@ -770,6 +771,18 @@ def test_with_scheduling(n):
             keys = [_circuit_key(test_with_scheduling(i)) for i in range(1, 5)]
             self.assertEqual(len(keys), len(set(keys)))
 
+    def test_circuit_with_unitary(self):
+        """Test for circuit with unitary gate."""
+        gate = UnitaryGate(np.eye(2))
+
+        circuit = QuantumCircuit(1)
+        circuit.append(gate, [0])
+        circuit.measure_all()
+
+        sampler = Sampler()
+        sampler_result = sampler.run([circuit]).result()
+        self.assertDictAlmostEqual(sampler_result.quasi_dists[0], {0: 1, 1: 0})
+
 
 if __name__ == "__main__":
     unittest.main()