Repository: Qiskit/qiskit-terra (python)

Issue:
qasm3 exporter fails if circuits have delays
### Environment

- **Qiskit Terra version**: 20.0
- **Python version**: 3.8.5
- **Operating system**: linux



### What is happening?

Using the qasm3 exporter, if your circuit has a delay instruction in it, the export fails complaining about opaque gates:

```
~/venv/qiskit/lib/python3.8/site-packages/qiskit/qasm3/exporter.py in build_opaque_definition(self, instruction)
    568         """Builds an Opaque gate definition as a CalibrationDefinition"""
    569         # We can't do anything sensible with this yet, so it's better to loudly say that.
--> 570         raise QASM3ExporterError(
    571             "Exporting opaque instructions with pulse-level calibrations is not yet supported by"
    572             " the OpenQASM 3 exporter. Received this instruction, which appears opaque:"

QASM3ExporterError: 'Exporting opaque instructions with pulse-level calibrations is not yet supported by the OpenQASM 3 exporter. Received this instruction, which appears opaque:\nDelay(duration=50[unit=dt])'
```

### How can we reproduce the issue?

The following code demonstrates the error

```
from qiskit.qasm3 import Exporter
from qiskit import QuantumCircuit

circuit = QuantumCircuit(2)
circuit.delay(50)
circuit.h(1)
circuit.measure_all()
circuit.draw()

Exporter().dumps(circuit)
```




### What should happen?

A proper qasm3 string should be generated.

### Any suggestions?

_No response_

Pull request description:
Add delay handling to QASM 3 exporter
### Summary

This simply adds the additional special-case needed to handle the
built-in delay instructions for OpenQASM 3.  We have to take care when
dealing with `ps`, which is a supported unit by Terra but not by OQ3.

<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->


### Details and comments

Fix #7903


Patch:
diff --git a/qiskit/qasm3/ast.py b/qiskit/qasm3/ast.py
index 5bdc6e5b8..952aeb1b5 100644
--- a/qiskit/qasm3/ast.py
+++ b/qiskit/qasm3/ast.py
@@ -262,6 +262,24 @@ class Integer(Expression):
     """Integer : Digit+ ;"""
 
 
+class DurationUnit(enum.Enum):
+    """Valid values for the unit of durations."""
+
+    NANOSECOND = "ns"
+    MICROSECOND = "us"
+    MILLISECOND = "ms"
+    SECOND = "s"
+    SAMPLE = "dt"
+
+
+class DurationLiteral(Expression):
+    """Duration literal."""
+
+    def __init__(self, value: float, unit: DurationUnit):
+        self.value = value
+        self.unit = unit
+
+
 class Designator(ASTNode):
     """
     designator
@@ -374,6 +392,14 @@ class QuantumReset(QuantumInstruction):
         self.identifier = identifier
 
 
+class QuantumDelay(QuantumInstruction):
+    """A built-in ``delay[duration] q0;`` statement."""
+
+    def __init__(self, duration: Expression, qubits: List[Identifier]):
+        self.duration = duration
+        self.qubits = qubits
+
+
 class ProgramBlock(ASTNode):
     """
     programBlock
diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py
index 13a5dca49..759783d0c 100644
--- a/qiskit/qasm3/exporter.py
+++ b/qiskit/qasm3/exporter.py
@@ -31,6 +31,7 @@ from qiskit.circuit import (
     QuantumRegister,
     Qubit,
     Reset,
+    Delay,
 )
 from qiskit.circuit.bit import Bit
 from qiskit.circuit.controlflow import (
@@ -290,7 +291,7 @@ _Scope = collections.namedtuple("_Scope", ("circuit", "bit_map", "symbol_map"))
 class QASM3Builder:
     """QASM3 builder constructs an AST from a QuantumCircuit."""
 
-    builtins = (Barrier, Measure, Reset, BreakLoopOp, ContinueLoopOp)
+    builtins = (Barrier, Measure, Reset, Delay, BreakLoopOp, ContinueLoopOp)
     gate_parameter_prefix = "_gate_p"
     gate_qubit_prefix = "_gate_q"
 
@@ -796,6 +797,8 @@ class QASM3Builder:
             elif isinstance(instruction[0], Reset):
                 for operand in instruction[1]:
                     ret.append(ast.QuantumReset(self.build_single_bit_reference(operand)))
+            elif isinstance(instruction[0], Delay):
+                ret.append(self.build_delay(*instruction))
             elif isinstance(instruction[0], ForLoopOp):
                 ret.append(self.build_for_loop(*instruction))
             elif isinstance(instruction[0], WhileLoopOp):
@@ -871,6 +874,30 @@ class QASM3Builder:
         self.pop_scope()
         return ast.ForLoopStatement(indexset_ast, loop_parameter_ast, body_ast)
 
+    def build_delay(
+        self, instruction: Delay, qubits: Iterable[Qubit], clbits: Iterable[Clbit]
+    ) -> ast.QuantumDelay:
+        """Build a built-in delay statement."""
+        clbits = tuple(clbits)
+        if clbits:
+            raise QASM3ExporterError(
+                f"Found a delay instruction acting on classical bits: {instruction} on {clbits}"
+            )
+        if instruction.unit == "ps":
+            duration = ast.DurationLiteral(1000 * instruction.duration, ast.DurationUnit.NANOSECOND)
+        else:
+            unit_map = {
+                "ns": ast.DurationUnit.NANOSECOND,
+                "us": ast.DurationUnit.MICROSECOND,
+                "ms": ast.DurationUnit.MILLISECOND,
+                "s": ast.DurationUnit.SECOND,
+                "dt": ast.DurationUnit.SAMPLE,
+            }
+            duration = ast.DurationLiteral(instruction.duration, unit_map[instruction.unit])
+        return ast.QuantumDelay(
+            duration, [self.build_single_bit_reference(qubit) for qubit in qubits]
+        )
+
     def build_integer(self, value) -> ast.Integer:
         """Build an integer literal, raising a :obj:`.QASM3ExporterError` if the input is not
         actually an
diff --git a/qiskit/qasm3/printer.py b/qiskit/qasm3/printer.py
index 9d07af8b9..8dc4b3e5f 100644
--- a/qiskit/qasm3/printer.py
+++ b/qiskit/qasm3/printer.py
@@ -208,9 +208,20 @@ class BasicPrinter:
         self.visit(node.identifier)
         self._end_statement()
 
+    def _visit_QuantumDelay(self, node: ast.QuantumDelay) -> None:
+        self._start_line()
+        self.stream.write("delay[")
+        self.visit(node.duration)
+        self.stream.write("] ")
+        self._visit_sequence(node.qubits, separator=", ")
+        self._end_statement()
+
     def _visit_Integer(self, node: ast.Integer) -> None:
         self.stream.write(str(node.something))
 
+    def _visit_DurationLiteral(self, node: ast.DurationLiteral) -> None:
+        self.stream.write(f"{node.value}{node.unit.value}")
+
     def _visit_Designator(self, node: ast.Designator) -> None:
         self.stream.write("[")
         self.visit(node.expression)
diff --git a/releasenotes/notes/qasm3-exporter-delay-ef3003e01412c97e.yaml b/releasenotes/notes/qasm3-exporter-delay-ef3003e01412c97e.yaml
new file mode 100644
index 000000000..483372890
--- /dev/null
+++ b/releasenotes/notes/qasm3-exporter-delay-ef3003e01412c97e.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    The OpenQASM 3 exporter (:mod:`qiskit.qasm3`) now supports exporting circuits
+    with explicit delays, such as from :meth:`.QuantumCircuit.delay`.


Test patch:
diff --git a/test/python/circuit/test_circuit_qasm3.py b/test/python/circuit/test_circuit_qasm3.py
index 0d013647b..be71898a6 100644
--- a/test/python/circuit/test_circuit_qasm3.py
+++ b/test/python/circuit/test_circuit_qasm3.py
@@ -760,6 +760,32 @@ class TestCircuitQASM3(QiskitTestCase):
         )
         self.assertEqual(Exporter(includes=[]).dumps(qc), expected_qasm)
 
+    def test_delay_statement(self):
+        """Test that delay operations get output into valid QASM 3."""
+        inner = QuantumCircuit(1, name="inner_gate")
+        inner.delay(50, unit="dt")
+        qreg = QuantumRegister(2, "qr")
+        qc = QuantumCircuit(qreg)
+        qc.delay(100, qreg[0], unit="ms")
+        qc.delay(2, qreg[1], unit="ps")  # "ps" is not a valid unit in OQ3, so we need to convert.
+        qc.append(inner, [1], [])
+
+        expected_qasm = "\n".join(
+            [
+                "OPENQASM 3;",
+                "def inner_gate(qubit _gate_q_0) {",
+                "  delay[50dt] _gate_q_0;",
+                "}",
+                "qubit[2] _all_qubits;",
+                "let qr = _all_qubits[0:1];",
+                "delay[100ms] qr[0];",
+                "delay[2000ns] qr[1];",
+                "inner_gate qr[1];",
+                "",
+            ]
+        )
+        self.assertEqual(Exporter(includes=[]).dumps(qc), expected_qasm)
+
     def test_loose_qubits(self):
         """Test that qubits that are not in any register can be used without issue."""
         bits = [Qubit(), Qubit()]