Skip to content

Commit

Permalink
Handle huge integers in OpenQASM 2 expression evaluator (#12140) (#12400
Browse files Browse the repository at this point in the history
)

* Handle huge integers in OpenQASM 2 expression evaluator

This modifies the expression evaluator to directly parse the backing
string data of an integer token in a floating-point context, which lets
us handle numbers that would otherwise overflow a `usize`.  It's
possible for this to be totally valid, if, for example, the integer is a
multiple of some very large power of two that doesn't overflow a
double-precision float.

We already needed to immediately cast the integer to a float, so this is
just a more accurate way of doing the evaluation, and doesn't affect
when we use integers in other contexts.

* Clarify int/float split

(cherry picked from commit fe69594)

Co-authored-by: Jake Lishman <[email protected]>
  • Loading branch information
mergify[bot] and jakelishman authored May 14, 2024
1 parent 49bd0d3 commit 9685476
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 4 deletions.
14 changes: 12 additions & 2 deletions crates/qasm2/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,8 +501,13 @@ impl<'a> ExprParser<'a> {
| TokenType::Sin
| TokenType::Sqrt
| TokenType::Tan => Ok(Some(Atom::Function(token.ttype.into()))),
TokenType::Real => Ok(Some(Atom::Const(token.real(self.context)))),
TokenType::Integer => Ok(Some(Atom::Const(token.int(self.context) as f64))),
// This deliberately parses an _integer_ token as a float, since all OpenQASM 2.0
// integers can be interpreted as floats, and doing that allows us to gracefully handle
// cases where a huge float would overflow a `usize`. Never mind that in such a case,
// there's almost certainly precision loss from the floating-point representating
// having insufficient mantissa digits to faithfully represent the angle mod 2pi;
// that's not our fault in the parser.
TokenType::Real | TokenType::Integer => Ok(Some(Atom::Const(token.real(self.context)))),
TokenType::Pi => Ok(Some(Atom::Const(f64::consts::PI))),
TokenType::Id => {
let id = token.text(self.context);
Expand Down Expand Up @@ -698,6 +703,11 @@ impl<'a> ExprParser<'a> {

/// Parse a single expression completely. This is the only public entry point to the
/// operator-precedence parser.
///
/// .. note::
///
/// This evaluates in a floating-point context, including evaluating integer tokens, since
/// the only places that expressions are valid in OpenQASM 2 is during gate applications.
pub fn parse_expression(&mut self, cause: &Token) -> PyResult<Expr> {
self.eval_expression(0, cause)
}
Expand Down
4 changes: 2 additions & 2 deletions crates/qasm2/src/lex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,9 @@ impl Token {
}

/// If the token is a real number, this method can be called to evaluate its value. Panics if
/// the token is not a real number.
/// the token is not a float or an integer.
pub fn real(&self, context: &TokenContext) -> f64 {
if self.ttype != TokenType::Real {
if !(self.ttype == TokenType::Real || self.ttype == TokenType::Integer) {
panic!()
}
context.text[self.index].parse().unwrap()
Expand Down
9 changes: 9 additions & 0 deletions releasenotes/notes/qasm2-bigint-8eff42acb67903e6.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
fixes:
- |
The OpenQASM 2.0 parser (:func:`.qasm2.load` and :func:`.qasm2.loads`) can now evaluate
gate-angle expressions including integer operands that would overflow the system-size integer.
These will be evaluated in a double-precision floating-point context, just like the rest of the
expression always has been. Beware: an arbitrarily large integer will not necessarily be
exactly representable in double-precision floating-point, so there is a chance that however the
circuit was generated, it had already lost all numerical precision modulo :math:`2\pi`.
12 changes: 12 additions & 0 deletions test/python/qasm2/test_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,18 @@ def test_function_symbolic(self, function_str, function_py):
actual = [float(x) for x in abstract_op.definition.data[0].operation.params]
self.assertEqual(list(actual), expected)

def test_bigint(self):
"""Test that an expression can be evaluated even if it contains an integer that will
overflow the integer handling."""
bigint = 1 << 200
# Sanity check that the number we're trying for is represented at full precision in floating
# point (which it should be - it's a power of two with fewer than 11 bits of exponent).
self.assertEqual(int(float(bigint)), bigint)
program = f"qreg q[1]; U({bigint}, -{bigint}, {bigint} * 2.0) q[0];"
parsed = qiskit.qasm2.loads(program)
parameters = list(parsed.data[0].operation.params)
self.assertEqual([bigint, -bigint, 2 * bigint], parameters)


class TestPrecedenceAssociativity(QiskitTestCase):
def test_precedence(self):
Expand Down

0 comments on commit 9685476

Please sign in to comment.