diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 52848e2e77..083325efad 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -306,9 +306,9 @@ Parametric Quantum Circuits .. autosummary:: :toctree: ../stubs/ - Parameter - ParameterVector - ParameterExpression + Parameter + ParameterVector + ParameterExpression Random Circuits --------------- diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 0d5015a292..6858efa130 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -498,6 +498,21 @@ class ParameterExpression: def __deepcopy__(self, memo=None): return self + def __abs__(self): + """Absolute of a ParameterExpression""" + if _optionals.HAS_SYMENGINE: + import symengine + + return self._call(symengine.Abs) + else: + from sympy import Abs as _abs + + return self._call(_abs) + + def abs(self): + """Absolute of a ParameterExpression""" + return self.__abs__() + def __eq__(self, other): """Check if this parameter expression is equal to another parameter expression or a fixed value (only if this is a bound expression). diff --git a/releasenotes/notes/add-abs-to-parameterexpression-347ffef62946b38b.yaml b/releasenotes/notes/add-abs-to-parameterexpression-347ffef62946b38b.yaml new file mode 100644 index 0000000000..1385724d8d --- /dev/null +++ b/releasenotes/notes/add-abs-to-parameterexpression-347ffef62946b38b.yaml @@ -0,0 +1,14 @@ + +issues: + - | + Added support for taking absolute values of :class:`.ParameterExpression`\s. For example, + the following is now possible:: + + from qiskit.circuit import QuantumCircuit, Parameter + + x = Parameter("x") + circuit = QuantumCircuit(1) + circuit.rx(abs(x), 0) + + bound = circuit.bind_parameters({x: -1}) + diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index 5f49f026a7..e19fbd2051 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -1113,6 +1113,7 @@ class TestParameters(QiskitTestCase): theta = Parameter(name="theta") qc = QuantumCircuit(2) + qc.p(numpy.abs(-phi), 0) qc.p(numpy.cos(phi), 0) qc.p(numpy.sin(phi), 0) qc.p(numpy.tan(phi), 0) @@ -1123,6 +1124,7 @@ class TestParameters(QiskitTestCase): qc.assign_parameters({phi: pi, theta: 1}, inplace=True) qc_ref = QuantumCircuit(2) + qc_ref.p(pi, 0) qc_ref.p(-1, 0) qc_ref.p(0, 0) qc_ref.p(0, 0) @@ -1133,14 +1135,40 @@ class TestParameters(QiskitTestCase): self.assertEqual(qc, qc_ref) def test_compile_with_ufunc(self): - """Test compiling of circuit with unbounded parameters + """Test compiling of circuit with unbound parameters after we apply universal functions.""" - phi = Parameter("phi") - qc = QuantumCircuit(1) - qc.rx(numpy.cos(phi), 0) - backend = BasicAer.get_backend("qasm_simulator") - qc_aer = transpile(qc, backend) - self.assertIn(phi, qc_aer.parameters) + from math import pi + + theta = ParameterVector("theta", length=7) + + qc = QuantumCircuit(7) + qc.rx(numpy.abs(theta[0]), 0) + qc.rx(numpy.cos(theta[1]), 1) + qc.rx(numpy.sin(theta[2]), 2) + qc.rx(numpy.tan(theta[3]), 3) + qc.rx(numpy.arccos(theta[4]), 4) + qc.rx(numpy.arctan(theta[5]), 5) + qc.rx(numpy.arcsin(theta[6]), 6) + + # transpile to different basis + transpiled = transpile(qc, basis_gates=["rz", "sx", "x", "cx"], optimization_level=0) + + for x in theta: + self.assertIn(x, transpiled.parameters) + + bound = transpiled.bind_parameters({theta: [-1, pi, pi, pi, 1, 1, 1]}) + + expected = QuantumCircuit(7) + expected.rx(1.0, 0) + expected.rx(-1.0, 1) + expected.rx(0.0, 2) + expected.rx(0.0, 3) + expected.rx(0.0, 4) + expected.rx(pi / 4, 5) + expected.rx(pi / 2, 6) + expected = transpile(expected, basis_gates=["rz", "sx", "x", "cx"], optimization_level=0) + + self.assertEqual(expected, bound) def test_parametervector_resize(self): """Test the resize method of the parameter vector.""" @@ -1207,6 +1235,29 @@ class TestParameterExpressions(QiskitTestCase): bound_expr = x.bind({x: 2.3}) self.assertEqual(bound_expr, 2.3) + def test_abs_function_when_bound(self): + """Verify expression can be used with + abs functions when bound.""" + + x = Parameter("x") + xb_1 = x.bind({x: 2.0}) + xb_2 = x.bind({x: 3.0 + 4.0j}) + + self.assertEqual(abs(xb_1), 2.0) + self.assertEqual(abs(-xb_1), 2.0) + self.assertEqual(abs(xb_2), 5.0) + + def test_abs_function_when_not_bound(self): + """Verify expression can be used with + abs functions when not bound.""" + + x = Parameter("x") + y = Parameter("y") + + self.assertEqual(abs(x), abs(-x)) + self.assertEqual(abs(x) * abs(y), abs(x * y)) + self.assertEqual(abs(x) / abs(y), abs(x / y)) + def test_cast_to_float_when_bound(self): """Verify expression can be cast to a float when fully bound."""