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..14bea5aa99bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,16 @@ license = "Apache-2.0" [workspace.dependencies] indexmap.version = "2.2.6" hashbrown.version = "0.14.0" +<<<<<<< HEAD +======= +num-bigint = "0.4" +num-complex = "0.4" +ndarray = "^0.15.6" +numpy = "0.21.0" +smallvec = "1.13" +thiserror = "1.0" + +>>>>>>> 85f98605a (Fix parsing of huge OpenQASM 2 conditionals (#12774)) # 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..02925d0929cd 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -17,11 +17,19 @@ rand_pcg = "0.3" rand_distr = "0.4.3" ahash = "0.8.11" num-traits = "0.2" +<<<<<<< HEAD num-complex = "0.4" num-bigint = "0.4" rustworkx-core = "0.14" faer = "0.18.2" itertools = "0.12.1" +======= +num-complex.workspace = true +num-bigint.workspace = true +rustworkx-core = "0.15" +faer = "0.19.1" +itertools = "0.13.0" +>>>>>>> 85f98605a (Fix parsing of huge OpenQASM 2 conditionals (#12774)) qiskit-circuit.workspace = true [dependencies.smallvec] 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):