Repository: Qiskit/qiskit

Issue:
ParameterExpression throws an error for gradients with complex coefficients.
<!-- ⚠️ 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?

When having a `ParameterExpression` with an imaginary unit, taking a gradient w.r.t. a parameter existing in a `ParameterExpression` results in an error.

### Steps to reproduce the problem

```
p = Parameter("theta")
p2 = 1j*p
p2
Out[27]: ParameterExpression(1.0*I*theta)
p2.gradient(p)
Traceback (most recent call last):
  File "/qiskit-terra/venv/lib/python3.8/site-packages/IPython/core/interactiveshell.py", line 3437, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-28-058190e5d73e>", line 1, in <module>
    p2.gradient(p)
  File "/qiskit-terra/qiskit/circuit/parameterexpression.py", line 303, in gradient
    return float(expr_grad)
  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
```

### What is the expected behavior?

Calculating a gradient with a complex coefficient.

### Suggested solutions
Perhaps
```
  File "/qiskit-terra/qiskit/circuit/parameterexpression.py", line 303, in gradient
    return float(expr_grad)
```
should be changed to
```
  File "/qiskit-terra/qiskit/circuit/parameterexpression.py", line 303, in gradient
    return complex(expr_grad)
```
?


Discussion:

Resolving patch:
diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py
--- a/qiskit/circuit/parameterexpression.py
+++ b/qiskit/circuit/parameterexpression.py
@@ -265,7 +265,7 @@ def _apply_operation(
 
         return ParameterExpression(parameter_symbols, expr)
 
-    def gradient(self, param) -> Union["ParameterExpression", float]:
+    def gradient(self, param) -> Union["ParameterExpression", complex]:
         """Get the derivative of a parameter expression w.r.t. a specified parameter expression.
 
         Args:
@@ -273,6 +273,7 @@ def gradient(self, param) -> Union["ParameterExpression", float]:
 
         Returns:
             ParameterExpression representing the gradient of param_expr w.r.t. param
+            or complex or float number
         """
         # Check if the parameter is contained in the parameter expression
         if param not in self._parameter_symbols.keys():
@@ -299,8 +300,12 @@ def gradient(self, param) -> Union["ParameterExpression", float]:
         # If the gradient corresponds to a parameter expression then return the new expression.
         if len(parameter_symbols) > 0:
             return ParameterExpression(parameter_symbols, expr=expr_grad)
-        # If no free symbols left, return a float corresponding to the gradient.
-        return float(expr_grad)
+        # If no free symbols left, return a complex or float gradient
+        expr_grad_cplx = complex(expr_grad)
+        if expr_grad_cplx.imag != 0:
+            return expr_grad_cplx
+        else:
+            return float(expr_grad)
 
     def __add__(self, other):
         return self._apply_operation(operator.add, other)


Test patch:
diff --git a/test/python/opflow/test_gradients.py b/test/python/opflow/test_gradients.py
--- a/test/python/opflow/test_gradients.py
+++ b/test/python/opflow/test_gradients.py
@@ -1089,6 +1089,14 @@ def test_converted_to_float_if_bound(self):
         grad = expr.gradient(x)
         self.assertIsInstance(grad, float)
 
+    def test_converted_to_complex_if_bound(self):
+        """Test the gradient is a complex when no free symbols are left."""
+        x = Parameter("x")
+        x2 = 1j * x
+        expr = 2 * x2 + 1
+        grad = expr.gradient(x)
+        self.assertIsInstance(grad, complex)
+
 
 @ddt
 class TestQFI(QiskitOpflowTestCase):