Fix OpenQASM 3 built-ins with classical conditions (#8731)

The `.condition` attribute for `Instruction` instances corresponding to
OpenQASM 3 built-ins (such as `reset` and `measure`) would previously be
ignored.  This now correctly nests them inside a a branching statement
in the exporter, so the condition is preserved.

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Jake Lishman 2022-09-13 18:18:45 +01:00 committed by GitHub
parent 780099972c
commit bcec9a314c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 90 additions and 25 deletions

View File

@ -779,45 +779,49 @@ class QASM3Builder:
"""Builds a list of call statements"""
ret = []
for instruction in instructions:
if isinstance(instruction.operation, ForLoopOp):
ret.append(self.build_for_loop(instruction))
continue
if isinstance(instruction.operation, WhileLoopOp):
ret.append(self.build_while_loop(instruction))
continue
if isinstance(instruction.operation, IfElseOp):
ret.append(self.build_if_statement(instruction))
continue
# Build the node, ignoring any condition.
if isinstance(instruction.operation, Gate):
if instruction.operation.condition:
eqcondition = self.build_eqcondition(instruction.operation.condition)
operation_without_condition = instruction.operation.copy()
operation_without_condition.condition = None
true_body = self.build_program_block(
[instruction.replace(operation=operation_without_condition)]
)
ret.append(ast.BranchingStatement(eqcondition, true_body))
else:
ret.append(self.build_gate_call(instruction))
nodes = [self.build_gate_call(instruction)]
elif isinstance(instruction.operation, Barrier):
operands = [
self.build_single_bit_reference(operand) for operand in instruction.qubits
]
ret.append(ast.QuantumBarrier(operands))
nodes = [ast.QuantumBarrier(operands)]
elif isinstance(instruction.operation, Measure):
measurement = ast.QuantumMeasurement(
[self.build_single_bit_reference(operand) for operand in instruction.qubits]
)
qubit = self.build_single_bit_reference(instruction.clbits[0])
ret.append(ast.QuantumMeasurementAssignment(qubit, measurement))
nodes = [ast.QuantumMeasurementAssignment(qubit, measurement)]
elif isinstance(instruction.operation, Reset):
for operand in instruction.qubits:
ret.append(ast.QuantumReset(self.build_single_bit_reference(operand)))
nodes = [
ast.QuantumReset(self.build_single_bit_reference(operand))
for operand in instruction.qubits
]
elif isinstance(instruction.operation, Delay):
ret.append(self.build_delay(instruction))
elif isinstance(instruction.operation, ForLoopOp):
ret.append(self.build_for_loop(instruction))
elif isinstance(instruction.operation, WhileLoopOp):
ret.append(self.build_while_loop(instruction))
elif isinstance(instruction.operation, IfElseOp):
ret.append(self.build_if_statement(instruction))
nodes = [self.build_delay(instruction)]
elif isinstance(instruction.operation, BreakLoopOp):
ret.append(ast.BreakStatement())
nodes = [ast.BreakStatement()]
elif isinstance(instruction.operation, ContinueLoopOp):
ret.append(ast.ContinueStatement())
nodes = [ast.ContinueStatement()]
else:
ret.append(self.build_subroutine_call(instruction))
nodes = [self.build_subroutine_call(instruction)]
if instruction.operation.condition is None:
ret.extend(nodes)
else:
eqcondition = self.build_eqcondition(instruction.operation.condition)
body = ast.ProgramBlock(nodes)
ret.append(ast.BranchingStatement(eqcondition, body))
return ret
def build_if_statement(self, instruction: CircuitInstruction) -> ast.BranchingStatement:

View File

@ -0,0 +1,7 @@
---
fixes:
- |
The OpenQASM 3 exporter (:mod:`qiskit.qasm3`) will now correctly handle
OpenQASM built-ins (such as ``reset`` and ``measure``) that have a classical
condition applied by :meth:`~.InstructionSet.c_if`. Previously the condition
would have been ignored.

View File

@ -22,7 +22,7 @@ import unittest
from ddt import ddt, data
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, transpile
from qiskit.circuit import Parameter, Qubit, Clbit, Instruction, Gate
from qiskit.circuit import Parameter, Qubit, Clbit, Instruction, Gate, Delay, Barrier
from qiskit.test import QiskitTestCase
from qiskit.qasm3 import Exporter, dumps, dump, QASM3ExporterError
from qiskit.qasm3.exporter import QASM3Builder
@ -1696,6 +1696,60 @@ class TestCircuitQASM3ExporterTemporaryCasesWithBadParameterisation(QiskitTestCa
)
self.assertEqual(Exporter(includes=[]).dumps(circuit), expected_qasm)
def test_unusual_conditions(self):
"""Test that special QASM constructs such as ``measure`` are correctly handled when the
Terra instructions have old-style conditions."""
qc = QuantumCircuit(3, 2)
qc.h(0)
qc.measure(0, 0)
qc.measure(1, 1).c_if(0, True)
qc.reset([0, 1]).c_if(0, True)
with qc.while_loop((qc.clbits[0], True)):
qc.break_loop().c_if(0, True)
qc.continue_loop().c_if(0, True)
# Terra forbids delay and barrier from being conditioned through `c_if`, but in theory they
# should work fine in a dynamic-circuits sense (although what a conditional barrier _means_
# is a whole other kettle of fish).
delay = Delay(16, "dt")
delay.condition = (qc.clbits[0], True)
qc.append(delay, [0], [])
barrier = Barrier(2)
barrier.condition = (qc.clbits[0], True)
qc.append(barrier, [0, 1], [])
expected = """
OPENQASM 3;
include "stdgates.inc";
bit[2] c;
qubit[3] _all_qubits;
let q = _all_qubits[0:2];
h q[0];
c[0] = measure q[0];
if (c[0] == 1) {
c[1] = measure q[1];
}
if (c[0] == 1) {
reset q[0];
}
if (c[0] == 1) {
reset q[1];
}
while (c[0] == 1) {
if (c[0] == 1) {
break;
}
if (c[0] == 1) {
continue;
}
}
if (c[0] == 1) {
delay[16dt] q[0];
}
if (c[0] == 1) {
barrier q[0], q[1];
}"""
self.assertEqual(dumps(qc).strip(), expected.strip())
@ddt
class TestQASM3ExporterFailurePaths(QiskitTestCase):