From 89aceef61b9ae7ea0320146712008b5cca772833 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 30 Jul 2024 14:23:26 +0100 Subject: [PATCH] Fix parsing of huge OpenQASM 2 conditionals (#12774) * Fix parsing of huge OpenQASM 2 conditionals We fixed handling of giant integers in gate expression positions gh-12140, and this commit fixes the handling in conditionals. Unfortunately, this means pulling in big-int handling properly; the integers simply _are_ bigints, and we're not immediately converting them into something else. The need to support this may influence how the Rust-space data models of `QuantumCircuit` evolve. * Move `num-bigint` dependency to workspace (cherry picked from commit 85f98605a6488d9e25e5cd04eedc3687ebece614) --- Cargo.lock | 1 + Cargo.toml | 2 ++ crates/accelerate/Cargo.toml | 2 +- crates/qasm2/Cargo.toml | 1 + crates/qasm2/src/bytecode.rs | 7 +++-- crates/qasm2/src/lex.rs | 10 +++++++ crates/qasm2/src/parse.rs | 18 +++++++----- .../qasm2-big-condition-cfd203d53540d4ca.yaml | 6 ++++ test/python/qasm2/test_structure.py | 29 +++++++++++++++++++ 9 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/qasm2-big-condition-cfd203d53540d4ca.yaml diff --git a/Cargo.lock b/Cargo.lock index 75f0dbed752c..e329661e0bdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1144,6 +1144,7 @@ name = "qiskit-qasm2" version = "1.1.1" dependencies = [ "hashbrown 0.14.5", + "num-bigint", "pyo3", "qiskit-circuit", ] diff --git a/Cargo.toml b/Cargo.toml index 5c8ac40769ce..156bb41ce0c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ license = "Apache-2.0" [workspace.dependencies] indexmap.version = "2.2.6" hashbrown.version = "0.14.0" +num-bigint = "0.4" + # Most of the crates don't need the feature `extension-module`, since only `qiskit-pyext` builds an # actual C extension (the feature disables linking in `libpython`, which is forbidden in Python # distributions). We only activate that feature when building the C extension module; we still need diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index a43fdc6ff506..76d578275a70 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 = "0.4" -num-bigint = "0.4" +num-bigint.workspace = true rustworkx-core = "0.14" faer = "0.18.2" itertools = "0.12.1" 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 f9f674cbc93e..9e631da395e8 100644 --- a/crates/qasm2/src/lex.rs +++ b/crates/qasm2/src/lex.rs @@ -24,6 +24,7 @@ //! real-number tokenisation. 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 e4c749841124..4299d740af03 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):