Skip to content

Commit

Permalink
Make numeric casts of ParameterExpression flexible (#6802)
Browse files Browse the repository at this point in the history
Previously, ParameterExpression would refuse numeric conversions if it
contained unbound parameters, even if the underlying symbolic expression
had a known, fixed value.  This patch passes the entire responsibility
for determining if a numeric conversion is possible to Sympy/Symengine,
meaning that expressions such as

    >>> x = Parameter('x')
    >>> float(x - x + 2)
    2.0

are now possible.

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
jakelishman and mergify[bot] authored Nov 19, 2021
1 parent cd7e1d5 commit 7a13cc8
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 16 deletions.
5 changes: 2 additions & 3 deletions qiskit/circuit/library/n_local/n_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
32 changes: 19 additions & 13 deletions qiskit/circuit/parameterexpression.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 14 additions & 0 deletions test/python/circuit/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand All @@ -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."""

Expand Down

0 comments on commit 7a13cc8

Please sign in to comment.