diff --git a/Cargo.lock b/Cargo.lock index e3416426b4d1..f91bab4d5603 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1213,6 +1213,7 @@ name = "qiskit-qasm2" version = "1.3.0" dependencies = [ "hashbrown 0.14.5", + "num-bigint", "pyo3", "qiskit-circuit", ] diff --git a/Cargo.toml b/Cargo.toml index a10792b9ad0f..1e8a7185f449 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ license = "Apache-2.0" bytemuck = "1.16" indexmap.version = "2.2.6" hashbrown.version = "0.14.0" +num-bigint = "0.4" num-complex = "0.4" ndarray = "^0.15.6" numpy = "0.21.0" diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index 9d6024783996..0b23cf08743e 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -18,7 +18,7 @@ rand_distr = "0.4.3" ahash = "0.8.11" num-traits = "0.2" num-complex.workspace = true -num-bigint = "0.4" +num-bigint.workspace = true rustworkx-core = "0.15" faer = "0.19.1" itertools = "0.13.0" diff --git a/crates/qasm2/Cargo.toml b/crates/qasm2/Cargo.toml index 68137ee602bf..681693c4a17d 100644 --- a/crates/qasm2/Cargo.toml +++ b/crates/qasm2/Cargo.toml @@ -10,6 +10,7 @@ name = "qiskit_qasm2" doctest = false [dependencies] +num-bigint.workspace = true hashbrown.workspace = true pyo3.workspace = true qiskit-circuit.workspace = true diff --git a/crates/qasm2/src/bytecode.rs b/crates/qasm2/src/bytecode.rs index 2dea3a2b0e85..fab973f2186f 100644 --- a/crates/qasm2/src/bytecode.rs +++ b/crates/qasm2/src/bytecode.rs @@ -10,6 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +use num_bigint::BigUint; use pyo3::prelude::*; use crate::error::QASM2ParseError; @@ -160,7 +161,7 @@ pub enum InternalBytecode { arguments: Vec, qubits: Vec, creg: CregId, - value: usize, + value: BigUint, }, Measure { qubit: QubitId, @@ -170,7 +171,7 @@ pub enum InternalBytecode { qubit: QubitId, clbit: ClbitId, creg: CregId, - value: usize, + value: BigUint, }, Reset { qubit: QubitId, @@ -178,7 +179,7 @@ pub enum InternalBytecode { ConditionedReset { qubit: QubitId, creg: CregId, - value: usize, + value: BigUint, }, Barrier { qubits: Vec, diff --git a/crates/qasm2/src/lex.rs b/crates/qasm2/src/lex.rs index 551fd2b7af48..cfac9e98fce0 100644 --- a/crates/qasm2/src/lex.rs +++ b/crates/qasm2/src/lex.rs @@ -24,6 +24,7 @@ //! real-number tokenization. use hashbrown::HashMap; +use num_bigint::BigUint; use pyo3::prelude::PyResult; use std::path::Path; @@ -279,6 +280,15 @@ impl Token { context.text[self.index].parse().unwrap() } + /// If the token is an integer (by type, not just by value), this method can be called to + /// evaluate its value as a big integer. Panics if the token is not an integer type. + pub fn bigint(&self, context: &TokenContext) -> BigUint { + if self.ttype != TokenType::Integer { + panic!() + } + context.text[self.index].parse().unwrap() + } + /// If the token is a filename path, this method can be called to get a (regular) string /// representing it. Panics if the token type was not a filename. pub fn filename(&self, context: &TokenContext) -> String { diff --git a/crates/qasm2/src/parse.rs b/crates/qasm2/src/parse.rs index f7eceb6aeef4..36e2a49f669c 100644 --- a/crates/qasm2/src/parse.rs +++ b/crates/qasm2/src/parse.rs @@ -16,6 +16,7 @@ //! operator-precedence parser. use hashbrown::{HashMap, HashSet}; +use num_bigint::BigUint; use pyo3::prelude::{PyObject, PyResult, Python}; use crate::bytecode::InternalBytecode; @@ -188,9 +189,10 @@ enum GateParameters { /// An equality condition from an `if` statement. These can condition gate applications, measures /// and resets, although in practice they're basically only ever used on gates. +#[derive(Clone)] struct Condition { creg: CregId, - value: usize, + value: BigUint, } /// Find the first match for the partial [filename] in the directories along [path]. Returns @@ -1105,7 +1107,7 @@ impl State { } { return match parameters { GateParameters::Constant(parameters) => { - self.emit_single_global_gate(bc, gate_id, parameters, qubits, &condition) + self.emit_single_global_gate(bc, gate_id, parameters, qubits, condition) } GateParameters::Expression(parameters) => { self.emit_single_gate_gate(bc, gate_id, parameters, qubits) @@ -1174,7 +1176,7 @@ impl State { } return match parameters { GateParameters::Constant(parameters) => { - self.emit_single_global_gate(bc, gate_id, parameters, qubits, &condition) + self.emit_single_global_gate(bc, gate_id, parameters, qubits, condition) } GateParameters::Expression(parameters) => { self.emit_single_gate_gate(bc, gate_id, parameters, qubits) @@ -1196,7 +1198,7 @@ impl State { gate_id, parameters.clone(), qubits, - &condition, + condition.clone(), )?; } // Gates used in gate-body definitions can't ever broadcast, because their only @@ -1215,7 +1217,7 @@ impl State { gate_id: GateId, arguments: Vec, qubits: Vec, - condition: &Option, + condition: Option, ) -> PyResult { if let Some(condition) = condition { bc.push(Some(InternalBytecode::ConditionedGate { @@ -1262,7 +1264,7 @@ impl State { self.expect(TokenType::Equals, "'=='", &if_token)?; let value = self .expect(TokenType::Integer, "an integer", &if_token)? - .int(&self.context); + .bigint(&self.context); self.expect(TokenType::RParen, "')'", &lparen_token)?; let name = name_token.id(&self.context); let creg = match self.symbols.get(&name) { @@ -1408,7 +1410,7 @@ impl State { qubit: q_start + i, clbit: c_start + i, creg, - value, + value: value.clone(), }) })); Ok(q_size) @@ -1477,7 +1479,7 @@ impl State { Some(InternalBytecode::ConditionedReset { qubit: start + offset, creg, - value, + value: value.clone(), }) })); Ok(size) diff --git a/releasenotes/notes/qasm2-big-condition-cfd203d53540d4ca.yaml b/releasenotes/notes/qasm2-big-condition-cfd203d53540d4ca.yaml new file mode 100644 index 000000000000..a5863cae2310 --- /dev/null +++ b/releasenotes/notes/qasm2-big-condition-cfd203d53540d4ca.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + The OpenQASM 2 parser (:mod:`qiskit.qasm2`) can now handle conditionals + with integers that do not fit within a 64-bit integer. Fixed + `#12773 `__. diff --git a/test/python/qasm2/test_structure.py b/test/python/qasm2/test_structure.py index 22eff30b38f4..d963eea7e255 100644 --- a/test/python/qasm2/test_structure.py +++ b/test/python/qasm2/test_structure.py @@ -324,6 +324,35 @@ def test_parameterless_gates_accept_parentheses(self): qc.cx(1, 0) self.assertEqual(parsed, qc) + def test_huge_conditions(self): + # Something way bigger than any native integer. + bigint = (1 << 300) + 123456789 + program = f""" + qreg qr[2]; + creg cr[2]; + creg cond[500]; + if (cond=={bigint}) U(0, 0, 0) qr[0]; + if (cond=={bigint}) U(0, 0, 0) qr; + if (cond=={bigint}) reset qr[0]; + if (cond=={bigint}) reset qr; + if (cond=={bigint}) measure qr[0] -> cr[0]; + if (cond=={bigint}) measure qr -> cr; + """ + parsed = qiskit.qasm2.loads(program) + qr, cr = QuantumRegister(2, "qr"), ClassicalRegister(2, "cr") + cond = ClassicalRegister(500, "cond") + qc = QuantumCircuit(qr, cr, cond) + qc.u(0, 0, 0, qr[0]).c_if(cond, bigint) + qc.u(0, 0, 0, qr[0]).c_if(cond, bigint) + qc.u(0, 0, 0, qr[1]).c_if(cond, bigint) + qc.reset(qr[0]).c_if(cond, bigint) + qc.reset(qr[0]).c_if(cond, bigint) + qc.reset(qr[1]).c_if(cond, bigint) + qc.measure(qr[0], cr[0]).c_if(cond, bigint) + qc.measure(qr[0], cr[0]).c_if(cond, bigint) + qc.measure(qr[1], cr[1]).c_if(cond, bigint) + self.assertEqual(parsed, qc) + class TestGateDefinition(QiskitTestCase): def test_simple_definition(self):