Repository: Qiskit/qiskit (python)

Issue:
PanicException: called `Result::unwrap()` on an `Err` value: ParseIntError { kind: PosOverflow }
### Environment

- **Qiskit Terra version**: qiskit 0.44.1
- **Python version**: 3.9
- **Operating system**: linux

### What is happening?

I'm simply trying to generate a QuantumCircuit object from different QASM files. Why can some files be generated successfully while others result in an error?


### How can we reproduce the issue?

'''from qiskit import QuantumCircuit
qc = QuantumCircuit.from_qasm_file("ae_indep_qiskit_69.qasm")'''

### What should happen?

generate a QuantumCircuit object

### Any suggestions?

_No response_

Pull request description:
Handle huge integers in OpenQASM 2 expression evaluator (backport #12140)
### Summary

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.



### Details and comments

Fix #11561
<hr>This is an automatic backport of pull request #12140 done by [Mergify](https://mergify.com).

Patch:
diff --git a/crates/qasm2/src/expr.rs b/crates/qasm2/src/expr.rs
index f7faad0c6..d8a08080a 100644
--- a/crates/qasm2/src/expr.rs
+++ b/crates/qasm2/src/expr.rs
@@ -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);
@@ -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)
     }
diff --git a/crates/qasm2/src/lex.rs b/crates/qasm2/src/lex.rs
index 024681b87..f9f674cbc 100644
--- a/crates/qasm2/src/lex.rs
+++ b/crates/qasm2/src/lex.rs
@@ -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()
diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
index cf51579be..440a0319c 100644
--- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
+++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
@@ -1165,6 +1165,8 @@ class SparsePauliOp(LinearOp):
                 raise QiskitError("Provided layout contains indices outside the number of qubits.")
             if len(set(layout)) != len(layout):
                 raise QiskitError("Provided layout contains duplicate indices.")
+        if self.num_qubits == 0:
+            return type(self)(["I" * n_qubits] * self.size, self.coeffs)
         new_op = type(self)("I" * n_qubits)
         return new_op.compose(self, qargs=layout)
 
diff --git a/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml b/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml
new file mode 100644
index 000000000..117230aee
--- /dev/null
+++ b/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml
@@ -0,0 +1,10 @@
+fixes:
+  - |
+    Fixed :meth:`.SparsePauliOp.apply_layout` to work correctly with zero-qubit operators.
+    For example, if you previously created a 0 qubit and applied a layout like::
+    
+        op = SparsePauliOp("")
+        op.apply_layout(None, 3)
+
+    this would have previously raised an error. Now this will correctly return an operator of the form:
+    ``SparsePauliOp(['III'], coeffs=[1.+0.j])``
diff --git a/releasenotes/notes/qasm2-bigint-8eff42acb67903e6.yaml b/releasenotes/notes/qasm2-bigint-8eff42acb67903e6.yaml
new file mode 100644
index 000000000..2fb1b4dcc
--- /dev/null
+++ b/releasenotes/notes/qasm2-bigint-8eff42acb67903e6.yaml
@@ -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`.


Test patch:
diff --git a/test/python/qasm2/test_expression.py b/test/python/qasm2/test_expression.py
index 98aead7f3..2ef35abde 100644
--- a/test/python/qasm2/test_expression.py
+++ b/test/python/qasm2/test_expression.py
@@ -123,6 +123,18 @@ class TestSimple(QiskitTestCase):
         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):
diff --git a/test/python/quantum_info/operators/symplectic/test_pauli.py b/test/python/quantum_info/operators/symplectic/test_pauli.py
index 89324e821..35acd46a4 100644
--- a/test/python/quantum_info/operators/symplectic/test_pauli.py
+++ b/test/python/quantum_info/operators/symplectic/test_pauli.py
@@ -14,42 +14,41 @@
 
 """Tests for Pauli operator class."""
 
+import itertools as it
 import re
 import unittest
-import itertools as it
 from functools import lru_cache
+from test import QiskitTestCase, combine
+
 import numpy as np
-from ddt import ddt, data, unpack
+from ddt import data, ddt, unpack
 
 from qiskit import QuantumCircuit
 from qiskit.circuit import Qubit
-from qiskit.exceptions import QiskitError
 from qiskit.circuit.library import (
-    IGate,
-    XGate,
-    YGate,
-    ZGate,
-    HGate,
-    SGate,
-    SdgGate,
     CXGate,
-    CZGate,
     CYGate,
-    SwapGate,
+    CZGate,
     ECRGate,
     EfficientSU2,
+    HGate,
+    IGate,
+    SdgGate,
+    SGate,
+    SwapGate,
+    XGate,
+    YGate,
+    ZGate,
 )
 from qiskit.circuit.library.generalized_gates import PauliGate
 from qiskit.compiler.transpiler import transpile
-from qiskit.providers.fake_provider import GenericBackendV2
+from qiskit.exceptions import QiskitError
 from qiskit.primitives import BackendEstimator
+from qiskit.providers.fake_provider import GenericBackendV2
+from qiskit.quantum_info.operators import Operator, Pauli, SparsePauliOp
 from qiskit.quantum_info.random import random_clifford, random_pauli
-from qiskit.quantum_info.operators import Pauli, Operator, SparsePauliOp
 from qiskit.utils import optionals
 
-from test import QiskitTestCase  # pylint: disable=wrong-import-order
-
-
 LABEL_REGEX = re.compile(r"(?P<coeff>[+-]?1?[ij]?)(?P<pauli>[IXYZ]*)")
 PHASE_MAP = {"": 0, "-i": 1, "-": 2, "i": 3}
 
@@ -618,6 +617,13 @@ class TestPauli(QiskitTestCase):
         with self.assertRaises(QiskitError):
             op.apply_layout(layout=[0, 0], num_qubits=3)
 
+    @combine(phase=["", "-i", "-", "i"], layout=[None, []])
+    def test_apply_layout_zero_qubit(self, phase, layout):
+        """Test apply_layout with a zero-qubit operator"""
+        op = Pauli(phase)
+        res = op.apply_layout(layout=layout, num_qubits=5)
+        self.assertEqual(Pauli(phase + "IIIII"), res)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py
index 1149ef1f3..e7a9b89b7 100644
--- a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py
+++ b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py
@@ -14,23 +14,22 @@
 
 import itertools as it
 import unittest
+from test import QiskitTestCase, combine
+
 import numpy as np
-import scipy.sparse
 import rustworkx as rx
+import scipy.sparse
 from ddt import ddt
 
-
 from qiskit import QiskitError
-from qiskit.circuit import ParameterExpression, Parameter, ParameterVector
-from qiskit.circuit.parametertable import ParameterView
-from qiskit.quantum_info.operators import Operator, Pauli, PauliList, SparsePauliOp
+from qiskit.circuit import Parameter, ParameterExpression, ParameterVector
 from qiskit.circuit.library import EfficientSU2
+from qiskit.circuit.parametertable import ParameterView
+from qiskit.compiler.transpiler import transpile
 from qiskit.primitives import BackendEstimator
 from qiskit.providers.fake_provider import GenericBackendV2
-from qiskit.compiler.transpiler import transpile
+from qiskit.quantum_info.operators import Operator, Pauli, PauliList, SparsePauliOp
 from qiskit.utils import optionals
-from test import QiskitTestCase  # pylint: disable=wrong-import-order
-from test import combine  # pylint: disable=wrong-import-order
 
 
 def pauli_mat(label):
@@ -1191,6 +1190,22 @@ class TestSparsePauliOpMethods(QiskitTestCase):
         with self.assertRaises(QiskitError):
             op.apply_layout(layout=[0, 0], num_qubits=3)
 
+    @combine(layout=[None, []])
+    def test_apply_layout_zero_qubit(self, layout):
+        """Test apply_layout with a zero-qubit operator"""
+        with self.subTest("default"):
+            op = SparsePauliOp("")
+            res = op.apply_layout(layout=layout, num_qubits=5)
+            self.assertEqual(SparsePauliOp("IIIII"), res)
+        with self.subTest("coeff"):
+            op = SparsePauliOp("", 2)
+            res = op.apply_layout(layout=layout, num_qubits=5)
+            self.assertEqual(SparsePauliOp("IIIII", 2), res)
+        with self.subTest("multiple ops"):
+            op = SparsePauliOp.from_list([("", 1), ("", 2)])
+            res = op.apply_layout(layout=layout, num_qubits=5)
+            self.assertEqual(SparsePauliOp.from_list([("IIIII", 1), ("IIIII", 2)]), res)
+
 
 if __name__ == "__main__":
     unittest.main()