Skip to content

Commit 166125b

Browse files
jakelishmanmergify[bot]
authored andcommitted
Make circuit drawers *not crash* on Expr nodes (#10504)
* Make circuit drawers *not crash* on `Expr` nodes This at least causes the circuit visualisers to not crash when encountering an `Expr` node, and instead emit a warning and make a best-effort attempt (except for LaTeX) to output _something_. We intend to extend the capabilities of these drawers in the future. * Soften warnings about unsupported `Expr` nodes (cherry picked from commit c8552f6)
1 parent 418d24e commit 166125b

File tree

6 files changed

+98
-38
lines changed

6 files changed

+98
-38
lines changed

qiskit/circuit/quantumcircuit.py

+7
Original file line numberDiff line numberDiff line change
@@ -1794,6 +1794,13 @@ def draw(
17941794
17951795
**latex_source**: raw uncompiled latex output.
17961796
1797+
.. warning::
1798+
1799+
Support for :class:`~.expr.Expr` nodes in conditions and :attr:`.SwitchCaseOp.target`
1800+
fields is preliminary and incomplete. The ``text`` and ``mpl`` drawers will make a
1801+
best-effort attempt to show data dependencies, but the LaTeX-based drawers will skip
1802+
these completely.
1803+
17971804
Args:
17981805
output (str): select the output method to use for drawing the circuit.
17991806
Valid choices are ``text``, ``mpl``, ``latex``, ``latex_source``.

qiskit/visualization/circuit/_utils.py

+6-10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
Instruction,
2626
Measure,
2727
)
28+
from qiskit.circuit.controlflow import condition_resources
2829
from qiskit.circuit.library import PauliEvolutionGate
2930
from qiskit.circuit import ClassicalRegister, QuantumCircuit, Qubit, ControlFlowOp
3031
from qiskit.circuit.tools import pi_check
@@ -549,16 +550,11 @@ def slide_from_left(self, node, index):
549550
curr_index = index
550551
last_insertable_index = -1
551552
index_stop = -1
552-
if getattr(node.op, "condition", None):
553-
if isinstance(node.op.condition[0], Clbit):
554-
cond_bit = [clbit for clbit in self.clbits if node.op.condition[0] == clbit]
555-
index_stop = self.measure_map[cond_bit[0]]
556-
else:
557-
for bit in node.op.condition[0]:
558-
max_index = -1
559-
if bit in self.measure_map:
560-
if self.measure_map[bit] > max_index:
561-
index_stop = max_index = self.measure_map[bit]
553+
if (condition := getattr(node.op, "condition", None)) is not None:
554+
index_stop = max(
555+
(self.measure_map[bit] for bit in condition_resources(condition).clbits),
556+
default=index_stop,
557+
)
562558
if node.cargs:
563559
for carg in node.cargs:
564560
try:

qiskit/visualization/circuit/circuit_visualization.py

+6
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ def circuit_drawer(
7373
7474
**latex_source**: raw uncompiled latex output.
7575
76+
.. warning::
77+
78+
Support for :class:`~.expr.Expr` nodes in conditions and :attr:`.SwitchCaseOp.target` fields
79+
is preliminary and incomplete. The ``text`` and ``mpl`` drawers will make a best-effort
80+
attempt to show data dependencies, but the LaTeX-based drawers will skip these completely.
81+
7682
Args:
7783
circuit (QuantumCircuit): the quantum circuit to draw
7884
scale (float): scale of image to draw (shrink if < 1.0). Only used by

qiskit/visualization/circuit/latex.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import numpy as np
2222
from qiskit.circuit import Clbit, Qubit, ClassicalRegister, QuantumRegister, QuantumCircuit
23+
from qiskit.circuit.classical import expr
2324
from qiskit.circuit.controlledgate import ControlledGate
2425
from qiskit.circuit.library.standard_gates import SwapGate, XGate, ZGate, RZZGate, U1Gate, PhaseGate
2526
from qiskit.circuit.measure import Measure
@@ -416,7 +417,10 @@ def _build_latex_array(self):
416417
num_cols_op = 1
417418
wire_list = [self._wire_map[qarg] for qarg in node.qargs if qarg in self._qubits]
418419
if getattr(op, "condition", None):
419-
self._add_condition(op, wire_list, column)
420+
if isinstance(op.condition, expr.Expr):
421+
warn("ignoring expression condition, which is not supported yet")
422+
else:
423+
self._add_condition(op, wire_list, column)
420424

421425
if isinstance(op, Measure):
422426
self._build_measure(node, column)
@@ -619,7 +623,6 @@ def _add_condition(self, op, wire_list, col):
619623
# cwire - the wire number for the first wire for the condition register
620624
# or if cregbundle, wire number of the condition register itself
621625
# gap - the number of wires from cwire to the bottom gate qubit
622-
623626
label, val_bits = get_condition_label_val(op.condition, self._circuit, self._cregbundle)
624627
cond_is_bit = isinstance(op.condition[0], Clbit)
625628
cond_reg = op.condition[0]

qiskit/visualization/circuit/matplotlib.py

+50-26
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
"""mpl circuit visualization backend."""
1616

17+
import collections
1718
import itertools
1819
import re
1920
from warnings import warn
@@ -33,6 +34,8 @@
3334
ForLoopOp,
3435
SwitchCaseOp,
3536
)
37+
from qiskit.circuit.controlflow import condition_resources
38+
from qiskit.circuit.classical import expr
3639
from qiskit.circuit.library.standard_gates import (
3740
SwapGate,
3841
RZZGate,
@@ -1090,45 +1093,66 @@ def _condition(self, node, node_data, wire_map, cond_xy, glob_data):
10901093
# For SwitchCaseOp convert the target to a fully closed Clbit or register
10911094
# in condition format
10921095
if isinstance(node.op, SwitchCaseOp):
1093-
if isinstance(node.op.target, Clbit):
1096+
if isinstance(node.op.target, expr.Expr):
1097+
condition = node.op.target
1098+
elif isinstance(node.op.target, Clbit):
10941099
condition = (node.op.target, 1)
10951100
else:
10961101
condition = (node.op.target, 2 ** (node.op.target.size) - 1)
10971102
else:
10981103
condition = node.op.condition
1099-
label, val_bits = get_condition_label_val(condition, self._circuit, self._cregbundle)
1100-
cond_bit_reg = condition[0]
1101-
cond_bit_val = int(condition[1])
11021104

1105+
override_fc = False
11031106
first_clbit = len(self._qubits)
11041107
cond_pos = []
11051108

1106-
# In the first case, multiple bits are indicated on the drawing. In all
1107-
# other cases, only one bit is shown.
1108-
if not self._cregbundle and isinstance(cond_bit_reg, ClassicalRegister):
1109-
for idx in range(cond_bit_reg.size):
1110-
cond_pos.append(cond_xy[wire_map[cond_bit_reg[idx]] - first_clbit])
1111-
1112-
# If it's a register bit and cregbundle, need to use the register to find the location
1113-
elif self._cregbundle and isinstance(cond_bit_reg, Clbit):
1114-
register = get_bit_register(self._circuit, cond_bit_reg)
1115-
if register is not None:
1116-
cond_pos.append(cond_xy[wire_map[register] - first_clbit])
1109+
if isinstance(condition, expr.Expr):
1110+
# If fixing this, please update the docstrings of `QuantumCircuit.draw` and
1111+
# `visualization.circuit_drawer` to remove warnings.
1112+
condition_bits = condition_resources(condition).clbits
1113+
label = "[expression]"
1114+
override_fc = True
1115+
registers = collections.defaultdict(list)
1116+
for bit in condition_bits:
1117+
registers[get_bit_register(self._circuit, bit)].append(bit)
1118+
# Registerless bits don't care whether cregbundle is set.
1119+
cond_pos.extend(cond_xy[wire_map[bit] - first_clbit] for bit in registers.pop(None, ()))
1120+
if self._cregbundle:
1121+
cond_pos.extend(
1122+
cond_xy[wire_map[register[0]] - first_clbit] for register in registers
1123+
)
11171124
else:
1118-
cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit])
1125+
cond_pos.extend(
1126+
cond_xy[wire_map[bit] - first_clbit]
1127+
for register, bits in registers.items()
1128+
for bit in bits
1129+
)
1130+
val_bits = ["1"] * len(cond_pos)
11191131
else:
1120-
cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit])
1132+
label, val_bits = get_condition_label_val(condition, self._circuit, self._cregbundle)
1133+
cond_bit_reg = condition[0]
1134+
cond_bit_val = int(condition[1])
1135+
override_fc = cond_bit_val != 0
1136+
1137+
# In the first case, multiple bits are indicated on the drawing. In all
1138+
# other cases, only one bit is shown.
1139+
if not self._cregbundle and isinstance(cond_bit_reg, ClassicalRegister):
1140+
for idx in range(cond_bit_reg.size):
1141+
cond_pos.append(cond_xy[wire_map[cond_bit_reg[idx]] - first_clbit])
1142+
1143+
# If it's a register bit and cregbundle, need to use the register to find the location
1144+
elif self._cregbundle and isinstance(cond_bit_reg, Clbit):
1145+
register = get_bit_register(self._circuit, cond_bit_reg)
1146+
if register is not None:
1147+
cond_pos.append(cond_xy[wire_map[register] - first_clbit])
1148+
else:
1149+
cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit])
1150+
else:
1151+
cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit])
11211152

11221153
xy_plot = []
1123-
for idx, xy in enumerate(cond_pos):
1124-
if val_bits[idx] == "1" or (
1125-
isinstance(cond_bit_reg, ClassicalRegister)
1126-
and cond_bit_val != 0
1127-
and self._cregbundle
1128-
):
1129-
fc = self._style["lc"]
1130-
else:
1131-
fc = self._style["bg"]
1154+
for val_bit, xy in zip(val_bits, cond_pos):
1155+
fc = self._style["lc"] if override_fc or val_bit == "1" else self._style["bg"]
11321156
box = glob_data["patches_mod"].Circle(
11331157
xy=xy,
11341158
radius=WID * 0.15,

qiskit/visualization/circuit/text.py

+24
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616

1717
from warnings import warn
1818
from shutil import get_terminal_size
19+
import collections
1920
import itertools
2021
import sys
2122

2223
from qiskit.circuit import Qubit, Clbit, ClassicalRegister
2324
from qiskit.circuit import ControlledGate
2425
from qiskit.circuit import Reset
2526
from qiskit.circuit import Measure
27+
from qiskit.circuit.classical import expr
28+
from qiskit.circuit.controlflow import node_resources
2629
from qiskit.circuit.library.standard_gates import IGate, RZZGate, SwapGate, SXGate, SXdgGate
2730
from qiskit.circuit.tools.pi_check import pi_check
2831

@@ -1344,6 +1347,27 @@ def set_cl_multibox(self, condition, top_connect="┴"):
13441347
Returns:
13451348
List: list of tuples of connections between clbits for multi-bit conditions
13461349
"""
1350+
if isinstance(condition, expr.Expr):
1351+
# If fixing this, please update the docstrings of `QuantumCircuit.draw` and
1352+
# `visualization.circuit_drawer` to remove warnings.
1353+
label = "<expression>"
1354+
out = []
1355+
condition_bits = node_resources(condition).clbits
1356+
registers = collections.defaultdict(list)
1357+
for bit in condition_bits:
1358+
registers[get_bit_register(self._circuit, bit)].append(bit)
1359+
if registerless := registers.pop(None, ()):
1360+
out.extend(self.set_cond_bullets(label, ["1"] * len(registerless), registerless))
1361+
if self.cregbundle:
1362+
# It's hard to do something properly sensible here without more major rewrites, so
1363+
# as a minimum to *not crash* we'll just treat a condition that touches part of a
1364+
# register like it touched the whole register.
1365+
for register in registers:
1366+
self.set_clbit(register[0], BoxOnClWire(label=label, top_connect=top_connect))
1367+
else:
1368+
for register, bits in registers.items():
1369+
out.extend(self.set_cond_bullets(label, ["1"] * len(bits), bits))
1370+
return out
13471371
label, val_bits = get_condition_label_val(condition, self._circuit, self.cregbundle)
13481372
if isinstance(condition[0], ClassicalRegister):
13491373
cond_reg = condition[0]

0 commit comments

Comments
 (0)