Repository: Qiskit/qiskit

Issue:
Support powering Parameters
### What should we add?

At least integer powers should be supported. Floats can probably be supported too. Maybe even complex.

```python
from qiskit.circuit import Parameter

p = Parameter("x")

print(p * p)
print(p**2)
```
```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-7ebbd444bd39> in <module>
      4 
      5 print(p * p)
----> 6 print(p**2)

TypeError: unsupported operand type(s) for ** or pow(): 'Parameter' and 'int'
```
Support powering Parameters
### What should we add?

At least integer powers should be supported. Floats can probably be supported too. Maybe even complex.

```python
from qiskit.circuit import Parameter

p = Parameter("x")

print(p * p)
print(p**2)
```
```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-7ebbd444bd39> in <module>
      4 
      5 print(p * p)
----> 6 print(p**2)

TypeError: unsupported operand type(s) for ** or pow(): 'Parameter' and 'int'
```


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
@@ -362,6 +362,12 @@ def __truediv__(self, other):
     def __rtruediv__(self, other):
         return self._apply_operation(operator.truediv, other, reflected=True)
 
+    def __pow__(self, other):
+        return self._apply_operation(pow, other)
+
+    def __rpow__(self, other):
+        return self._apply_operation(pow, other, reflected=True)
+
     def _call(self, ufunc):
         return ParameterExpression(self._parameter_symbols, ufunc(self._symbol_expr))
 


Test patch:
diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py
--- a/test/python/circuit/test_parameters.py
+++ b/test/python/circuit/test_parameters.py
@@ -1332,7 +1332,14 @@ def _paramvec_names(prefix, length):
 class TestParameterExpressions(QiskitTestCase):
     """Test expressions of Parameters."""
 
-    supported_operations = [add, sub, mul, truediv]
+    # supported operations dictionary operation : accuracy (0=exact match)
+    supported_operations = {
+        add: 0,
+        sub: 0,
+        mul: 0,
+        truediv: 0,
+        pow: 1e-12,
+    }
 
     def setUp(self):
         super().setUp()
@@ -1504,12 +1511,19 @@ def test_expressions_of_parameter_with_constant(self):
 
         x = Parameter("x")
 
-        for op in self.supported_operations:
+        for op, rel_tol in self.supported_operations.items():
             for const in good_constants:
                 expr = op(const, x)
                 bound_expr = expr.bind({x: 2.3})
 
-                self.assertEqual(complex(bound_expr), op(const, 2.3))
+                res = complex(bound_expr)
+                expected = op(const, 2.3)
+                if rel_tol > 0:
+                    self.assertTrue(
+                        cmath.isclose(res, expected, rel_tol=rel_tol), f"{res} != {expected}"
+                    )
+                else:
+                    self.assertEqual(res, expected)
 
                 # Division by zero will raise. Tested elsewhere.
                 if const == 0 and op == truediv:
@@ -1954,6 +1968,21 @@ def test_parameter_expression_grad(self):
             self.assertEqual(expr.gradient(x), 2 * x)
             self.assertEqual(expr.gradient(x).gradient(x), 2)
 
+    def test_parameter_expression_exp_log_vs_pow(self):
+        """Test exp, log, pow for ParameterExpressions by asserting x**y = exp(y log(x))."""
+
+        x = Parameter("x")
+        y = Parameter("y")
+        pow1 = x**y
+        pow2 = (y * x.log()).exp()
+        for x_val in [2, 1.3, numpy.pi]:
+            for y_val in [2, 1.3, 0, -1, -1.0, numpy.pi, 1j]:
+                with self.subTest(msg="with x={x_val}, y={y_val}"):
+                    vals = {x: x_val, y: y_val}
+                    pow1_val = pow1.bind(vals)
+                    pow2_val = pow2.bind(vals)
+                    self.assertTrue(cmath.isclose(pow1_val, pow2_val), f"{pow1_val} != {pow2_val}")
+
     def test_bound_expression_is_real(self):
         """Test is_real on bound parameters."""
         x = Parameter("x")