Skip to content
This repository was archived by the owner on Apr 11, 2024. It is now read-only.

Commit 9b16876

Browse files
committed
Fix overriding qelib1.inc with custom gates
The bytecode interpreter assumed that all gates `qelib1.inc` would always need to be appended to the gate list. The inclusion of custom instructions broke this assumption. It was masked if _all_ the gates were overridden, and no further user gates were defined, but failed in other situations.
1 parent 4b6b843 commit 9b16876

File tree

5 files changed

+129
-46
lines changed

5 files changed

+129
-46
lines changed

docs/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ Unreleased
88
* Added some missing gates to :data:`.QISKIT_CUSTOM_INSTRUCTIONS`; Qiskit's legacy importer
99
made rather a lot of changes to the file as presented in the paper!
1010

11+
* Fixed incorrect gate creations when a strict subset of the ``qelib1.inc`` gates were overridden
12+
with custom constructors, or if any user gates were defined after all every gate in that include
13+
file was overridden.
14+
1115
0.3.1 (2023-01-20)
1216
==================
1317

src-python/qiskit_qasm2/parse.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,9 @@ def from_bytecode(bytecode, custom_instructions: Iterable[CustomInstruction]):
236236
# Including `qelib1.inc` is pretty much universal, and we treat its gates as having
237237
# special relationships to the Qiskit ones, so we don't actually parse it; we just
238238
# short-circuit to add its pre-calculated content to our state.
239-
gates += QELIB1
239+
(indices,) = op.operands
240+
for index in indices:
241+
gates.append(QELIB1[index])
240242
elif opcode == OpCode.DeclareGate:
241243
name, n_qubits = op.operands
242244
# This inner loop advances the iterator of the outer loop as well, since `bc` is a

src-rust/bytecode.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ pub enum InternalBytecode {
182182
n_qubits: usize,
183183
},
184184
SpecialInclude {
185-
name: String,
185+
indices: Vec<usize>,
186186
},
187187
}
188188

@@ -264,9 +264,9 @@ impl IntoPy<Bytecode> for InternalBytecode {
264264
opcode: OpCode::DeclareOpaque,
265265
operands: (name, n_qubits).into_py(py),
266266
},
267-
InternalBytecode::SpecialInclude { name } => Bytecode {
267+
InternalBytecode::SpecialInclude { indices } => Bytecode {
268268
opcode: OpCode::SpecialInclude,
269-
operands: (name,).into_py(py),
269+
operands: (indices,).into_py(py),
270270
},
271271
}
272272
}

src-rust/parse.rs

Lines changed: 39 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,33 @@ use crate::CustomInstruction;
1313

1414
/// The number of gates that are built in to the OpenQASM 2 language. This is U and CX.
1515
const N_BUILTIN_GATES: usize = 2;
16-
/// The number of gates that 'qelib1.inc' defines. For efficiency, this parser doesn't actually
17-
/// parse the include file itself, since its contents are fixed by the OpenQASM 2 arXiv paper,
18-
/// which this parser follows to the letter. Instead, we just have it hardcoded as a special
19-
/// import that happens quickly when the relevant 'include' statement is seen.
20-
const N_QELIB1_GATES: usize = 23;
16+
/// The "qelib1.inc" special include. The elements of the tuple are the gate name, the number of
17+
/// parameters it takes, and the number of qubits it acts on.
18+
const QELIB1: [(&str, usize, usize); 23] = [
19+
("u3", 3, 1),
20+
("u2", 2, 1),
21+
("u1", 1, 1),
22+
("cx", 0, 2),
23+
("id", 0, 1),
24+
("x", 0, 1),
25+
("y", 0, 1),
26+
("z", 0, 1),
27+
("h", 0, 1),
28+
("s", 0, 1),
29+
("sdg", 0, 1),
30+
("t", 0, 1),
31+
("tdg", 0, 1),
32+
("rx", 1, 1),
33+
("ry", 1, 1),
34+
("rz", 1, 1),
35+
("cz", 0, 2),
36+
("cy", 0, 2),
37+
("ch", 0, 2),
38+
("ccx", 0, 3),
39+
("crz", 1, 2),
40+
("cu1", 1, 2),
41+
("cu3", 3, 2),
42+
];
2143

2244
/// A symbol in the global symbol table. Parameters and individual qubits can't be in the global
2345
/// symbol table, as there is no way for them to be defined.
@@ -132,7 +154,7 @@ impl State {
132154
// custom instructions, but this is small-potatoes allocation and we'd rather not have
133155
// to reallocate.
134156
symbols: HashMap::with_capacity(
135-
N_BUILTIN_GATES + N_QELIB1_GATES + custom_instructions.len(),
157+
N_BUILTIN_GATES + QELIB1.len() + custom_instructions.len(),
136158
),
137159
gate_symbols: HashMap::new(),
138160
n_qubits: 0,
@@ -1279,8 +1301,14 @@ impl State {
12791301
self.expect(TokenType::Semicolon, "';'", &include_token)?;
12801302
let filename = filename_token.filename(&self.context);
12811303
if filename == "qelib1.inc" {
1282-
self.include_qelib1(&include_token)?;
1283-
bc.push(Some(InternalBytecode::SpecialInclude { name: filename }));
1304+
self.symbols.reserve(QELIB1.len());
1305+
let mut indices = Vec::with_capacity(QELIB1.len());
1306+
for (i, (name, n_params, n_qubits)) in QELIB1.iter().enumerate() {
1307+
if self.define_gate(&include_token, name.to_string(), *n_params, *n_qubits)? {
1308+
indices.push(i);
1309+
}
1310+
}
1311+
bc.push(Some(InternalBytecode::SpecialInclude { indices }));
12841312
Ok(1)
12851313
} else {
12861314
let base_filename = std::path::PathBuf::from(&filename);
@@ -1308,36 +1336,6 @@ impl State {
13081336
}
13091337
}
13101338

1311-
/// Update the parser state with the built-in version of the `qelib1.inc` include file. This
1312-
/// is precisely as the file is described in the arXiv paper.
1313-
fn include_qelib1(&mut self, include: &Token) -> Result<(), String> {
1314-
self.symbols.reserve(N_QELIB1_GATES);
1315-
self.define_gate(include, "u3".to_owned(), 3, 1)?;
1316-
self.define_gate(include, "u2".to_owned(), 2, 1)?;
1317-
self.define_gate(include, "u1".to_owned(), 1, 1)?;
1318-
self.define_gate(include, "cx".to_owned(), 0, 2)?;
1319-
self.define_gate(include, "id".to_owned(), 0, 1)?;
1320-
self.define_gate(include, "x".to_owned(), 0, 1)?;
1321-
self.define_gate(include, "y".to_owned(), 0, 1)?;
1322-
self.define_gate(include, "z".to_owned(), 0, 1)?;
1323-
self.define_gate(include, "h".to_owned(), 0, 1)?;
1324-
self.define_gate(include, "s".to_owned(), 0, 1)?;
1325-
self.define_gate(include, "sdg".to_owned(), 0, 1)?;
1326-
self.define_gate(include, "t".to_owned(), 0, 1)?;
1327-
self.define_gate(include, "tdg".to_owned(), 0, 1)?;
1328-
self.define_gate(include, "rx".to_owned(), 1, 1)?;
1329-
self.define_gate(include, "ry".to_owned(), 1, 1)?;
1330-
self.define_gate(include, "rz".to_owned(), 1, 1)?;
1331-
self.define_gate(include, "cz".to_owned(), 0, 2)?;
1332-
self.define_gate(include, "cy".to_owned(), 0, 2)?;
1333-
self.define_gate(include, "ch".to_owned(), 0, 2)?;
1334-
self.define_gate(include, "ccx".to_owned(), 0, 3)?;
1335-
self.define_gate(include, "crz".to_owned(), 1, 2)?;
1336-
self.define_gate(include, "cu1".to_owned(), 1, 2)?;
1337-
self.define_gate(include, "cu3".to_owned(), 3, 2)?;
1338-
Ok(())
1339-
}
1340-
13411339
/// Update the parser state with the definition of a particular gate. This does not emit any
13421340
/// bytecode because not all gate definitions need something passing to Python. For example,
13431341
/// the Python parser initialises its state including the built-in gates `U` and `CX`, and
@@ -1348,7 +1346,7 @@ impl State {
13481346
name: String,
13491347
n_params: usize,
13501348
n_qubits: usize,
1351-
) -> Result<(), String> {
1349+
) -> Result<bool, String> {
13521350
match self.symbols.get_mut(&name) {
13531351
None => {
13541352
self.symbols.insert(
@@ -1362,7 +1360,7 @@ impl State {
13621360
},
13631361
);
13641362
self.n_gates += 1;
1365-
Ok(())
1363+
Ok(true)
13661364
}
13671365
Some(GlobalSymbol::Gate {
13681366
n_params: defined_n_params,
@@ -1402,7 +1400,7 @@ impl State {
14021400
))
14031401
} else {
14041402
*defined = true;
1405-
Ok(())
1403+
Ok(false)
14061404
}
14071405
}
14081406
_ => Err(message_from_token(

tests/test_structure.py

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,85 @@ def test_qelib1_include_overridden(self):
11951195
from_qiskit = QuantumCircuit.from_qasm_str(program)
11961196
assert parsed == from_qiskit
11971197

1198+
def test_qelib1_sparse_overrides(self):
1199+
"""Test that the qelib1 special import still works as expected when a couple of gates in the
1200+
middle of it are custom. As long as qelib1 is handled specially, there is a risk that this
1201+
handling will break in weird ways when custom instructions overlap it."""
1202+
program = """
1203+
include "qelib1.inc";
1204+
qreg q[3];
1205+
u3(0.5, 0.25, 0.125) q[0];
1206+
u2(0.5, 0.25) q[0];
1207+
u1(0.5) q[0];
1208+
cx q[0], q[1];
1209+
id q[0];
1210+
x q[0];
1211+
y q[0];
1212+
z q[0];
1213+
h q[0];
1214+
s q[0];
1215+
sdg q[0];
1216+
t q[0];
1217+
tdg q[0];
1218+
rx(0.5) q[0];
1219+
ry(0.5) q[0];
1220+
rz(0.5) q[0];
1221+
cz q[0], q[1];
1222+
cy q[0], q[1];
1223+
ch q[0], q[1];
1224+
ccx q[0], q[1], q[2];
1225+
crz(0.5) q[0], q[1];
1226+
cu1(0.5) q[0], q[1];
1227+
cu3(0.5, 0.25, 0.125) q[0], q[1];
1228+
"""
1229+
parsed = qiskit_qasm2.loads(
1230+
program,
1231+
custom_instructions=[
1232+
qiskit_qasm2.CustomInstruction("id", 0, 1, lib.IGate),
1233+
qiskit_qasm2.CustomInstruction("h", 0, 1, lib.HGate),
1234+
qiskit_qasm2.CustomInstruction("crz", 1, 2, lib.CRZGate),
1235+
],
1236+
)
1237+
qc = QuantumCircuit(QuantumRegister(3, "q"))
1238+
qc.append(lib.U3Gate(0.5, 0.25, 0.125), [0])
1239+
qc.append(lib.U2Gate(0.5, 0.25), [0])
1240+
qc.append(lib.U1Gate(0.5), [0])
1241+
qc.append(lib.CXGate(), [0, 1])
1242+
qc.append(lib.IGate(), [0])
1243+
qc.append(lib.XGate(), [0])
1244+
qc.append(lib.YGate(), [0])
1245+
qc.append(lib.ZGate(), [0])
1246+
qc.append(lib.HGate(), [0])
1247+
qc.append(lib.SGate(), [0])
1248+
qc.append(lib.SdgGate(), [0])
1249+
qc.append(lib.TGate(), [0])
1250+
qc.append(lib.TdgGate(), [0])
1251+
qc.append(lib.RXGate(0.5), [0])
1252+
qc.append(lib.RYGate(0.5), [0])
1253+
qc.append(lib.RZGate(0.5), [0])
1254+
qc.append(lib.CZGate(), [0, 1])
1255+
qc.append(lib.CYGate(), [0, 1])
1256+
qc.append(lib.CHGate(), [0, 1])
1257+
qc.append(lib.CCXGate(), [0, 1, 2])
1258+
qc.append(lib.CRZGate(0.5), [0, 1])
1259+
qc.append(lib.CU1Gate(0.5), [0, 1])
1260+
qc.append(lib.CU3Gate(0.5, 0.25, 0.125), [0, 1])
1261+
assert parsed == qc
1262+
1263+
def test_user_gate_after_overidden_qelib1(self):
1264+
program = """
1265+
include "qelib1.inc";
1266+
qreg q[1];
1267+
opaque my_gate q;
1268+
my_gate q[0];
1269+
"""
1270+
parsed = qiskit_qasm2.loads(
1271+
program, custom_instructions=qiskit_qasm2.QISKIT_CUSTOM_INSTRUCTIONS
1272+
)
1273+
qc = QuantumCircuit(QuantumRegister(1, "q"))
1274+
qc.append(Gate("my_gate", 1, []), [0])
1275+
assert parsed == qc
1276+
11981277
def test_qiskit_extra_builtins(self):
11991278
program = """
12001279
qreg q[5];
@@ -1245,7 +1324,7 @@ def test_qiskit_extra_builtins(self):
12451324
# is and it has no Qiskit equivalent, so we'll just test that it using it doesn't produce an
12461325
# error.
12471326
parsed = qiskit_qasm2.loads(
1248-
'qreg q[1]; u0(1) q[0];', custom_instructions=qiskit_qasm2.QISKIT_CUSTOM_INSTRUCTIONS
1327+
"qreg q[1]; u0(1) q[0];", custom_instructions=qiskit_qasm2.QISKIT_CUSTOM_INSTRUCTIONS
12491328
)
12501329
assert parsed.data[0].operation.name == "u0"
12511330

0 commit comments

Comments
 (0)