Skip to content

Commit 2b31d49

Browse files
committed
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.
1 parent f01b7ab commit 2b31d49

File tree

4 files changed

+83
-38
lines changed

4 files changed

+83
-38
lines changed

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/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

+49-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,65 @@ 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+
warn("runtime-expression conditions are not properly supported yet")
1111+
condition_bits = condition_resources(condition).clbits
1112+
label = "[expression]"
1113+
override_fc = True
1114+
registers = collections.defaultdict(list)
1115+
for bit in condition_bits:
1116+
registers[get_bit_register(self._circuit, bit)].append(bit)
1117+
# Registerless bits don't care whether cregbundle is set.
1118+
cond_pos.extend(cond_xy[wire_map[bit] - first_clbit] for bit in registers.pop(None, ()))
1119+
if self._cregbundle:
1120+
cond_pos.extend(
1121+
cond_xy[wire_map[register[0]] - first_clbit] for register in registers
1122+
)
11171123
else:
1118-
cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit])
1124+
cond_pos.extend(
1125+
cond_xy[wire_map[bit] - first_clbit]
1126+
for register, bits in registers.items()
1127+
for bit in bits
1128+
)
1129+
val_bits = ["1"] * len(cond_pos)
11191130
else:
1120-
cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit])
1131+
label, val_bits = get_condition_label_val(condition, self._circuit, self._cregbundle)
1132+
cond_bit_reg = condition[0]
1133+
cond_bit_val = int(condition[1])
1134+
override_fc = cond_bit_val != 0
1135+
1136+
# In the first case, multiple bits are indicated on the drawing. In all
1137+
# other cases, only one bit is shown.
1138+
if not self._cregbundle and isinstance(cond_bit_reg, ClassicalRegister):
1139+
for idx in range(cond_bit_reg.size):
1140+
cond_pos.append(cond_xy[wire_map[cond_bit_reg[idx]] - first_clbit])
1141+
1142+
# If it's a register bit and cregbundle, need to use the register to find the location
1143+
elif self._cregbundle and isinstance(cond_bit_reg, Clbit):
1144+
register = get_bit_register(self._circuit, cond_bit_reg)
1145+
if register is not None:
1146+
cond_pos.append(cond_xy[wire_map[register] - first_clbit])
1147+
else:
1148+
cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit])
1149+
else:
1150+
cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit])
11211151

11221152
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"]
1153+
for val_bit, xy in zip(val_bits, cond_pos):
1154+
fc = self._style["lc"] if override_fc or val_bit == "1" else self._style["bg"]
11321155
box = glob_data["patches_mod"].Circle(
11331156
xy=xy,
11341157
radius=WID * 0.15,

qiskit/visualization/circuit/text.py

+23
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,26 @@ 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+
warn("runtime-expression conditions are not properly supported yet")
1352+
label = "<expression>"
1353+
out = []
1354+
condition_bits = node_resources(condition).clbits
1355+
registers = collections.defaultdict(list)
1356+
for bit in condition_bits:
1357+
registers[get_bit_register(self._circuit, bit)].append(bit)
1358+
if registerless := registers.pop(None, ()):
1359+
out.extend(self.set_cond_bullets(label, ["1"] * len(registerless), registerless))
1360+
if self.cregbundle:
1361+
# It's hard to do something properly sensible here without more major rewrites, so
1362+
# as a minimum to *not crash* we'll just treat a condition that touches part of a
1363+
# register like it touched the whole register.
1364+
for register in registers:
1365+
self.set_clbit(register[0], BoxOnClWire(label=label, top_connect=top_connect))
1366+
else:
1367+
for register, bits in registers.items():
1368+
out.extend(self.set_cond_bullets(label, ["1"] * len(bits), bits))
1369+
return out
13471370
label, val_bits = get_condition_label_val(condition, self._circuit, self.cregbundle)
13481371
if isinstance(condition[0], ClassicalRegister):
13491372
cond_reg = condition[0]

0 commit comments

Comments
 (0)