Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issues with ControlFlowOp displays #10842

Merged
merged 44 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e9f5af3
Fix override bug and testing
enavarro51 Aug 5, 2023
bf3dfde
Preliminary changes
enavarro51 Aug 7, 2023
b54ac68
Merge branch 'main' into add_expr_to_drawers
enavarro51 Aug 10, 2023
131c899
Merge main
enavarro51 Aug 15, 2023
f77c0e3
First attempts
enavarro51 Aug 15, 2023
aaeea24
Work on x_index
enavarro51 Aug 15, 2023
9f0aba0
Merge branch 'main' into add_expr_to_drawers
enavarro51 Aug 22, 2023
38502e0
Early testing
enavarro51 Aug 25, 2023
1bc3e9a
Merge branch 'main' into add_expr_to_drawers
enavarro51 Aug 28, 2023
2e43bcf
Cleanup getattr
enavarro51 Aug 28, 2023
0fd86e2
Complete adding exprs
enavarro51 Aug 30, 2023
b1bdcda
First fix for cregbundle control flow
enavarro51 Sep 1, 2023
a124a5b
Finish basic cregbundle mods
enavarro51 Sep 3, 2023
e37ccef
Merge branch 'main' into fix_flow_cregbundle
enavarro51 Sep 3, 2023
4891144
Finish all cregbundle except wire_map bug
enavarro51 Sep 3, 2023
25da3db
Move carg check to circuit_vis and change text tests to circuit_drawer
enavarro51 Sep 4, 2023
f636dd2
Move mpl tests to use circuit_drawer and update images for cregbundle…
enavarro51 Sep 5, 2023
71f9847
Change mpl tests to circuit_drawer
enavarro51 Sep 5, 2023
3343dc8
Fix flow_wire_map and tranpile bug
enavarro51 Sep 8, 2023
e75f3f5
Final cleanup
enavarro51 Sep 9, 2023
5212e79
Merge branch 'main' into fix_flow_cregbundle
enavarro51 Sep 9, 2023
9cd6e0e
Missing outputs on mpl tests
enavarro51 Sep 9, 2023
9d7a2e6
Merge branch 'fix_flow_cregbundle' of github.com:enavarro51/qiskit-te…
enavarro51 Sep 9, 2023
85ba619
Fix output
enavarro51 Sep 9, 2023
6b1bb97
Merge branch 'main' into fix_flow_cregbundle_2
enavarro51 Sep 13, 2023
8e84851
Restore tests and move carg check to text and mpl and make circuit
enavarro51 Sep 14, 2023
c577036
Prepare final cregbundle fix
enavarro51 Sep 14, 2023
27d4714
Update ref images
enavarro51 Sep 14, 2023
e48ae60
Fixing refs
enavarro51 Sep 14, 2023
6883110
Update refs again
enavarro51 Sep 14, 2023
3ec10aa
Final mods
enavarro51 Sep 14, 2023
e857813
Incorporate mtreinish changes
enavarro51 Sep 14, 2023
8275ea9
Add reno
enavarro51 Sep 14, 2023
5072d44
Fix if_else_op test
enavarro51 Sep 14, 2023
3da0bd3
Merge branch 'main' into fix_flow_cregbundle_2
enavarro51 Sep 14, 2023
e7e132d
Lint
enavarro51 Sep 14, 2023
3fc89c4
Merge branch 'fix_flow_cregbundle_2' of github.com:enavarro51/qiskit-…
enavarro51 Sep 14, 2023
a7d9388
Fix target == expr bug in text
enavarro51 Sep 20, 2023
269a3ee
Merge branch 'main' into fix_flow_cregbundle_2
enavarro51 Sep 20, 2023
e1d7982
Review fixes and outer_circuit, wire_map fixes
enavarro51 Sep 22, 2023
5736d03
Remove png
enavarro51 Sep 23, 2023
278f597
Minor changes
enavarro51 Sep 23, 2023
1859210
Fix for_loop measure placement bug
enavarro51 Sep 23, 2023
aca3641
Merge remote-tracking branch 'ibm/main' into fix_flow_cregbundle_2
jakelishman Sep 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added bit_conditional_bundle.png
jakelishman marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1670,6 +1670,7 @@ def draw(
initial_state: bool = False,
cregbundle: bool = None,
wire_order: list = None,
encoding: str = None,
jakelishman marked this conversation as resolved.
Show resolved Hide resolved
):
"""Draw the quantum circuit. Use the output parameter to choose the drawing format:

Expand Down Expand Up @@ -1763,6 +1764,8 @@ def draw(
wire_order (list): Optional. A list of integers used to reorder the display
of the bits. The list must have an entry for every bit with the bits
in the range 0 to (``num_qubits`` + ``num_clbits``).
encoding (str): Optional. Sets the encoding preference of the output.
Default: ``sys.stdout.encoding``.

Returns:
:class:`.TextDrawing` or :class:`matplotlib.figure` or :class:`PIL.Image` or
Expand Down Expand Up @@ -1815,6 +1818,7 @@ def draw(
initial_state=initial_state,
cregbundle=cregbundle,
wire_order=wire_order,
encoding=encoding,
)

def size(
Expand Down
4 changes: 4 additions & 0 deletions qiskit/visualization/circuit/circuit_visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def circuit_drawer(
initial_state=False,
cregbundle=None,
wire_order=None,
encoding=None,
):
"""Draw the quantum circuit. Use the output parameter to choose the drawing format:

Expand Down Expand Up @@ -156,6 +157,8 @@ def circuit_drawer(
wire_order (list): Optional. A list of integers used to reorder the display
of the bits. The list must have an entry for every bit with the bits
in the range 0 to (num_qubits + num_clbits).
encoding (str): Optional. Sets the encoding preference of the output.
Default: ``sys.stdout.encoding``.

Returns:
:class:`TextDrawing` or :class:`matplotlib.figure` or :class:`PIL.Image` or
Expand Down Expand Up @@ -257,6 +260,7 @@ def circuit_drawer(
initial_state=initial_state,
cregbundle=cregbundle,
wire_order=complete_wire_order,
encoding=encoding,
)
elif output == "latex":
image = _latex_circuit_drawer(
Expand Down
187 changes: 112 additions & 75 deletions qiskit/visualization/circuit/matplotlib.py

Large diffs are not rendered by default.

93 changes: 53 additions & 40 deletions qiskit/visualization/circuit/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from warnings import warn
from shutil import get_terminal_size
import collections
import itertools
import sys

from qiskit.circuit import Qubit, Clbit, ClassicalRegister
Expand Down Expand Up @@ -732,19 +731,27 @@ def __init__(
self.vertical_compression = vertical_compression
self._wire_map = {}

for node in itertools.chain.from_iterable(self.nodes):
if node.cargs and node.op.name != "measure":
if cregbundle:
warn(
"Cregbundle set to False since an instruction needs to refer"
" to individual classical wire",
RuntimeWarning,
2,
)
self.cregbundle = False
break
else:
self.cregbundle = True if cregbundle is None else cregbundle
def check_clbit_in_inst(circuit, cregbundle):
if cregbundle is False:
return False
for inst in circuit.data:
if isinstance(inst.operation, ControlFlowOp):
for block in inst.operation.blocks:
if check_clbit_in_inst(block, cregbundle) is False:
return False
elif inst.clbits and not isinstance(inst.operation, Measure):
if cregbundle is not False:
warn(
"Cregbundle set to False since an instruction needs to refer"
" to individual classical wire",
RuntimeWarning,
3,
)
return False

return True

self.cregbundle = check_clbit_in_inst(circuit, cregbundle)

if encoding:
self.encoding = encoding
Expand Down Expand Up @@ -1257,7 +1264,13 @@ def build_layers(self):
layers = [InputWire.fillup_layer(wire_names)]

for node_layer in self.nodes:
layer = Layer(self.qubits, self.clbits, self.cregbundle, self._circuit, self._wire_map)
layer = Layer(
self.qubits,
self.clbits,
self.cregbundle,
self._circuit,
self._wire_map,
)
for node in node_layer:
if isinstance(node.op, ControlFlowOp):
self._nest_depth = 0
Expand Down Expand Up @@ -1291,25 +1304,18 @@ def add_control_flow(self, node, layers, wire_map):
# Update the wire_map with the qubits and clbits from the inner circuit
flow_wire_map = wire_map.copy()
flow_wire_map.update(
(inner, wire_map[outer]) for outer, inner in zip(node.qargs, circuit.qubits)
)
flow_wire_map.update(
(inner, wire_map[outer]) for outer, inner in zip(node.cargs, circuit.clbits)
{inner: wire_map[outer] for outer, inner in zip(node.qargs, circuit.qubits)}
)
jakelishman marked this conversation as resolved.
Show resolved Hide resolved
if circ_num > 0:
# Draw a middle box such as Else and Case
flow_layer = self.draw_flow_box(node, flow_wire_map, CF_MID, circ_num - 1)
layers.append(flow_layer.full_layer)

qubits, _, nodes = _get_layered_instructions(circuit, wire_map=flow_wire_map)
__, _, nodes = _get_layered_instructions(circuit, wire_map=flow_wire_map)
for layer_nodes in nodes:
# Limit qubits sent to only ones from main circuit, so qubit_layer is correct length
flow_layer2 = Layer(
qubits[: len(self.qubits)],
self.clbits,
self.cregbundle,
circuit,
flow_wire_map,
self.qubits, self.clbits, self.cregbundle, self._circuit, flow_wire_map
)
for layer_node in layer_nodes:
if isinstance(layer_node.op, ControlFlowOp):
Expand Down Expand Up @@ -1337,15 +1343,16 @@ def add_control_flow(self, node, layers, wire_map):
def draw_flow_box(self, node, flow_wire_map, section, circ_num=0):
"""Draw the left, middle, or right of a control flow box"""

conditional = section == CF_LEFT and not isinstance(node.op, ForLoopOp)
op = node.op
conditional = section == CF_LEFT and not isinstance(op, ForLoopOp)
depth = str(self._nest_depth)
if section == CF_LEFT:
if isinstance(node.op, IfElseOp):
if isinstance(op, IfElseOp):
jakelishman marked this conversation as resolved.
Show resolved Hide resolved
label = "If-" + depth
elif isinstance(node.op, WhileLoopOp):
elif isinstance(op, WhileLoopOp):
label = "While-" + depth
elif isinstance(node.op, ForLoopOp):
indexset = node.op.params[0]
elif isinstance(op, ForLoopOp):
indexset = op.params[0]
# If tuple of values instead of range, cut it off at 4 items
if "range" not in str(indexset) and len(indexset) > 4:
index_str = str(indexset[:4])
Expand All @@ -1356,11 +1363,11 @@ def draw_flow_box(self, node, flow_wire_map, section, circ_num=0):
else:
label = "Switch-" + depth
elif section == CF_MID:
if isinstance(node.op, IfElseOp):
if isinstance(op, IfElseOp):
label = "Else-" + depth
else:
jump_list = []
for jump_values, _ in list(node.op.cases_specifier()):
for jump_values, _ in list(op.cases_specifier()):
jump_list.append(jump_values)

if "default" in str(jump_list[circ_num][0]):
Expand All @@ -1372,7 +1379,13 @@ def draw_flow_box(self, node, flow_wire_map, section, circ_num=0):
else:
label = "End-" + depth

flow_layer = Layer(self.qubits, self.clbits, self.cregbundle, self._circuit, flow_wire_map)
flow_layer = Layer(
self.qubits,
self.clbits,
self.cregbundle,
self._circuit,
flow_wire_map,
)
# If only 1 qubit, draw basic 1 qubit box
if len(node.qargs) == 1:
flow_layer.set_qubit(
Expand Down Expand Up @@ -1411,13 +1424,15 @@ def draw_flow_box(self, node, flow_wire_map, section, circ_num=0):
),
)
if conditional:
if isinstance(node.op, SwitchCaseOp):
if isinstance(node.op.target, Clbit):
condition = (node.op.target, 1)
if isinstance(op, SwitchCaseOp):
if isinstance(op.target, expr.Expr):
condition = op.target
elif isinstance(op.target, Clbit):
condition = (op.target, 1)
else:
condition = (node.op.target, 2 ** (node.op.target.size) - 1)
condition = (op.target, 2 ** (op.target.size) - 1)
else:
condition = node.op.condition
condition = op.condition
_ = flow_layer.set_cl_multibox(condition, flow_wire_map, top_connect="╨")

return flow_layer
Expand All @@ -1428,9 +1443,7 @@ class Layer:

def __init__(self, qubits, clbits, cregbundle, circuit, wire_map):
self.qubits = qubits
self.clbits_raw = clbits # list of clbits ignoring cregbundle change below
self._circuit = circuit

if cregbundle:
self.clbits = []
previous_creg = None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
fixes:
- |
Fixed an issue with the matplotlib based visualization in the
:meth:`.QuantumCircuit.draw` method and the :func:`~.circuit_drawer`
function when visualizing circuits that had control flow instructions.
Previously in some situations, especially with a layout set, the output
visualization could have put gates inside a control flow block on the
wrong wires in the visualization.
Fixed `#10601 <https://github.com/Qiskit/qiskit-terra/issues/10601>`__
- |
Fixed an issue with the matplotlib based visualization in the
:meth:`.QuantumCircuit.draw` method and the :func:`~.circuit_drawer`
function when visualizing circuits that had control flow instructions.
Previously when the `cregbundle` option was set to None or True, the
drawer did not properly display the `cregs` as bundled.
72 changes: 59 additions & 13 deletions test/python/visualization/test_circuit_text_drawer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5280,8 +5280,8 @@ def test_text_drawer_cp437(self):
class TestCircuitControlFlowOps(QiskitVisualizationTestCase):
"""Test ControlFlowOps."""

def test_if_op(self):
"""Test an IfElseOp with if only"""
def test_if_op_bundle_false(self):
"""Test an IfElseOp with if only and cregbundle false"""
expected = "\n".join(
[
" ┌────── ┌───┐ ───────┐ ",
Expand All @@ -5304,6 +5304,35 @@ def test_if_op(self):
cr = ClassicalRegister(2, "cr")
circuit = QuantumCircuit(qr, cr)

with circuit.if_test((cr[1], 1)):
circuit.h(0)
circuit.cx(0, 1)
self.assertEqual(
str(_text_circuit_drawer(circuit, initial_state=False, cregbundle=False)), expected
)

def test_if_op_bundle_true(self):
"""Test an IfElseOp with if only and cregbundle true"""
expected = "\n".join(
[
" ┌────── ┌───┐ ───────┐ ",
" q_0: ──┤ ──┤ H ├──■── ├─",
" │ If-0 └───┘┌─┴─┐ End-0 │ ",
" q_1: ──┤ ───────┤ X ├ ├─",
" └──╥─── └───┘ ───────┘ ",
" q_2: ─────╫──────────────────────────",
" ║ ",
" q_3: ─────╫──────────────────────────",
" ┌────╨─────┐ ",
"cr: 2/╡ cr_1=0x1 ╞════════════════════",
" └──────────┘ ",
]
)

qr = QuantumRegister(4, "q")
cr = ClassicalRegister(2, "cr")
circuit = QuantumCircuit(qr, cr)

with circuit.if_test((cr[1], 1)):
circuit.h(0)
circuit.cx(0, 1)
Expand Down Expand Up @@ -5350,7 +5379,9 @@ def test_if_else_with_body_specified(self):

circuit.if_else((cr[1], 1), circuit2, None, [0, 1, 2], [0, 1, 2])
circuit.x(0, label="X1i")
self.assertEqual(str(_text_circuit_drawer(circuit, initial_state=False)), expected)
self.assertEqual(
str(_text_circuit_drawer(circuit, initial_state=False, cregbundle=False)), expected
)

def test_if_op_nested_wire_order(self):
"""Test IfElseOp with nested if's and wire_order change."""
Expand Down Expand Up @@ -5432,7 +5463,10 @@ def test_if_op_nested_wire_order(self):
self.assertEqual(
str(
_text_circuit_drawer(
circuit, fold=77, initial_state=False, wire_order=[2, 0, 3, 1, 4, 5, 6]
circuit,
fold=77,
initial_state=False,
wire_order=[2, 0, 3, 1, 4, 5, 6],
)
),
expected,
Expand Down Expand Up @@ -5472,7 +5506,9 @@ def test_while_loop(self):
circuit.measure(0, 0)
with circuit.if_test((cr[2], 1)):
circuit.x(0)
self.assertEqual(str(_text_circuit_drawer(circuit, initial_state=False)), expected)
self.assertEqual(
str(_text_circuit_drawer(circuit, initial_state=False, cregbundle=False)), expected
)

def test_for_loop(self):
"""Test ForLoopOp."""
Expand Down Expand Up @@ -5509,7 +5545,10 @@ def test_for_loop(self):
circuit.measure(0, 0)
with circuit.if_test((cr[2], 1)):
circuit.z(0)
self.assertEqual(str(_text_circuit_drawer(circuit, fold=-1, initial_state=False)), expected)
self.assertEqual(
str(_text_circuit_drawer(circuit, fold=-1, initial_state=False, cregbundle=False)),
expected,
)

def test_switch_case(self):
"""Test SwitchCaseOp."""
Expand Down Expand Up @@ -5562,7 +5601,10 @@ def test_switch_case(self):
with case(case.DEFAULT):
circuit.cx(0, 1)
circuit.h(0)
self.assertEqual(str(_text_circuit_drawer(circuit, fold=78, initial_state=False)), expected)
self.assertEqual(
str(_text_circuit_drawer(circuit, fold=78, initial_state=False, cregbundle=False)),
expected,
)

def test_inner_wire_map_control_op(self):
"""Test that the gates inside ControlFlowOps land on correct qubits when transpiled"""
Expand Down Expand Up @@ -5599,7 +5641,10 @@ def test_inner_wire_map_control_op(self):
backend.target.add_instruction(IfElseOp, name="if_else")

circuit = transpile(qc, backend, optimization_level=2, seed_transpiler=671_42)
self.assertEqual(str(_text_circuit_drawer(circuit, fold=78, initial_state=False)), expected)
self.assertEqual(
str(_text_circuit_drawer(circuit, fold=78, initial_state=False, cregbundle=False)),
expected,
)

def test_if_else_op_from_circuit_with_conditions(self):
"""Test an IfElseOp built from circuit with conditions inside the if using inner creg"""
Expand Down Expand Up @@ -5630,13 +5675,14 @@ def test_if_else_op_from_circuit_with_conditions(self):
circuit.x(2).c_if(cr[1], 2)

qr2 = QuantumRegister(3, "qr2")
cr2 = ClassicalRegister(3, "cr2")
qc2 = QuantumCircuit(qr2, cr2)
qc2.x(0, label="X1").c_if(cr2, 4)
qc2.x(1, label="X2").c_if(cr2[1], 1)
qc2 = QuantumCircuit(qr2, cr)
qc2.x(0, label="X1").c_if(cr, 4)
qc2.x(1, label="X2").c_if(cr[1], 1)

circuit.if_else((cr[1], 1), qc2, None, [0, 1, 2], [0, 1, 2])
self.assertEqual(str(_text_circuit_drawer(circuit, initial_state=False)), expected)
self.assertEqual(
str(_text_circuit_drawer(circuit, initial_state=False, cregbundle=False)), expected
)


if __name__ == "__main__":
Expand Down
Binary file modified test/visual/mpl/circuit/references/fold_with_conditions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading