Repository: Qiskit/qiskit

Issue:
Text drawer can't draw composite instructions
composite gates (i.e. without classical registers) work fine, but with classical registers it cannot be drawn. The logic is here: 
https://github.com/Qiskit/qiskit-terra/blob/109d8ea562987b9309501b9260c80180216061eb/qiskit/visualization/text.py#L849

latex and mpl can do this.


Discussion:
Example code?
It this the expected output?

```python
q = QuantumRegister(4)
c = ClassicalRegister(4)
circ1 = QuantumCircuit(q, c, name='my instruction')
circ1.measure(q, c)
inst = circ1.to_instruction()

circ2 = QuantumCircuit(q, c, name='circ2')
circ2.append(inst, q[:], c[:])
circ2.draw(output='...')
```
## Latex
![image](https://user-images.githubusercontent.com/766693/63095190-f1762080-bf6a-11e9-8dd6-7ed01e951051.png)

## MPL
![image](https://user-images.githubusercontent.com/766693/63095173-e7542200-bf6a-11e9-846b-4714b882cd8f.png)

## Text
Error. I agree needs to be fixed.
No mpl and latex also need to be fixed. The box needs to extend over the classical registers as well. But at least they draw something. I would say text is priority since that's the default drawer.

Resolving patch:
diff --git a/qiskit/visualization/text.py b/qiskit/visualization/text.py
--- a/qiskit/visualization/text.py
+++ b/qiskit/visualization/text.py
@@ -204,6 +204,9 @@ def center_label(self, input_length, order):
             input_length (int): Rhe amount of wires affected.
             order (int): Which middle element is this one?
         """
+        if input_length == order == 0:
+            self.top_connect = self.label
+            return
         location_in_the_box = '*'.center(input_length * 2 - 1).index('*') + 1
         top_limit = order * 2 + 2
         bot_limit = top_limit + 2
@@ -238,21 +241,28 @@ def __init__(self, label="", top_connect=None, wire_label=''):
         self.top_connect = top_connect if top_connect else '─'
 
 
-class BoxOnQuWireMid(MultiBox, BoxOnQuWire):
-    """ Draws the middle part of a box that affects more than one quantum wire"""
+class BoxOnWireMid(MultiBox):
+    """ A generic middle box"""
 
     def __init__(self, label, input_length, order, wire_label=''):
-        super().__init__(label)
+        super().__init__(label, input_length, order)
         self.top_pad = self.bot_pad = self.top_connect = self.bot_connect = " "
         self.wire_label = wire_label
         self.left_fill = len(self.wire_label)
         self.top_format = "│{} %s │".format(self.top_pad * self.left_fill)
-        self.mid_format = "┤{} %s ├".format(self.wire_label)
         self.bot_format = "│{} %s │".format(self.bot_pad * self.left_fill)
         self.top_connect = self.bot_connect = self.mid_content = ''
         self.center_label(input_length, order)
 
 
+class BoxOnQuWireMid(BoxOnWireMid, BoxOnQuWire):
+    """ Draws the middle part of a box that affects more than one quantum wire"""
+
+    def __init__(self, label, input_length, order, wire_label=''):
+        super().__init__(label, input_length, order, wire_label=wire_label)
+        self.mid_format = "┤{} %s ├".format(self.wire_label)
+
+
 class BoxOnQuWireBot(MultiBox, BoxOnQuWire):
     """ Draws the bottom part of a box that affects more than one quantum wire"""
 
@@ -283,17 +293,12 @@ def __init__(self, label="", top_connect=None, wire_label=''):
         self.bot_connect = self.bot_pad = " "
 
 
-class BoxOnClWireMid(MultiBox, BoxOnClWire):
+class BoxOnClWireMid(BoxOnWireMid, BoxOnClWire):
     """ Draws the middle part of a conditional box that affects more than one classical wire"""
 
     def __init__(self, label, input_length, order, wire_label=''):
-        super().__init__(label)
-        self.wire_label = wire_label
-        self.top_format = "│ %s │"
-        self.bot_format = "│ %s │"
-        self.top_pad = self.bot_pad = ' '
-        self.top_connect = self.bot_connect = self.mid_content = ''
-        self.center_label(input_length, order)
+        super().__init__(label, input_length, order, wire_label=wire_label)
+        self.mid_format = "╡{} %s ╞".format(self.wire_label)
 
 
 class BoxOnClWireBot(MultiBox, BoxOnClWire):
@@ -302,8 +307,12 @@ class BoxOnClWireBot(MultiBox, BoxOnClWire):
     def __init__(self, label, input_length, bot_connect='─', wire_label='', **_):
         super().__init__(label)
         self.wire_label = wire_label
-        self.top_format = "│ %s │"
-        self.top_pad = " "
+        self.left_fill = len(self.wire_label)
+        self.top_pad = ' '
+        self.bot_pad = '─'
+        self.top_format = "│{} %s │".format(self.top_pad * self.left_fill)
+        self.mid_format = "╡{} %s ╞".format(self.wire_label)
+        self.bot_format = "└{}%s──┘".format(self.bot_pad * self.left_fill)
         self.bot_connect = bot_connect
 
         self.mid_content = self.top_connect = ""
@@ -779,8 +788,7 @@ def add_connected_gate(instruction, gates, layer, current_cons):
             layer.set_qubit(instruction.qargs[0], gate)
             layer.set_clbit(instruction.cargs[0], MeasureTo())
 
-        elif instruction.name in ['barrier', 'snapshot', 'save', 'load',
-                                  'noise']:
+        elif instruction.name in ['barrier', 'snapshot', 'save', 'load', 'noise']:
             # barrier
             if not self.plotbarriers:
                 return layer, current_cons, connection_label
@@ -863,9 +871,17 @@ def add_connected_gate(instruction, gates, layer, current_cons):
                 label += "(%s)" % ','.join(params)
             layer.set_qu_multibox(instruction.qargs, label, conditional=conditional)
 
+        elif instruction.qargs and instruction.cargs:
+            # multiple gate, involving both qargs AND cargs
+            label = instruction.name
+            params = TextDrawing.params_for_label(instruction)
+            if params:
+                label += "(%s)" % ','.join(params)
+            layer._set_multibox(label, qubits=instruction.qargs, clbits=instruction.cargs,
+                                conditional=conditional)
         else:
             raise VisualizationError(
-                "Text visualizer does not know how to handle this instruction", instruction)
+                "Text visualizer does not know how to handle this instruction: ", instruction.name)
 
         # sort into the order they were declared in
         # this ensures that connected boxes have lines in the right direction
@@ -939,35 +955,70 @@ def set_clbit(self, clbit, element):
         """
         self.clbit_layer[self.cregs.index(clbit)] = element
 
-    def _set_multibox(self, wire_type, bits, label, top_connect=None, conditional=False):
-        bits = list(bits)
-        if wire_type == "cl":
+    def _set_multibox(self, label, qubits=None, clbits=None, top_connect=None, conditional=False):
+        if qubits is not None and clbits is not None:
+            qubits = list(qubits)
+            clbits = list(clbits)
+            cbit_index = sorted([i for i, x in enumerate(self.cregs) if x in clbits])
+            qbit_index = sorted([i for i, x in enumerate(self.qregs) if x in qubits])
+            qargs = [str(qubits.index(qbit)) for qbit in self.qregs if qbit in qubits]
+            cargs = [str(clbits.index(cbit)) for cbit in self.cregs if cbit in clbits]
+
+            box_height = len(self.qregs) - min(qbit_index) + max(cbit_index) + 1
+
+            self.set_qubit(qubits.pop(0), BoxOnQuWireTop(label, wire_label=qargs.pop(0)))
+            order = 0
+            for order, bit_i in enumerate(range(min(qbit_index) + 1, len(self.qregs))):
+                if bit_i in qbit_index:
+                    named_bit = qubits.pop(0)
+                    wire_label = qargs.pop(0)
+                else:
+                    named_bit = self.qregs[bit_i]
+                    wire_label = ' ' * len(wire_label)
+                self.set_qubit(named_bit, BoxOnQuWireMid(label, box_height, order,
+                                                         wire_label=wire_label))
+            for order, bit_i in enumerate(range(max(cbit_index)), order + 1):
+                if bit_i in cbit_index:
+                    named_bit = clbits.pop(0)
+                    wire_label = cargs.pop(0)
+                else:
+                    named_bit = self.cregs[bit_i]
+                    wire_label = ' ' * len(cargs[0])
+                self.set_clbit(named_bit, BoxOnClWireMid(label, box_height, order,
+                                                         wire_label=wire_label))
+            self.set_clbit(clbits.pop(0),
+                           BoxOnClWireBot(label, box_height, wire_label=cargs.pop(0)))
+            return
+
+        if qubits is None and clbits is not None:
+            bits = list(clbits)
             bit_index = sorted([i for i, x in enumerate(self.cregs) if x in bits])
             bits.sort(key=self.cregs.index)
             qargs = [''] * len(bits)
             set_bit = self.set_clbit
-            BoxOnWire = BoxOnClWire
-            BoxOnWireTop = BoxOnClWireTop
-            BoxOnWireMid = BoxOnClWireMid
-            BoxOnWireBot = BoxOnClWireBot
-        elif wire_type == "qu":
+            OnWire = BoxOnClWire
+            OnWireTop = BoxOnClWireTop
+            OnWireMid = BoxOnClWireMid
+            OnWireBot = BoxOnClWireBot
+        elif clbits is None and qubits is not None:
+            bits = list(qubits)
             bit_index = sorted([i for i, x in enumerate(self.qregs) if x in bits])
             qargs = [str(bits.index(qbit)) for qbit in self.qregs if qbit in bits]
             bits.sort(key=self.qregs.index)
             set_bit = self.set_qubit
-            BoxOnWire = BoxOnQuWire
-            BoxOnWireTop = BoxOnQuWireTop
-            BoxOnWireMid = BoxOnQuWireMid
-            BoxOnWireBot = BoxOnQuWireBot
+            OnWire = BoxOnQuWire
+            OnWireTop = BoxOnQuWireTop
+            OnWireMid = BoxOnQuWireMid
+            OnWireBot = BoxOnQuWireBot
         else:
-            raise VisualizationError("_set_multibox only supports 'cl' and 'qu' as wire types.")
+            raise VisualizationError("_set_multibox error!.")
 
         if len(bit_index) == 1:
-            set_bit(bits[0], BoxOnWire(label, top_connect=top_connect))
+            set_bit(bits[0], OnWire(label, top_connect=top_connect))
         else:
             box_height = max(bit_index) - min(bit_index) + 1
             set_bit(bits.pop(0),
-                    BoxOnWireTop(label, top_connect=top_connect, wire_label=qargs.pop(0)))
+                    OnWireTop(label, top_connect=top_connect, wire_label=qargs.pop(0)))
             for order, bit_i in enumerate(range(min(bit_index) + 1, max(bit_index))):
                 if bit_i in bit_index:
                     named_bit = bits.pop(0)
@@ -975,9 +1026,9 @@ def _set_multibox(self, wire_type, bits, label, top_connect=None, conditional=Fa
                 else:
                     named_bit = (self.qregs + self.cregs)[bit_i]
                     wire_label = ' ' * len(qargs[0])
-                set_bit(named_bit, BoxOnWireMid(label, box_height, order, wire_label=wire_label))
-            set_bit(bits.pop(0), BoxOnWireBot(label, box_height, wire_label=qargs.pop(0),
-                                              conditional=conditional))
+                set_bit(named_bit, OnWireMid(label, box_height, order, wire_label=wire_label))
+            set_bit(bits.pop(0), OnWireBot(label, box_height, wire_label=qargs.pop(0),
+                                           conditional=conditional))
 
     def set_cl_multibox(self, creg, label, top_connect='┴'):
         """
@@ -988,7 +1039,7 @@ def set_cl_multibox(self, creg, label, top_connect='┴'):
             top_connect (char): The char to connect the box on the top.
         """
         clbit = [bit for bit in self.cregs if bit.register == creg]
-        self._set_multibox("cl", clbit, label, top_connect=top_connect)
+        self._set_multibox(label, clbits=clbit, top_connect=top_connect)
 
     def set_qu_multibox(self, bits, label, conditional=False):
         """
@@ -998,7 +1049,7 @@ def set_qu_multibox(self, bits, label, conditional=False):
             label (string): The label for the multi qubit box.
             conditional (bool): If the box has a conditional
         """
-        self._set_multibox("qu", bits, label, conditional=conditional)
+        self._set_multibox(label, qubits=bits, conditional=conditional)
 
     def connect_with(self, wire_char):
         """


Test patch:
diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py
--- a/test/python/visualization/test_circuit_text_drawer.py
+++ b/test/python/visualization/test_circuit_text_drawer.py
@@ -1456,6 +1456,85 @@ def test_text_pifrac(self):
         self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
 
 
+class TestTextInstructionWithBothWires(QiskitTestCase):
+    """Composite instructions with both kind of wires
+    See https://github.com/Qiskit/qiskit-terra/issues/2973"""
+
+    def test_text_all_1q_1c(self):
+        """ Test q0-c0 in q0-c0"""
+        expected = '\n'.join(["         ┌───────┐",
+                              "qr_0: |0>┤0      ├",
+                              "         │  name │",
+                              " cr_0: 0 ╡0      ╞",
+                              "         └───────┘"])
+
+        qr1 = QuantumRegister(1, 'qr')
+        cr1 = ClassicalRegister(1, 'cr')
+        inst = QuantumCircuit(qr1, cr1, name='name').to_instruction()
+        circuit = QuantumCircuit(qr1, cr1)
+        circuit.append(inst, qr1[:], cr1[:])
+
+        self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
+
+    def test_text_all_2q_2c(self):
+        """ Test q0-q1-c0-c1 in q0-q1-c0-c1"""
+        expected = '\n'.join(["         ┌───────┐",
+                              "qr_0: |0>┤0      ├",
+                              "         │       │",
+                              "qr_1: |0>┤1      ├",
+                              "         │  name │",
+                              " cr_0: 0 ╡0      ╞",
+                              "         │       │",
+                              " cr_1: 0 ╡1      ╞",
+                              "         └───────┘"])
+
+        qr2 = QuantumRegister(2, 'qr')
+        cr2 = ClassicalRegister(2, 'cr')
+        inst = QuantumCircuit(qr2, cr2, name='name').to_instruction()
+        circuit = QuantumCircuit(qr2, cr2)
+        circuit.append(inst, qr2[:], cr2[:])
+
+        self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
+
+    def test_text_4q_2c(self):
+        """ Test q1-q2-q3-q4-c1-c2 in q0-q1-q2-q3-q4-q5-c0-c1-c2-c3-c4-c5"""
+        expected = '\n'.join(["                 ",
+                              "q_0: |0>─────────",
+                              "        ┌───────┐",
+                              "q_1: |0>┤0      ├",
+                              "        │       │",
+                              "q_2: |0>┤1      ├",
+                              "        │       │",
+                              "q_3: |0>┤2      ├",
+                              "        │       │",
+                              "q_4: |0>┤3      ├",
+                              "        │  name │",
+                              "q_5: |0>┤       ├",
+                              "        │       │",
+                              " c_0: 0 ╡       ╞",
+                              "        │       │",
+                              " c_1: 0 ╡0      ╞",
+                              "        │       │",
+                              " c_2: 0 ╡1      ╞",
+                              "        └───────┘",
+                              " c_3: 0 ═════════",
+                              "                 ",
+                              " c_4: 0 ═════════",
+                              "                 ",
+                              " c_5: 0 ═════════",
+                              "                 "])
+
+        qr4 = QuantumRegister(4)
+        cr4 = ClassicalRegister(2)
+        inst = QuantumCircuit(qr4, cr4, name='name').to_instruction()
+        qr6 = QuantumRegister(6, 'q')
+        cr6 = ClassicalRegister(6, 'c')
+        circuit = QuantumCircuit(qr6, cr6)
+        circuit.append(inst, qr6[1:5], cr6[1:3])
+
+        self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
+
+
 class TestTextWithLayout(QiskitTestCase):
     """The with_layout option"""