Skip to content

Commit

Permalink
Support for cregbundle in text circuit drawer (#4274)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
Luciano Bello and mergify[bot] authored Apr 28, 2020
1 parent f904bbb commit a0b9736
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 24 deletions.
7 changes: 5 additions & 2 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
19 changes: 14 additions & 5 deletions qiskit/visualization/circuit_visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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`:
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand All @@ -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.
"""
Expand All @@ -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
Expand Down
57 changes: 45 additions & 12 deletions qiskit/visualization/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "═"


Expand Down Expand Up @@ -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']:
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 = \
Expand All @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
99 changes: 95 additions & 4 deletions test/python/visualization/test_circuit_text_drawer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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([' ┌─┐ ',
Expand Down Expand Up @@ -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 = """
Expand All @@ -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 = """
Expand Down Expand Up @@ -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):
Expand All @@ -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). """
Expand All @@ -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. """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit a0b9736

Please sign in to comment.