Repository: Qiskit/qiskit

Issue:
composite gates do not have definition when qasm()'d
# Information

- **Qiskit Terra version**: master
- **Python version**: 3.7

### What is the current behavior?

```python
from qiskit import QuantumCircuit, QuantumRegister

rinv_q = QuantumRegister(1, name='q')
rinv_gate = QuantumCircuit(rinv_q, name='rinv')
rinv_gate.sdg(rinv_q)
rinv_gate.h(rinv_q)
rinv_gate.sdg(rinv_q)
rinv_gate.h(rinv_q)
rinv = rinv_gate.to_instruction() 

qr = QuantumRegister(1, name='qr') 
circuit = QuantumCircuit(qr, name='circuit') 
circuit.append(rinv, [qr[0]])

print(circuit.qasm())
```
```
OPENQASM 2.0;
include "qelib1.inc";
qreg qr[1];
rinv qr[0];
```
### What is the expected behavior?

```
OPENQASM 2.0;
include "qelib1.inc";
gate rinv q {sdg q; h q; sdg q; h q; }    #  <--- the definition of rinv
qreg qr[1];
rinv qr[0];
```




Discussion:
I've been looking into this one. For the original code you posted, I currently have it returning 
```
OPENQASM 2.0;
include "qelib1.inc";
gate rinv q[0]; {sdg q[0]; h q[0]; sdg q[0]; h q[0]; }
qreg qr[1];
rinv qr[0];
``` 

This is how it returns other types of circuits:
- When the composite circuit includes a `ClassicalRegister` 
```
OPENQASM 2.0;
include "qelib1.inc";
gate circ_c q[0] c[0]; {h q[0]; measure q[0] -> c[0]; }
qreg q[1];
creg c[1];
circ_c q[0],c[0];
```

- When the composite circuit includes a nested composite circuit
```
OPENQASM 2.0;
include "qelib1.inc";
gate nested q[0]; {gate nested_2 q[0]; {h q[0]; } nested_2 q[0]; }
qreg q[1];
nested q[0];
```

I think I can improve the code I used for these. I currently have another method which handles creating the actual qasm string for the composite circuit, which gets called within the `qasm()` method if the instruction is a composite circuit. So I will continue to look into improving the code I have, but just wanted to make sure what is being printed out seems acceptable.
> For the original code you posted, I currently have it returning

That output would solve thins problem! Are you planning a PR?

> `gate circ_c q[0] c[0]; {h q[0]; measure q[0] -> c[0]; }`

I dont think that QASM supports that.

> `gate nested q[0]; {gate nested_2 q[0]; {h q[0]; } nested_2 q[0]; }`

Similarly here, I think nested definitions is not a QASM thing... 
> I dont think that QASM supports that.

Which part is not supported? I can edit it so that it only includes syntax supported by QASM. I can also remove the nested definition handling as well. 

I will open a PR, but will not be able to until Jan 21st, as I am unavailable until then.
> Which part is not supported? 

The [QASM specification](https://arxiv.org/pdf/1707.03429.pdf) does not allow classical bits as arguments. I think. Check out if you can parse them back.

> I will open a PR, but will not be able to until Jan 21st, as I am unavailable until then.

Great! We can wait until  then.. no worries.
If the classical bits and nested composite circuits are not allowed, what should be done in the case where the composite circuit includes a one of these? Should I implement a check to make sure these are not included, or should I assume these will not be included?

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -25,6 +25,7 @@
 from qiskit.util import is_main_process
 from qiskit.util import deprecate_arguments
 from qiskit.circuit.instruction import Instruction
+from qiskit.circuit.gate import Gate
 from qiskit.qasm.qasm import Qasm
 from qiskit.circuit.exceptions import CircuitError
 from .parameterexpression import ParameterExpression
@@ -828,6 +829,23 @@ def _check_compatible_regs(self, rhs):
                     if element1 != element2:
                         raise CircuitError("circuits are 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()."""
+
+        qubit_parameters = ",".join(["q%i" % num for num in range(instruction.num_qubits)])
+        composite_circuit_gates = ""
+
+        for data, qargs, _ in instruction.definition:
+            gate_qargs = ",".join(["q%i" % index for index in [qubit.index for qubit in qargs]])
+            composite_circuit_gates += "%s %s; " % (data.qasm(), gate_qargs)
+
+        qasm_string = "gate %s %s {%s}" % (instruction.name, qubit_parameters,
+                                           composite_circuit_gates)
+
+        return qasm_string
+
     def qasm(self, formatted=False, filename=None):
         """Return OpenQASM string.
 
@@ -842,6 +860,13 @@ def qasm(self, formatted=False, filename=None):
             ImportError: If pygments is not installed and ``formatted`` is
                 ``True``.
         """
+        existing_gate_names = ['ch', 'cx', 'cy', 'cz', 'crx', 'cry', 'crz', 'ccx', 'cswap',
+                               'cu1', 'cu3', 'dcx', 'h', 'i', 'id', 'iden', 'iswap', 'ms',
+                               'r', 'rx', 'rxx', 'ry', 'ryy', 'rz', 'rzx', 'rzz', 's', 'sdg',
+                               'swap', 'x', 'y', 'z', 't', 'tdg', 'u1', 'u2', 'u3']
+
+        existing_composite_circuits = []
+
         string_temp = self.header + "\n"
         string_temp += self.extension_lib + "\n"
         for register in self.qregs:
@@ -856,6 +881,33 @@ def qasm(self, formatted=False, filename=None):
                 string_temp += "%s %s[%d] -> %s[%d];\n" % (instruction.qasm(),
                                                            qubit.register.name, qubit.index,
                                                            clbit.register.name, clbit.index)
+            # If instruction is a composite circuit
+            elif not isinstance(instruction, Gate) and (instruction.name not in ['barrier',
+                                                                                 'reset']):
+                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,
+                                                      "%s\n%s" % (self.extension_lib,
+                                                                  qasm_string))
+
+                    existing_composite_circuits.append(instruction)
+                    existing_gate_names.append(instruction.name)
+
+                # Insert qasm representation of the original instruction
+                string_temp += "%s %s;\n" % (instruction.qasm(),
+                                             ",".join(["%s[%d]" % (j.register.name, j.index)
+                                                       for j in qargs + cargs]))
             else:
                 string_temp += "%s %s;\n" % (instruction.qasm(),
                                              ",".join(["%s[%d]" % (j.register.name, j.index)


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
@@ -68,6 +68,112 @@ def test_circuit_qasm(self):
 measure qr2[1] -> cr[2];\n"""
         self.assertEqual(qc.qasm(), expected_qasm)
 
+    def test_circuit_qasm_with_composite_circuit(self):
+        """Test circuit qasm() method when a composite circuit instruction
+        is included within circuit.
+        """
+
+        composite_circ_qreg = QuantumRegister(2)
+        composite_circ = QuantumCircuit(composite_circ_qreg, name="composite_circ")
+        composite_circ.h(0)
+        composite_circ.x(1)
+        composite_circ.cx(0, 1)
+        composite_circ_instr = composite_circ.to_instruction()
+
+        qr = QuantumRegister(2, 'qr')
+        cr = ClassicalRegister(2, 'cr')
+        qc = QuantumCircuit(qr, cr)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.barrier()
+        qc.append(composite_circ_instr, [0, 1])
+        qc.measure([0, 1], [0, 1])
+
+        expected_qasm = """OPENQASM 2.0;
+include "qelib1.inc";
+gate composite_circ q0,q1 {h q0; x q1; cx q0,q1; }
+qreg qr[2];
+creg cr[2];
+h qr[0];
+cx qr[0],qr[1];
+barrier qr[0],qr[1];
+composite_circ qr[0],qr[1];
+measure qr[0] -> cr[0];
+measure qr[1] -> cr[1];\n"""
+        self.assertEqual(qc.qasm(), expected_qasm)
+
+    def test_circuit_qasm_with_multiple_same_composite_circuits(self):
+        """Test circuit qasm() method when a composite circuit is added
+        to the circuit multiple times
+        """
+
+        composite_circ_qreg = QuantumRegister(2)
+        composite_circ = QuantumCircuit(composite_circ_qreg, name="composite_circ")
+        composite_circ.h(0)
+        composite_circ.x(1)
+        composite_circ.cx(0, 1)
+        composite_circ_instr = composite_circ.to_instruction()
+
+        qr = QuantumRegister(2, 'qr')
+        cr = ClassicalRegister(2, 'cr')
+        qc = QuantumCircuit(qr, cr)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.barrier()
+        qc.append(composite_circ_instr, [0, 1])
+        qc.append(composite_circ_instr, [0, 1])
+        qc.measure([0, 1], [0, 1])
+
+        expected_qasm = """OPENQASM 2.0;
+include "qelib1.inc";
+gate composite_circ q0,q1 {h q0; x q1; cx q0,q1; }
+qreg qr[2];
+creg cr[2];
+h qr[0];
+cx qr[0],qr[1];
+barrier qr[0],qr[1];
+composite_circ qr[0],qr[1];
+composite_circ qr[0],qr[1];
+measure qr[0] -> cr[0];
+measure qr[1] -> cr[1];\n"""
+        self.assertEqual(qc.qasm(), expected_qasm)
+
+    def test_circuit_qasm_with_multiple_composite_circuits_with_same_name(self):
+        """Test circuit qasm() method when multiple composite circuit instructions
+        with the same circuit name are added to the circuit
+        """
+
+        my_gate = QuantumCircuit(1, name='my_gate')
+        my_gate.h(0)
+        my_gate_inst1 = my_gate.to_instruction()
+
+        my_gate = QuantumCircuit(1, name='my_gate')
+        my_gate.x(0)
+        my_gate_inst2 = my_gate.to_instruction()
+
+        my_gate = QuantumCircuit(1, name='my_gate')
+        my_gate.x(0)
+        my_gate_inst3 = my_gate.to_instruction()
+
+        qr = QuantumRegister(1, name='qr')
+        circuit = QuantumCircuit(qr, name='circuit')
+        circuit.append(my_gate_inst1, [qr[0]])
+        circuit.append(my_gate_inst2, [qr[0]])
+        my_gate_inst2_id = id(circuit.data[-1][0])
+        circuit.append(my_gate_inst3, [qr[0]])
+        my_gate_inst3_id = id(circuit.data[-1][0])
+
+        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; }}
+qreg qr[1];
+my_gate qr[0];
+my_gate_{1} qr[0];
+my_gate_{0} qr[0];\n""".format(my_gate_inst3_id, my_gate_inst2_id)
+        self.assertEqual(circuit.qasm(), expected_qasm)
+
     def test_circuit_qasm_pi(self):
         """Test circuit qasm() method with pi params.
         """