Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle huge integers in OpenQASM 2 expression evaluator #12140

Merged
merged 2 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading