Repository: Qiskit/qiskit

Issue:
circuit_with_custom_gates.qasm() does not print the definition of the custom gate
_Originally posted by @1ucian0 in https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-592720851_

> > 3. Can we write the loaded QASM back as the same QASM (with the custom definitions)?
> 
> You mean `circuit.qasm()`? Let's leave the exporter out of this PR for now...
> 
> As a reference, this is the exporter behavior:
> ```
> qasm_string = """OPENQASM 2.0;
>                   include "qelib1.inc";
>                   gate my_other_gate(phi,lambda) q
>                     {u3(asin(cos(phi)/2), phi+pi, lambda/2) q;}
>                   gate my_gate(phi) r
>                     {my_other_gate(phi, phi+pi) r;}
>                   qreg qr[1];
>                   my_gate(pi) qr[0];"""
> circuit = QuantumCircuit.from_qasm_str(qasm_string)
> print(circuit.qasm())
> ```
> ```
> OPENQASM 2.0;
> include "qelib1.inc";
> qreg qr[1];
> my_gate(3.141592653589793) qr[0]; 
> ```


Discussion:

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -17,7 +17,6 @@
 import copy
 import itertools
 import functools
-import warnings
 import numbers
 import multiprocessing as mp
 from collections import OrderedDict, defaultdict
@@ -1306,46 +1305,6 @@ def _check_compatible_regs(self, rhs):
                             f" registers {element1} and {element2} not compatible"
                         )
 
-    @staticmethod
-    def _get_composite_circuit_qasm_from_instruction(instruction):
-        """Returns OpenQASM string composite circuit given an instruction.
-        The given instruction should be the result of composite_circuit.to_instruction()."""
-
-        gate_parameters = ",".join(["param%i" % num for num in range(len(instruction.params))])
-        qubit_parameters = ",".join(["q%i" % num for num in range(instruction.num_qubits)])
-        composite_circuit_gates = ""
-
-        definition = instruction.definition
-        definition_bit_labels = {
-            bit: idx
-            for bits in (definition.qubits, definition.clbits)
-            for idx, bit in enumerate(bits)
-        }
-        for data, qargs, _ in definition:
-            gate_qargs = ",".join(
-                ["q%i" % index for index in [definition_bit_labels[qubit] for qubit in qargs]]
-            )
-            composite_circuit_gates += f"{data.qasm()} {gate_qargs}; "
-
-        if composite_circuit_gates:
-            composite_circuit_gates = composite_circuit_gates.rstrip(" ")
-
-        if gate_parameters:
-            qasm_string = "gate %s(%s) %s { %s }" % (
-                instruction.name,
-                gate_parameters,
-                qubit_parameters,
-                composite_circuit_gates,
-            )
-        else:
-            qasm_string = "gate %s %s { %s }" % (
-                instruction.name,
-                qubit_parameters,
-                composite_circuit_gates,
-            )
-
-        return qasm_string
-
     def qasm(self, formatted=False, filename=None, encoding=None):
         """Return OpenQASM string.
 
@@ -1367,55 +1326,56 @@ def qasm(self, formatted=False, filename=None, encoding=None):
                 ``True``.
             QasmError: If circuit has free parameters.
         """
-        from qiskit.circuit.controlledgate import ControlledGate
 
         if self.num_parameters > 0:
             raise QasmError("Cannot represent circuits with unbound parameters in OpenQASM 2.")
 
         existing_gate_names = [
-            "ch",
-            "cp",
+            "barrier",
+            "measure",
+            "reset",
+            "u3",
+            "u2",
+            "u1",
             "cx",
-            "cy",
+            "id",
+            "u0",
+            "u",
+            "p",
+            "x",
+            "y",
+            "z",
+            "h",
+            "s",
+            "sdg",
+            "t",
+            "tdg",
+            "rx",
+            "ry",
+            "rz",
+            "sx",
+            "sxdg",
             "cz",
+            "cy",
+            "swap",
+            "ch",
+            "ccx",
+            "cswap",
             "crx",
             "cry",
             "crz",
-            "ccx",
-            "cswap",
-            "csx",
-            "cu",
             "cu1",
+            "cp",
             "cu3",
-            "dcx",
-            "h",
-            "i",
-            "id",
-            "iden",
-            "iswap",
-            "ms",
-            "p",
-            "r",
-            "rx",
+            "csx",
+            "cu",
             "rxx",
-            "ry",
-            "ryy",
-            "rz",
-            "rzx",
             "rzz",
-            "s",
-            "sdg",
-            "swap",
-            "sx",
-            "x",
-            "y",
-            "z",
-            "t",
-            "tdg",
-            "u",
-            "u1",
-            "u2",
-            "u3",
+            "rccx",
+            "rc3x",
+            "c3x",
+            "c3sx",
+            "c4x",
         ]
 
         existing_composite_circuits = []
@@ -1440,8 +1400,6 @@ def qasm(self, formatted=False, filename=None, encoding=None):
             regless_clbits = [bit for bit in self.clbits if bit not in creg_bits]
             string_temp += "creg %s[%d];\n" % ("regless", len(regless_clbits))
 
-        unitary_gates = []
-
         bit_labels = {
             bit: "%s[%d]" % (reg.name, idx)
             for reg in self.qregs + self.cregs
@@ -1465,55 +1423,33 @@ def qasm(self, formatted=False, filename=None, encoding=None):
                     bit_labels[qubit],
                     bit_labels[clbit],
                 )
-
-            # If instruction is a root gate or a root instruction (in that case, compositive)
-
-            elif (
-                type(instruction)
-                in [
-                    Gate,
-                    Instruction,
-                ]
-                or (isinstance(instruction, ControlledGate) and instruction._open_ctrl)
-            ):
-                if instruction not in existing_composite_circuits:
-                    if instruction.name in existing_gate_names:
-                        old_name = instruction.name
-                        instruction.name += "_" + str(id(instruction))
-
-                        warnings.warn(
-                            "A gate named {} already exists. "
-                            "We have renamed "
-                            "your gate to {}".format(old_name, instruction.name)
-                        )
-
-                    # Get qasm of composite circuit
-                    qasm_string = self._get_composite_circuit_qasm_from_instruction(instruction)
-
-                    # Insert composite circuit qasm definition right after header and extension lib
-                    string_temp = string_temp.replace(
-                        self.extension_lib, f"{self.extension_lib}\n{qasm_string}"
-                    )
+            else:
+                # decompose gate using definitions if they are not defined in OpenQASM2
+                if (
+                    instruction.name not in existing_gate_names
+                    and instruction not in existing_composite_circuits
+                ):
+                    if instruction.name in [
+                        instruction.name for instruction in existing_composite_circuits
+                    ]:
+                        # append instruction id to name to make it unique
+                        instruction.name += f"_{id(instruction)}"
 
                     existing_composite_circuits.append(instruction)
-                    existing_gate_names.append(instruction.name)
+                    _add_sub_instruction_to_existing_composite_circuits(
+                        instruction, existing_gate_names, existing_composite_circuits
+                    )
 
                 # Insert qasm representation of the original instruction
                 string_temp += "{} {};\n".format(
                     instruction.qasm(),
                     ",".join([bit_labels[j] for j in qargs + cargs]),
                 )
-            else:
-                string_temp += "{} {};\n".format(
-                    instruction.qasm(),
-                    ",".join([bit_labels[j] for j in qargs + cargs]),
-                )
-            if instruction.name == "unitary":
-                unitary_gates.append(instruction)
 
-        # this resets them, so if another call to qasm() is made the gate def is added again
-        for gate in unitary_gates:
-            gate._qasm_def_written = False
+        # insert gate definitions
+        string_temp = _insert_composite_gate_definition_qasm(
+            string_temp, existing_composite_circuits, self.extension_lib
+        )
 
         if filename:
             with open(filename, "w+", encoding=encoding) as file:
@@ -2643,7 +2579,9 @@ def csx(self, control_qubit, target_qubit, label=None, ctrl_state=None):
         from .library.standard_gates.sx import CSXGate
 
         return self.append(
-            CSXGate(label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], []
+            CSXGate(label=label, ctrl_state=ctrl_state),
+            [control_qubit, target_qubit],
+            [],
         )
 
     def t(self, qubit):  # pylint: disable=invalid-name
@@ -3051,3 +2989,80 @@ def _compare_parameters(param1, param2):
 
     # else sort by name
     return _standard_compare(param1.name, param2.name)
+
+
+def _add_sub_instruction_to_existing_composite_circuits(
+    instruction, existing_gate_names, existing_composite_circuits
+):
+    """Recursively add undefined sub-instructions in the definition of the given
+    instruction to existing_composite_circuit list.
+    """
+    for sub_instruction, _, _ in instruction.definition:
+        if (
+            sub_instruction.name not in existing_gate_names
+            and sub_instruction not in existing_composite_circuits
+        ):
+            existing_composite_circuits.insert(0, sub_instruction)
+            _add_sub_instruction_to_existing_composite_circuits(
+                sub_instruction, existing_gate_names, existing_composite_circuits
+            )
+
+
+def _get_composite_circuit_qasm_from_instruction(instruction):
+    """Returns OpenQASM string composite circuit given an instruction.
+    The given instruction should be the result of composite_circuit.to_instruction()."""
+
+    if instruction.definition is None:
+        raise ValueError(f'Instruction "{instruction.name}" is not defined.')
+
+    gate_parameters = ",".join(["param%i" % num for num in range(len(instruction.params))])
+    qubit_parameters = ",".join(["q%i" % num for num in range(instruction.num_qubits)])
+    composite_circuit_gates = ""
+
+    definition = instruction.definition
+    definition_bit_labels = {
+        bit: idx for bits in (definition.qubits, definition.clbits) for idx, bit in enumerate(bits)
+    }
+    for sub_instruction, qargs, _ in definition:
+        gate_qargs = ",".join(
+            ["q%i" % index for index in [definition_bit_labels[qubit] for qubit in qargs]]
+        )
+        composite_circuit_gates += "%s %s; " % (sub_instruction.qasm(), gate_qargs)
+
+    if composite_circuit_gates:
+        composite_circuit_gates = composite_circuit_gates.rstrip(" ")
+
+    if gate_parameters:
+        qasm_string = "gate %s(%s) %s { %s }" % (
+            instruction.name,
+            gate_parameters,
+            qubit_parameters,
+            composite_circuit_gates,
+        )
+    else:
+        qasm_string = "gate %s %s { %s }" % (
+            instruction.name,
+            qubit_parameters,
+            composite_circuit_gates,
+        )
+
+    return qasm_string
+
+
+def _insert_composite_gate_definition_qasm(string_temp, existing_composite_circuits, extension_lib):
+    """Insert composite gate definition QASM code right after extension library in the header"""
+
+    gate_definition_string = ""
+
+    # Generate gate definition string
+    for instruction in existing_composite_circuits:
+        if hasattr(instruction, "_qasm_definition"):
+            qasm_string = instruction._qasm_definition
+        else:
+            qasm_string = _get_composite_circuit_qasm_from_instruction(instruction)
+        gate_definition_string += "\n" + qasm_string
+
+    string_temp = string_temp.replace(
+        extension_lib, "%s%s" % (extension_lib, gate_definition_string)
+    )
+    return string_temp
diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py
--- a/qiskit/extensions/unitary.py
+++ b/qiskit/extensions/unitary.py
@@ -68,7 +68,6 @@ def __init__(self, data, label=None):
 
         self._qasm_name = None
         self._qasm_definition = None
-        self._qasm_def_written = False
         # Store instruction params
         super().__init__("unitary", num_qubits, [data], label=label)
 
@@ -166,18 +165,6 @@ def qasm(self):
         This is achieved by adding a custom gate that corresponds to the definition
         of this gate. It gives the gate a random name if one hasn't been given to it.
         """
-        # if this is true then we have written the gate definition already
-        # so we only need to write the name
-        if self._qasm_def_written:
-            return self._qasmif(self._qasm_name)
-
-        # we have worked out the definition before, but haven't written it yet
-        # so we need to write definition + name
-        if self._qasm_definition:
-            self._qasm_def_written = True
-            return self._qasm_definition + self._qasmif(self._qasm_name)
-
-        # need to work out the definition and then write it
 
         # give this unitary a name
         self._qasm_name = self.label if self.label else "unitary" + str(id(self))
@@ -209,13 +196,12 @@ def qasm(self):
             + ",".join(reg_to_qasm.values())
             + " {\n"
             + gates_def
-            + "}\n"
+            + "}"
         )
 
-        self._qasm_def_written = True
         self._qasm_definition = overall
 
-        return self._qasm_definition + self._qasmif(self._qasm_name)
+        return self._qasmif(self._qasm_name)
 
     def validate_parameter(self, parameter):
         """Unitary gate parameter has to be an ndarray."""


Test patch:
diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py
--- a/test/python/circuit/test_circuit_qasm.py
+++ b/test/python/circuit/test_circuit_qasm.py
@@ -163,9 +163,9 @@ def test_circuit_qasm_with_multiple_composite_circuits_with_same_name(self):
 
         expected_qasm = """OPENQASM 2.0;
 include "qelib1.inc";
-gate my_gate_{0} q0 {{ x q0; }}
-gate my_gate_{1} q0 {{ x q0; }}
 gate my_gate q0 {{ h q0; }}
+gate my_gate_{1} q0 {{ x q0; }}
+gate my_gate_{0} q0 {{ x q0; }}
 qreg qr[1];
 my_gate qr[0];
 my_gate_{1} qr[0];
@@ -174,6 +174,35 @@ def test_circuit_qasm_with_multiple_composite_circuits_with_same_name(self):
         )
         self.assertEqual(circuit.qasm(), expected_qasm)
 
+    def test_circuit_qasm_with_composite_circuit_with_children_composite_circuit(self):
+        """Test circuit qasm() method when composite circuits with children
+        composite circuits in the definitions are added to the circuit"""
+
+        child_circ = QuantumCircuit(2, name="child_circ")
+        child_circ.h(0)
+        child_circ.cx(0, 1)
+
+        parent_circ = QuantumCircuit(3, name="parent_circ")
+        parent_circ.append(child_circ, range(2))
+        parent_circ.h(2)
+
+        grandparent_circ = QuantumCircuit(4, name="grandparent_circ")
+        grandparent_circ.append(parent_circ, range(3))
+        grandparent_circ.x(3)
+
+        qc = QuantumCircuit(4)
+        qc.append(grandparent_circ, range(4))
+
+        expected_qasm = """OPENQASM 2.0;
+include "qelib1.inc";
+gate child_circ q0,q1 { h q0; cx q0,q1; }
+gate parent_circ q0,q1,q2 { child_circ q0,q1; h q2; }
+gate grandparent_circ q0,q1,q2,q3 { parent_circ q0,q1,q2; x q3; }
+qreg q[4];
+grandparent_circ q[0],q[1],q[2],q[3];\n"""
+
+        self.assertEqual(qc.qasm(), expected_qasm)
+
     def test_circuit_qasm_pi(self):
         """Test circuit qasm() method with pi params."""
         circuit = QuantumCircuit(2)
@@ -229,3 +258,45 @@ def test_gate_qasm_with_ctrl_state(self):
         qc.ch(0, 1, ctrl_state=0)
         qasm_str = qc.qasm()
         self.assertEqual(Operator(qc), Operator(QuantumCircuit.from_qasm_str(qasm_str)))
+
+    def test_circuit_qasm_with_mcx_gate(self):
+        """Test circuit qasm() method with MCXGate
+        See https://github.com/Qiskit/qiskit-terra/issues/4943
+        """
+        qc = QuantumCircuit(4)
+        qc.mcx([0, 1, 2], 3)
+
+        # qasm output doesn't support parameterized gate yet.
+        # param0 for "gate mcuq(param0) is not used inside the definition
+        expected_qasm = """OPENQASM 2.0;
+include "qelib1.inc";
+gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; }
+qreg q[4];
+mcx q[0],q[1],q[2],q[3];\n"""
+        self.assertEqual(qc.qasm(), expected_qasm)
+
+    def test_circuit_qasm_with_mcx_gate_variants(self):
+        """Test circuit qasm() method with MCXGrayCode, MCXRecursive, MCXVChain"""
+        import qiskit.circuit.library as cl
+
+        n = 5
+        qc = QuantumCircuit(2 * n - 1)
+        qc.append(cl.MCXGrayCode(n), range(n + 1))
+        qc.append(cl.MCXRecursive(n), range(n + 2))
+        qc.append(cl.MCXVChain(n), range(2 * n - 1))
+
+        # qasm output doesn't support parameterized gate yet.
+        # param0 for "gate mcuq(param0) is not used inside the definition
+        expected_qasm = """OPENQASM 2.0;
+include "qelib1.inc";
+gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; }
+gate mcu1(param0) q0,q1,q2,q3,q4,q5 { cu1(pi/16) q4,q5; cx q4,q3; cu1(-pi/16) q3,q5; cx q4,q3; cu1(pi/16) q3,q5; cx q3,q2; cu1(-pi/16) q2,q5; cx q4,q2; cu1(pi/16) q2,q5; cx q3,q2; cu1(-pi/16) q2,q5; cx q4,q2; cu1(pi/16) q2,q5; cx q2,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q3,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q2,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q3,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q1,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q2,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q1,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q2,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; }
+gate mcx_gray q0,q1,q2,q3,q4,q5 { h q5; mcu1(pi) q0,q1,q2,q3,q4,q5; h q5; }
+gate mcx_recursive q0,q1,q2,q3,q4,q5,q6 { mcx q0,q1,q2,q6; mcx q3,q4,q6,q5; mcx q0,q1,q2,q6; mcx q3,q4,q6,q5; }
+gate mcx_vchain q0,q1,q2,q3,q4,q5,q6,q7,q8 { rccx q0,q1,q6; rccx q2,q6,q7; rccx q3,q7,q8; ccx q4,q8,q5; rccx q3,q7,q8; rccx q2,q6,q7; rccx q0,q1,q6; }
+qreg q[9];
+mcx_gray q[0],q[1],q[2],q[3],q[4],q[5];
+mcx_recursive q[0],q[1],q[2],q[3],q[4],q[5],q[6];
+mcx_vchain q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8];\n"""
+
+        self.assertEqual(qc.qasm(), expected_qasm)
diff --git a/test/python/circuit/test_unitary.py b/test/python/circuit/test_unitary.py
--- a/test/python/circuit/test_unitary.py
+++ b/test/python/circuit/test_unitary.py
@@ -219,11 +219,11 @@ def test_qasm_unitary_only_one_def(self):
         expected_qasm = (
             "OPENQASM 2.0;\n"
             'include "qelib1.inc";\n'
-            "qreg q0[2];\ncreg c0[1];\n"
-            "x q0[0];\n"
             "gate custom_gate p0 {\n"
             "\tu3(0,0,0) p0;\n"
             "}\n"
+            "qreg q0[2];\ncreg c0[1];\n"
+            "x q0[0];\n"
             "custom_gate q0[0];\n"
             "custom_gate q0[1];\n"
         )
@@ -245,11 +245,11 @@ def test_qasm_unitary_twice(self):
         expected_qasm = (
             "OPENQASM 2.0;\n"
             'include "qelib1.inc";\n'
-            "qreg q0[2];\ncreg c0[1];\n"
-            "x q0[0];\n"
             "gate custom_gate p0 {\n"
             "\tu3(0,0,0) p0;\n"
             "}\n"
+            "qreg q0[2];\ncreg c0[1];\n"
+            "x q0[0];\n"
             "custom_gate q0[0];\n"
             "custom_gate q0[1];\n"
         )
@@ -271,13 +271,13 @@ def test_qasm_2q_unitary(self):
         expected_qasm = (
             "OPENQASM 2.0;\n"
             'include "qelib1.inc";\n'
-            "qreg q0[2];\n"
-            "creg c0[1];\n"
-            "x q0[0];\n"
             "gate custom_gate p0,p1 {\n"
             "\tu3(pi,-pi/2,pi/2) p0;\n"
             "\tu3(pi,pi/2,-pi/2) p1;\n"
             "}\n"
+            "qreg q0[2];\n"
+            "creg c0[1];\n"
+            "x q0[0];\n"
             "custom_gate q0[0],q0[1];\n"
             "custom_gate q0[1],q0[0];\n"
         )