From 5048020429dc81854fdaa111d51c63ad3cdb8f7c Mon Sep 17 00:00:00 2001 From: grossardt <99898527+grossardt@users.noreply.github.com> Date: Mon, 13 Nov 2023 00:40:05 +0100 Subject: [PATCH 1/2] Add pow for Parameter Adds powering for qiskit.circuit.ParameterExpression allowing to use a**n, n**a, a**b, pow(a,b) etc. for ParameterExpressions a, b and numbers n. Minimal change using default support of pow by Sympy/Symengine. Added pow to list of operators in TestParameterExpressions test case for unit testing. fixes #8959 Changelog: New Feature --- qiskit/circuit/parameterexpression.py | 6 ++++++ releasenotes/notes/add-parameter-pow-ff5f8d10813f5733.yaml | 7 +++++++ test/python/circuit/test_parameters.py | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-parameter-pow-ff5f8d10813f5733.yaml diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 6b88cede34e1..a54111d66b9b 100644 --- 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)) diff --git a/releasenotes/notes/add-parameter-pow-ff5f8d10813f5733.yaml b/releasenotes/notes/add-parameter-pow-ff5f8d10813f5733.yaml new file mode 100644 index 000000000000..094266071990 --- /dev/null +++ b/releasenotes/notes/add-parameter-pow-ff5f8d10813f5733.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + :class:`~qiskit.circuit.ParameterExpression` (and thus also + :class:`~qiskit.circuit.Parameter`) now support powering: :code:`x**y` + where :code:`x` and :code:`y` can be any combination of + :class:`~qiskit.circuit.ParameterExpression` and number types. diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index c694b39e6e7a..cfef3b6e4b63 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -1332,7 +1332,7 @@ def _paramvec_names(prefix, length): class TestParameterExpressions(QiskitTestCase): """Test expressions of Parameters.""" - supported_operations = [add, sub, mul, truediv] + supported_operations = [add, sub, mul, truediv, pow] def setUp(self): super().setUp() From 0331552d3abf73e49e590b69a47bfb6014b83dc8 Mon Sep 17 00:00:00 2001 From: grossardt <99898527+grossardt@users.noreply.github.com> Date: Sat, 18 Nov 2023 13:54:42 +0100 Subject: [PATCH 2/2] Update test_parameters.py --- test/python/circuit/test_parameters.py | 35 +++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index cfef3b6e4b63..6a5f780a96af 100644 --- 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, pow] + # 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")