Skip to content

Commit

Permalink
Fix empty-barrier handling in OpenQASM 2 (#10469)
Browse files Browse the repository at this point in the history
The new parser would allow a `barrier;` statement, implicitly
broadcasting it across all qubits in scope.  This is technically not
supported by the OpenQASM 2 specification, but is a useful
quality-of-life extension to the specification (in the same way that
Qiskit interprets barriers, and the OpenQASM 3 specification defines the
`barrier;` statement).  The precise rule is added to the new parser's
`strict` mode.

The OpenQASM 2 _exporter_ similarly should not have been putting out
`barrier;` statements.  These could only occur in Qiskit when a barrier
was explicitly constructed with zero elements (as opposed to the call
`QuantumCircuit.barrier()`, which has the all-in-scope behaviour), and
consequently have no actual meaning or effect.  The exporter is modified
to simply skip such instructions, for as long as Qiskit permits the
qubitless barrier statement.

(cherry picked from commit e75893d)
  • Loading branch information
jakelishman authored and mergify[bot] committed Jul 24, 2023
1 parent 97e1808 commit 31f13c6
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 0 deletions.
9 changes: 9 additions & 0 deletions crates/qasm2/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,15 @@ impl State {
}
self.check_trailing_comma(comma.as_ref())?;
qubits
} else if self.strict {
return Err(QASM2ParseError::new_err(message_generic(
Some(&Position::new(
self.current_filename(),
barrier_token.line,
barrier_token.col,
)),
"[strict] barrier statements must have at least one argument",
)));
} else if let Some(num_gate_qubits) = num_gate_qubits {
(0..num_gate_qubits).map(QubitId::new).collect::<Vec<_>>()
} else {
Expand Down
4 changes: 4 additions & 0 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1716,6 +1716,10 @@ def qasm(
elif operation.name == "reset":
instruction_qasm = f"reset {bit_labels[instruction.qubits[0]]};"
elif operation.name == "barrier":
if not instruction.qubits:
# Barriers with no operands are invalid in (strict) OQ2, and the statement
# would have no meaning anyway.
continue
qargs = ",".join(bit_labels[q] for q in instruction.qubits)
instruction_qasm = "barrier;" if not qargs else f"barrier {qargs};"
else:
Expand Down
13 changes: 13 additions & 0 deletions releasenotes/notes/qasm2-fix-zero-op-barrier-4af211b119d5b24d.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
fixes:
- |
The OpenQASM 2 parser (:func:`.qasm2.load` and :func:`~.qasm2.loads`) running in ``strict`` mode
will now correctly emit an error if a ``barrier`` statement has no arguments. When running in
the (default) more permissive mode, an argument-less ``barrier`` statement will continue to
cause a barrier on all qubits currently in scope (the qubits a gate definition affects, or all
the qubits defined by a program, if the statement is in a gate body or in the global scope,
respectively).
- |
The OpenQASM 2 exporter (:meth:`.QuantumCircuit.qasm`) will now no longer attempt
to output ``barrier`` statements that act on no qubits. Such a barrier statement has no effect
in Qiskit either, but is invalid OpenQASM 2.
17 changes: 17 additions & 0 deletions test/python/circuit/test_circuit_qasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,23 @@ def test_sequencial_inner_gates_with_same_name(self):

self.assertEqual(qc.qasm(), expected_output)

def test_empty_barrier(self):
"""Test that a blank barrier statement in _Qiskit_ acts over all qubits, while an explicitly
no-op barrier (assuming Qiskit continues to allow this) is not output to OQ2 at all, since
the statement requires an argument in the spec."""
qc = QuantumCircuit(QuantumRegister(2, "qr1"), QuantumRegister(3, "qr2"))
qc.barrier() # In Qiskit land, this affects _all_ qubits.
qc.barrier([]) # This explicitly affects _no_ qubits (so is totally meaningless).

expected = """\
OPENQASM 2.0;
include "qelib1.inc";
qreg qr1[2];
qreg qr2[3];
barrier qr1[0],qr1[1],qr2[0],qr2[1],qr2[2];
"""
self.assertEqual(qc.qasm(), expected)


if __name__ == "__main__":
unittest.main()
7 changes: 7 additions & 0 deletions test/python/qasm2/test_parse_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -865,3 +865,10 @@ def test_required_version_empty(self):
qiskit.qasm2.QASM2ParseError, r"\[strict\] .*needed a version statement"
):
qiskit.qasm2.loads("", strict=True)

def test_barrier_requires_args(self):
program = "OPENQASM 2.0; qreg q[2]; barrier;"
with self.assertRaisesRegex(
qiskit.qasm2.QASM2ParseError, r"\[strict\] barrier statements must have at least one"
):
qiskit.qasm2.loads(program, strict=True)

0 comments on commit 31f13c6

Please sign in to comment.