Skip to content

Commit

Permalink
Fix parsing of huge OpenQASM 2 conditionals (#12774)
Browse files Browse the repository at this point in the history
* 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 85f9860)

# Conflicts:
#	Cargo.toml
#	crates/accelerate/Cargo.toml
  • Loading branch information
jakelishman authored and mergify[bot] committed Jul 30, 2024
1 parent 4f09f57 commit 2287ec9
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 11 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions crates/accelerate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
1 change: 1 addition & 0 deletions crates/qasm2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ name = "qiskit_qasm2"
doctest = false

[dependencies]
num-bigint.workspace = true
hashbrown.workspace = true
pyo3.workspace = true
qiskit-circuit.workspace = true
7 changes: 4 additions & 3 deletions crates/qasm2/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -160,7 +161,7 @@ pub enum InternalBytecode {
arguments: Vec<f64>,
qubits: Vec<QubitId>,
creg: CregId,
value: usize,
value: BigUint,
},
Measure {
qubit: QubitId,
Expand All @@ -170,15 +171,15 @@ pub enum InternalBytecode {
qubit: QubitId,
clbit: ClbitId,
creg: CregId,
value: usize,
value: BigUint,
},
Reset {
qubit: QubitId,
},
ConditionedReset {
qubit: QubitId,
creg: CregId,
value: usize,
value: BigUint,
},
Barrier {
qubits: Vec<QubitId>,
Expand Down
10 changes: 10 additions & 0 deletions crates/qasm2/src/lex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
//! real-number tokenisation.

use hashbrown::HashMap;
use num_bigint::BigUint;
use pyo3::prelude::PyResult;

use std::path::Path;
Expand Down Expand Up @@ -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 {
Expand Down
18 changes: 10 additions & 8 deletions crates/qasm2/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -1215,7 +1217,7 @@ impl State {
gate_id: GateId,
arguments: Vec<f64>,
qubits: Vec<QubitId>,
condition: &Option<Condition>,
condition: Option<Condition>,
) -> PyResult<usize> {
if let Some(condition) = condition {
bc.push(Some(InternalBytecode::ConditionedGate {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -1408,7 +1410,7 @@ impl State {
qubit: q_start + i,
clbit: c_start + i,
creg,
value,
value: value.clone(),
})
}));
Ok(q_size)
Expand Down Expand Up @@ -1477,7 +1479,7 @@ impl State {
Some(InternalBytecode::ConditionedReset {
qubit: start + offset,
creg,
value,
value: value.clone(),
})
}));
Ok(size)
Expand Down
6 changes: 6 additions & 0 deletions releasenotes/notes/qasm2-big-condition-cfd203d53540d4ca.yaml
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/Qiskit/qiskit/issues/12773>`__.
29 changes: 29 additions & 0 deletions test/python/qasm2/test_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit 2287ec9

Please sign in to comment.