diff --git a/qiskit/circuit/library/n_local/n_local.py b/qiskit/circuit/library/n_local/n_local.py index 42e757163a36..b2144f601e19 100644 --- a/qiskit/circuit/library/n_local/n_local.py +++ b/qiskit/circuit/library/n_local/n_local.py @@ -954,9 +954,8 @@ def _build(self) -> None: # cast global phase to float if it has no free parameters if isinstance(circuit.global_phase, ParameterExpression): try: - circuit.global_phase = float(circuit.global_phase._symbol_expr) - # RuntimeError is raised if symengine is used, for SymPy it is a TypeError - except (RuntimeError, TypeError): + circuit.global_phase = float(circuit.global_phase) + except TypeError: # expression contains free parameters pass diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 9512dcae529d..55d35af2a3bf 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -429,29 +429,35 @@ def __str__(self): return sstr(sympify(self._symbol_expr), full_prec=False) - def __float__(self): - if self.parameters: + def __complex__(self): + try: + return complex(self._symbol_expr) + # TypeError is for sympy, RuntimeError for symengine + except (TypeError, RuntimeError) as exc: raise TypeError( "ParameterExpression with unbound parameters ({}) " - "cannot be cast to a float.".format(self.parameters) - ) - return float(self._symbol_expr) + "cannot be cast to a complex.".format(self.parameters) + ) from exc - def __complex__(self): - if self.parameters: + def __float__(self): + try: + return float(self._symbol_expr) + # TypeError is for sympy, RuntimeError for symengine + except (TypeError, RuntimeError) as exc: raise TypeError( "ParameterExpression with unbound parameters ({}) " - "cannot be cast to a complex.".format(self.parameters) - ) - return complex(self._symbol_expr) + "cannot be cast to a float.".format(self.parameters) + ) from exc def __int__(self): - if self.parameters: + try: + return int(self._symbol_expr) + # TypeError is for sympy, RuntimeError for symengine + except (TypeError, RuntimeError) as exc: raise TypeError( "ParameterExpression with unbound parameters ({}) " "cannot be cast to an int.".format(self.parameters) - ) - return int(self._symbol_expr) + ) from exc def __hash__(self): return hash((frozenset(self._parameter_symbols), self._symbol_expr)) diff --git a/releasenotes/notes/more-forgiving-numeric-conversions-in-ParameterExpression-6cd7316c32c67c55.yaml b/releasenotes/notes/more-forgiving-numeric-conversions-in-ParameterExpression-6cd7316c32c67c55.yaml new file mode 100644 index 000000000000..be6835065446 --- /dev/null +++ b/releasenotes/notes/more-forgiving-numeric-conversions-in-ParameterExpression-6cd7316c32c67c55.yaml @@ -0,0 +1,14 @@ +features: + - | + :obj:`~qiskit.cicuit.ParameterExpression` now delegates its numeric + conversions to the underlying symbolic library, even if there are + potentially unbound parameters. This allows conversions of expressions such + as :: + + >>> from qiskit.circuit import Parameter + >>> x = Parameter('x') + >>> float(x - x + 2.3) + 2.3 + + where the underlying expression has a fixed value, but the parameter ``x`` + is not yet bound. diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index 403da7fdd436..d9fcb450617d 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -1187,6 +1187,13 @@ def test_cast_to_float_when_bound(self): bound_expr = x.bind({x: 2.3}) self.assertEqual(float(bound_expr), 2.3) + def test_cast_to_float_when_underlying_expression_bound(self): + """Verify expression can be cast to a float when it still contains unbound parameters, but + the underlying symbolic expression has a knowable value.""" + x = Parameter("x") + expr = x - x + 2.3 + self.assertEqual(float(expr), 2.3) + def test_raise_if_cast_to_float_when_not_fully_bound(self): """Verify raises if casting to float and not fully bound.""" @@ -1212,6 +1219,13 @@ def test_cast_to_int_when_bound_truncates_after_evaluation(self): bound_expr = (x + y).bind({x: 2.3, y: 0.8}) self.assertEqual(int(bound_expr), 3) + def test_cast_to_int_when_underlying_expression_bound(self): + """Verify expression can be cast to a int when it still contains unbound parameters, but the + underlying symbolic expression has a knowable value.""" + x = Parameter("x") + expr = x - x + 2.3 + self.assertEqual(int(expr), 2) + def test_raise_if_cast_to_int_when_not_fully_bound(self): """Verify raises if casting to int and not fully bound."""