Repository: Qiskit/qiskit

Issue:
PrimitiveOp does not handle binding to complex values.
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: 0.18
- **Python version**: 3.8
- **Operating system**: MacOS

### What is the current behavior?
Error when binding a complex coefficient to an operator's parameter.
```
    return self.assign_parameters(param_dict)
  File "/qiskit-terra/qiskit/opflow/primitive_ops/primitive_op.py", line 249, in assign_parameters
    param_value = float(self.coeff.bind(binds))
  File "/qiskit-terra/qiskit/circuit/parameterexpression.py", line 431, in __float__
    return float(self._symbol_expr)
  File "symengine_wrapper.pyx", line 1133, in symengine.lib.symengine_wrapper.Basic.__float__
  File "symengine_wrapper.pyx", line 958, in symengine.lib.symengine_wrapper.Basic.n
  File "symengine_wrapper.pyx", line 4194, in symengine.lib.symengine_wrapper.evalf
RuntimeError: Not Implemented
```

### Steps to reproduce the problem

```
from qiskit.opflow import X
from qiskit.circuit import Parameter
th = Parameter('th')
(th * X).bind_parameters({th: 1j})
```

### What is the expected behavior?

No error, handling a potentially complex value.

### Suggested solutions

In File "/qiskit-terra/qiskit/opflow/primitive_ops/primitive_op.py", line 431, changing
    `param_value = float(self.coeff.bind(binds))`
to  `param_value = complex(self.coeff.bind(binds))`.



Discussion:

Resolving patch:
diff --git a/qiskit/opflow/primitive_ops/primitive_op.py b/qiskit/opflow/primitive_ops/primitive_op.py
--- a/qiskit/opflow/primitive_ops/primitive_op.py
+++ b/qiskit/opflow/primitive_ops/primitive_op.py
@@ -246,7 +246,9 @@ def assign_parameters(self, param_dict: dict) -> OperatorBase:
                 return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict])
             if self.coeff.parameters <= set(unrolled_dict.keys()):
                 binds = {param: unrolled_dict[param] for param in self.coeff.parameters}
-                param_value = float(self.coeff.bind(binds))
+                param_value = complex(self.coeff.bind(binds))
+                if abs(param_value.imag) == 0:
+                    param_value = param_value.real
         return self.__class__(self.primitive, coeff=param_value)
 
     # Nothing to collapse here.


Test patch:
diff --git a/test/python/opflow/test_evolution.py b/test/python/opflow/test_evolution.py
--- a/test/python/opflow/test_evolution.py
+++ b/test/python/opflow/test_evolution.py
@@ -197,6 +197,17 @@ def test_bind_parameter_list(self):
         for p in thetas:
             self.assertIn(p, evo.to_circuit().parameters)
 
+    def test_bind_parameters_complex(self):
+        """bind parameters with a complex value test"""
+        th1 = Parameter("th1")
+        th2 = Parameter("th2")
+
+        operator = th1 * X + th2 * Y
+        bound_operator = operator.bind_parameters({th1: 3j, th2: 2})
+
+        expected_bound_operator = SummedOp([3j * X, (2 + 0j) * Y])
+        self.assertEqual(bound_operator, expected_bound_operator)
+
     def test_qdrift(self):
         """QDrift test"""
         op = (2 * Z ^ Z) + (3 * X ^ X) - (4 * Y ^ Y) + (0.5 * Z ^ I)