From a0b9736615b87f48a36196028c2b93310fe91ac9 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Tue, 28 Apr 2020 00:54:46 -0400 Subject: [PATCH] Support for cregbundle in text circuit drawer (#4274) * drafting * size * layer is cregbundle aware * test * expose it in .draw() * print * scale docstring * lint * test adjustment * multiple cregs * lint * conditional * lint Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/circuit/quantumcircuit.py | 7 +- qiskit/visualization/circuit_visualization.py | 19 +++- qiskit/visualization/text.py | 57 ++++++++--- .../visualization/test_circuit_text_drawer.py | 99 ++++++++++++++++++- .../test_circuit_visualization_output.py | 3 +- 5 files changed, 161 insertions(+), 24 deletions(-) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index d94c9e580f4b..2e860e016fac 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -749,7 +749,7 @@ def qasm(self, formatted=False, filename=None): def draw(self, output=None, scale=0.7, filename=None, style=None, interactive=False, line_length=None, plot_barriers=True, reverse_bits=False, justify=None, vertical_compression='medium', idle_wires=True, - with_layout=True, fold=None, ax=None, initial_state=False): + with_layout=True, fold=None, ax=None, initial_state=False, cregbundle=True): """Draw the quantum circuit. **text**: ASCII art TextDrawing that can be printed in the console. @@ -822,6 +822,8 @@ def draw(self, output=None, scale=0.7, filename=None, style=None, initial_state (bool): Optional. Adds ``|0>`` in the beginning of the wire. Only used by the ``text``, ``latex`` and ``latex_source`` outputs. Default: ``False``. + cregbundle (bool): Optional. If set True bundle classical registers. Only used by + the ``text`` output. Default: ``True``. Returns: :class:`PIL.Image` or :class:`matplotlib.figure` or :class:`str` or @@ -964,7 +966,8 @@ def draw(self, output=None, scale=0.7, filename=None, style=None, with_layout=with_layout, fold=fold, ax=ax, - initial_state=initial_state) + initial_state=initial_state, + cregbundle=cregbundle) def size(self): """Returns total number of gate operations in circuit. diff --git a/qiskit/visualization/circuit_visualization.py b/qiskit/visualization/circuit_visualization.py index 1407388e4372..aeb90f6d83a3 100644 --- a/qiskit/visualization/circuit_visualization.py +++ b/qiskit/visualization/circuit_visualization.py @@ -65,7 +65,8 @@ def circuit_drawer(circuit, with_layout=True, fold=None, ax=None, - initial_state=False): + initial_state=False, + cregbundle=True): """Draw a quantum circuit to different formats (set by output parameter): **text**: ASCII art TextDrawing that can be printed in the console. @@ -78,7 +79,8 @@ def circuit_drawer(circuit, Args: circuit (QuantumCircuit): the quantum circuit to draw - scale (float): scale of image to draw (shrink if < 1) + scale (float): scale of image to draw (shrink if < 1). Only used by the ``mpl``, + ``latex``, and ``latex_source`` outputs. filename (str): file path to save image to style (dict or str): dictionary of style or file name of style file. This option is only used by the ``mpl`` output type. If a str is @@ -137,6 +139,8 @@ def circuit_drawer(circuit, initial_state (bool): Optional. Adds ``|0>`` in the beginning of the wire. Only used by the ``text``, ``latex`` and ``latex_source`` outputs. Default: ``False``. + cregbundle (bool): Optional. If set True bundle classical registers. Only used by + the ``text`` output. Default: ``True``. Returns: :class:`PIL.Image` or :class:`matplotlib.figure` or :class:`str` or :class:`TextDrawing`: @@ -286,7 +290,8 @@ def circuit_drawer(circuit, idle_wires=idle_wires, with_layout=with_layout, fold=fold, - initial_state=initial_state) + initial_state=initial_state, + cregbundle=cregbundle) elif output == 'latex': image = _latex_circuit_drawer(circuit, scale=scale, filename=filename, style=style, @@ -406,7 +411,8 @@ def qx_color_scheme(): def _text_circuit_drawer(circuit, filename=None, line_length=None, reverse_bits=False, plot_barriers=True, justify=None, vertical_compression='high', - idle_wires=True, with_layout=True, fold=None, initial_state=True): + idle_wires=True, with_layout=True, fold=None, initial_state=True, + cregbundle=False): """Draws a circuit using ascii art. Args: @@ -428,6 +434,8 @@ def _text_circuit_drawer(circuit, filename=None, line_length=None, reverse_bits= `shutil.get_terminal_size()`. If you don't want pagination at all, set `fold=-1`. initial_state (bool): Optional. Adds |0> in the beginning of the line. Default: `True`. + cregbundle (bool): Optional. If set True bundle classical registers. Only used by + the ``text`` output. Default: ``False``. Returns: TextDrawing: An instances that, when printed, draws the circuit in ascii art. """ @@ -442,7 +450,8 @@ def _text_circuit_drawer(circuit, filename=None, line_length=None, reverse_bits= if line_length: warn('The parameter "line_length" is being replaced by "fold"', DeprecationWarning, 3) fold = line_length - text_drawing = _text.TextDrawing(qregs, cregs, ops, layout=layout, initial_state=initial_state) + text_drawing = _text.TextDrawing(qregs, cregs, ops, layout=layout, initial_state=initial_state, + cregbundle=cregbundle) text_drawing.plotbarriers = plot_barriers text_drawing.line_length = fold text_drawing.vertical_compression = vertical_compression diff --git a/qiskit/visualization/text.py b/qiskit/visualization/text.py index 43cb19ead5ae..f10bf5954297 100644 --- a/qiskit/visualization/text.py +++ b/qiskit/visualization/text.py @@ -171,11 +171,11 @@ class MeasureTo(DrawElement): bot: """ - def __init__(self): + def __init__(self, label=''): super().__init__() self.top_connect = " ║ " self.mid_content = "═╩═" - self.bot_connect = " " + self.bot_connect = label self.mid_bck = "═" @@ -500,13 +500,15 @@ class TextDrawing(): """ The text drawing""" def __init__(self, qregs, cregs, instructions, plotbarriers=True, - line_length=None, vertical_compression='high', layout=None, initial_state=True): + line_length=None, vertical_compression='high', layout=None, initial_state=True, + cregbundle=False): self.qregs = qregs self.cregs = cregs self.instructions = instructions self.layout = layout self.initial_state = initial_state + self.cregbundle = cregbundle self.plotbarriers = plotbarriers self.line_length = line_length if vertical_compression not in ['high', 'medium', 'low']: @@ -641,9 +643,19 @@ def wire_names(self, with_initial_state=False): index=self.layout[bit.index].index, physical=bit.index)) clbit_labels = [] + previous_creg = None for bit in self.cregs: - label = '{name}_{index}: ' + initial_clbit_value - clbit_labels.append(label.format(name=bit.register.name, index=bit.index)) + if self.cregbundle: + if previous_creg == bit.register: + continue + previous_creg = bit.register + label = '{name}: {initial_value}{size}/' + clbit_labels.append(label.format(name=bit.register.name, + initial_value=initial_clbit_value, + size=bit.register.size)) + else: + label = '{name}_{index}: ' + initial_clbit_value + clbit_labels.append(label.format(name=bit.register.name, index=bit.index)) return qubit_labels + clbit_labels def should_compress(self, top_line, bot_line): @@ -798,6 +810,8 @@ def merge_lines(top, bot, icod="top"): ret += "┬" elif topc in "┘└" and botc in "─" and icod == 'top': ret += "┴" + elif botc == " " and icod == 'top': + ret += topc else: ret += botc return ret @@ -899,7 +913,10 @@ def add_connected_gate(instruction, gates, layer, current_cons): elif isinstance(instruction.op, MeasureInstruction): gate = MeasureFrom() layer.set_qubit(instruction.qargs[0], gate) - layer.set_clbit(instruction.cargs[0], MeasureTo()) + if self.cregbundle: + layer.set_clbit(instruction.cargs[0], MeasureTo(str(instruction.cargs[0].index))) + else: + layer.set_clbit(instruction.cargs[0], MeasureTo()) elif isinstance(instruction.op, (BarrierInstruction, Snapshot)): # barrier @@ -1002,7 +1019,7 @@ def build_layers(self): layers = [InputWire.fillup_layer(wire_names)] for instruction_layer in self.instructions: - layer = Layer(self.qregs, self.cregs) + layer = Layer(self.qregs, self.cregs, self.cregbundle) for instruction in instruction_layer: layer, current_connections, connection_label = \ @@ -1018,12 +1035,22 @@ def build_layers(self): class Layer: """ A layer is the "column" of the circuit. """ - def __init__(self, qregs, cregs): + def __init__(self, qregs, cregs, cregbundle=False): self.qregs = qregs - self.cregs = cregs + if cregbundle: + self.cregs = [] + previous_creg = None + for bit in cregs: + if previous_creg == bit.register: + continue + previous_creg = bit.register + self.cregs.append(bit.register) + else: + self.cregs = cregs self.qubit_layer = [None] * len(qregs) self.connections = [] self.clbit_layer = [None] * len(cregs) + self.cregbundle = cregbundle @property def full_layer(self): @@ -1050,7 +1077,10 @@ def set_clbit(self, clbit, element): clbit (cbit): Element of self.cregs. element (DrawElement): Element to set in the clbit """ - self.clbit_layer[self.cregs.index(clbit)] = element + if self.cregbundle: + self.clbit_layer[self.cregs.index(clbit.register)] = element + else: + self.clbit_layer[self.cregs.index(clbit)] = element def _set_multibox(self, label, qubits=None, clbits=None, top_connect=None, bot_connect=None, conditional=False, controlled_edge=None): @@ -1154,8 +1184,11 @@ def set_cl_multibox(self, creg, label, top_connect='┴'): label (string): The label for the multi clbit box. 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(label, clbits=clbit, top_connect=top_connect) + if self.cregbundle: + self.set_clbit(creg[0], BoxOnClWire(label=label, top_connect=top_connect)) + else: + clbit = [bit for bit in self.cregs if bit.register == creg] + self._set_multibox(label, clbits=clbit, top_connect=top_connect) def set_qu_multibox(self, bits, label, top_connect=None, bot_connect=None, conditional=False, controlled_edge=None): diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py index 9dd21358405f..df0e046f2d26 100644 --- a/test/python/visualization/test_circuit_text_drawer.py +++ b/test/python/visualization/test_circuit_text_drawer.py @@ -60,6 +60,14 @@ def test_measure_to(self): " "] self.assertEqualElement(expected, element) + def test_measure_to_label(self): + """ MeasureTo element with cregbundle """ + element = elements.MeasureTo('1') + expected = [" ║ ", + "═╩═", + " 1 "] + self.assertEqualElement(expected, element) + def test_measure_from(self): """ MeasureFrom element. """ element = elements.MeasureFrom() @@ -124,6 +132,44 @@ def test_text_no_pager(self): class TestTextDrawerGatesInCircuit(QiskitTestCase): """ Gate by gate checks in different settings.""" + def test_text_measure_cregbundle(self): + """ The measure operator, using 3-bit-length registers with cregbundle=True. """ + expected = '\n'.join([" ┌─┐ ", + "q_0: |0>┤M├──────", + " └╥┘┌─┐ ", + "q_1: |0>─╫─┤M├───", + " ║ └╥┘┌─┐", + "q_2: |0>─╫──╫─┤M├", + " ║ ║ └╥┘", + " c: 0 3/═╩══╩══╩═", + " 0 1 2 "]) + + qr = QuantumRegister(3, 'q') + cr = ClassicalRegister(3, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.measure(qr, cr) + self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + + def test_text_measure_cregbundle_2(self): + """ The measure operator, using 2 classical registers with cregbundle=True. """ + expected = '\n'.join([" ┌─┐ ", + "q_0: |0>┤M├───", + " └╥┘┌─┐", + "q_1: |0>─╫─┤M├", + " ║ └╥┘", + "cA: 0 1/═╩══╬═", + " 0 ║ ", + "cB: 0 1/════╩═", + " 0 "]) + + qr = QuantumRegister(2, 'q') + cr_a = ClassicalRegister(1, 'cA') + cr_b = ClassicalRegister(1, 'cB') + circuit = QuantumCircuit(qr, cr_a, cr_b) + circuit.measure(qr[0], cr_a[0]) + circuit.measure(qr[1], cr_b[0]) + self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + def test_text_measure_1(self): """ The measure operator, using 3-bit-length registers. """ expected = '\n'.join([' ┌─┐ ', @@ -1142,6 +1188,28 @@ def test_text_measure_with_spaces(self): class TestTextConditional(QiskitTestCase): """Gates with conditionals""" + def test_text_conditional_1_cregbundle(self): + """ Conditional drawing with 1-bit-length regs and cregbundle.""" + qasm_string = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[1]; + creg c0[1]; + creg c1[1]; + if(c0==1) x q[0]; + if(c1==1) x q[0]; + """ + expected = '\n'.join([" ┌───┐ ┌───┐ ", + "q_0: |0>─┤ X ├──┤ X ├─", + " ┌┴─┴─┴┐ └─┬─┘ ", + "c0: 0 1/╡ = 1 ╞═══╪═══", + " └─────┘┌──┴──┐", + "c1: 0 1/═══════╡ = 1 ╞", + " └─────┘"]) + + circuit = QuantumCircuit.from_qasm_str(qasm_string) + self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + def test_text_conditional_1(self): """ Conditional drawing with 1-bit-length regs.""" qasm_string = """ @@ -1164,6 +1232,27 @@ def test_text_conditional_1(self): circuit = QuantumCircuit.from_qasm_str(qasm_string) self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + def test_text_conditional_2_cregbundle(self): + """ Conditional drawing with 2-bit-length regs with cregbundle""" + qasm_string = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[1]; + creg c0[2]; + creg c1[2]; + if(c0==2) x q[0]; + if(c1==2) x q[0]; + """ + expected = '\n'.join([" ┌───┐ ┌───┐ ", + "q_0: |0>─┤ X ├──┤ X ├─", + " ┌┴─┴─┴┐ └─┬─┘ ", + "c0: 0 2/╡ = 2 ╞═══╪═══", + " └─────┘┌──┴──┐", + "c1: 0 2/═══════╡ = 2 ╞", + " └─────┘"]) + circuit = QuantumCircuit.from_qasm_str(qasm_string) + self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + def test_text_conditional_2(self): """ Conditional drawing with 2-bit-length regs.""" qasm_string = """ @@ -2607,7 +2696,7 @@ def test_after_transpile(self): [13, 12]] qc_result = transpile(qc, basis_gates=['u1', 'u2', 'u3', 'cx', 'id'], coupling_map=coupling_map, optimization_level=0, seed_transpiler=0) - self.assertEqual(qc_result.draw(output='text').single_string(), expected) + self.assertEqual(qc_result.draw(output='text', cregbundle=False).single_string(), expected) class TestTextInitialValue(QiskitTestCase): @@ -2631,7 +2720,8 @@ def test_draw_initial_value_default(self): "c_1: ════╩═", " "]) - self.assertEqual(self.circuit.draw(output='text').single_string(), expected) + self.assertEqual(self.circuit.draw(output='text', cregbundle=False).single_string(), + expected) def test_draw_initial_value_true(self): """ Text drawer .draw(initial_state=True). """ @@ -2644,8 +2734,9 @@ def test_draw_initial_value_true(self): " ║ ", " c_1: 0 ════╩═", " "]) - self.assertEqual(self.circuit.draw(output='text', initial_state=True).single_string(), - expected) + self.assertEqual(self.circuit.draw(output='text', + initial_state=True, + cregbundle=False).single_string(), expected) def test_initial_value_false(self): """ Text drawer with initial_state parameter False. """ diff --git a/test/python/visualization/test_circuit_visualization_output.py b/test/python/visualization/test_circuit_visualization_output.py index 4476b2856e79..6e4938fa5288 100644 --- a/test/python/visualization/test_circuit_visualization_output.py +++ b/test/python/visualization/test_circuit_visualization_output.py @@ -100,7 +100,8 @@ def test_matplotlib_drawer(self): def test_text_drawer(self): filename = self._get_resource_path('current_textplot.txt') qc = self.sample_circuit() - output = circuit_drawer(qc, filename=filename, output="text", fold=-1, initial_state=True) + output = circuit_drawer(qc, filename=filename, output="text", fold=-1, initial_state=True, + cregbundle=False) self.assertFilesAreEqual(filename, self.text_reference) os.remove(filename) try: