diff --git a/qiskit/visualization/text.py b/qiskit/visualization/text.py index 6fa431a1b2ad..7f89bdbaad0d 100644 --- a/qiskit/visualization/text.py +++ b/qiskit/visualization/text.py @@ -867,9 +867,7 @@ def should_compress(self, top_line, bot_line): for top, bot in zip(top_line, bot_line): if top in ["┴", "╨"] and bot in ["┬", "╥"]: return False - for line in (bot_line, top_line): - no_spaces = line.replace(" ", "") - if len(no_spaces) > 0 and all(c.isalpha() or c.isnumeric() for c in no_spaces): + if (top.isalnum() and bot != " ") or (bot.isalnum() and top != " "): return False return True @@ -959,6 +957,8 @@ def merge_lines(top, bot, icod="top"): ret += "╫" elif topc in "║╫╬" and botc in " ": ret += "║" + elif topc in "│┼╪" and botc in " ": + ret += "│" elif topc == "└" and botc == "┌" and icod == "top": ret += "├" elif topc == "┘" and botc == "┐": @@ -1518,8 +1518,10 @@ def connect_with(self, wire_char): wire_char = "║" if index == 0 and len(affected_bits) > 1: affected_bit.connect(wire_char, ["bot"]) - else: + elif index == len(affected_bits) - 1: affected_bit.connect(wire_char, ["top"]) + else: + affected_bit.connect(wire_char, ["bot", "top"]) else: if index == 0: affected_bit.connect(wire_char, ["bot"]) diff --git a/releasenotes/notes/fix-text-drawer-compression-a80a5636957e8eec.yaml b/releasenotes/notes/fix-text-drawer-compression-a80a5636957e8eec.yaml new file mode 100644 index 000000000000..a91cd87f0943 --- /dev/null +++ b/releasenotes/notes/fix-text-drawer-compression-a80a5636957e8eec.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + There were two bugs in the ``text`` circuit drawer that were fixed. + These appeared when ``vertical_compression`` was set to ``medium``, + which is the default. The first would sometimes cause text to overwrite + other text or gates, and the second would sometimes cause the connections + between a gate and its controls to break. + See `#8588 `__. diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py index 1ef9430c9a4d..3f2e4611b173 100644 --- a/test/python/visualization/test_circuit_text_drawer.py +++ b/test/python/visualization/test_circuit_text_drawer.py @@ -1683,7 +1683,7 @@ def test_control_gate_label_with_cond_1_high_cregbundle(self): str(_text_circuit_drawer(circ, vertical_compression="high", cregbundle=True)), expected ) - def test_control_gate_label_with_cond_2_med(self): + def test_control_gate_label_with_cond_2_med_space(self): """Control gate has a label and a conditional (on label, compression=med) See https://github.com/Qiskit/qiskit-terra/issues/4361""" expected = "\n".join( @@ -1692,8 +1692,7 @@ def test_control_gate_label_with_cond_2_med(self): "q_0: |0>┤ my h ├", " └──┬───┘", "q_1: |0>───■────", - " my ch ", - " ║ ", + " my║ch ", " c: 0 ═══■════", " 0x1 ", ] @@ -1708,6 +1707,31 @@ def test_control_gate_label_with_cond_2_med(self): self.assertEqual(str(_text_circuit_drawer(circ, vertical_compression="medium")), expected) + def test_control_gate_label_with_cond_2_med(self): + """Control gate has a label and a conditional (on label, compression=med) + See https://github.com/Qiskit/qiskit-terra/issues/4361""" + expected = "\n".join( + [ + " ┌──────┐ ", + "q_0: |0>──┤ my h ├─", + " └──┬───┘ ", + "q_1: |0>─────■─────", + " my ctrl-h ", + " ║ ", + " c: 0 ═════■═════", + " 0x1 ", + ] + ) + + qr = QuantumRegister(2, "q") + cr = ClassicalRegister(1, "c") + circ = QuantumCircuit(qr, cr) + hgate = HGate(label="my h") + controlh = hgate.control(label="my ctrl-h").c_if(cr, 1) + circ.append(controlh, [1, 0]) + + self.assertEqual(str(_text_circuit_drawer(circ, vertical_compression="medium")), expected) + def test_control_gate_label_with_cond_2_med_cregbundle(self): """Control gate has a label and a conditional (on label, compression=med) with cregbundle See https://github.com/Qiskit/qiskit-terra/issues/4361""" @@ -2046,7 +2070,6 @@ def test_text_conditional_1(self): " └─╥─┘└─╥─┘", "c0: 0 ══■════╬══", " 0x1 ║ ", - " ║ ", "c1: 0 ═══════■══", " 0x1 ", ] @@ -2148,6 +2171,98 @@ def test_text_measure_with_spaces_bundle(self): expected, ) + def test_text_barrier_med_compress_1(self): + """Medium vertical compression avoids connection break.""" + circuit = QuantumCircuit(4) + circuit.cx(1, 3) + circuit.x(1) + circuit.barrier((2, 3), label="Bar 1") + + expected = "\n".join( + [ + " ", + "q_0: |0>────────────", + " ┌───┐ ", + "q_1: |0>──■───┤ X ├─", + " │ └───┘ ", + " │ Bar 1 ", + "q_2: |0>──┼─────░───", + " ┌─┴─┐ ░ ", + "q_3: |0>┤ X ├───░───", + " └───┘ ░ ", + ] + ) + + self.assertEqual( + str(_text_circuit_drawer(circuit, vertical_compression="medium", cregbundle=False)), + expected, + ) + + def test_text_barrier_med_compress_2(self): + """Medium vertical compression avoids overprint.""" + circuit = QuantumCircuit(4) + circuit.barrier((0, 1, 2), label="a") + circuit.cx(1, 3) + circuit.x(1) + circuit.barrier((2, 3), label="Bar 1") + + expected = "\n".join( + [ + " a ", + "q_0: |0>─░─────────────", + " ░ ┌───┐ ", + "q_1: |0>─░───■───┤ X ├─", + " ░ │ └───┘ ", + " ░ │ Bar 1 ", + "q_2: |0>─░───┼─────░───", + " ░ ┌─┴─┐ ░ ", + "q_3: |0>───┤ X ├───░───", + " └───┘ ░ ", + ] + ) + + self.assertEqual( + str(_text_circuit_drawer(circuit, vertical_compression="medium", cregbundle=False)), + expected, + ) + + def test_text_barrier_med_compress_3(self): + """Medium vertical compression avoids conditional connection break.""" + qr = QuantumRegister(1, "qr") + qc1 = ClassicalRegister(3, "cr") + qc2 = ClassicalRegister(1, "cr2") + circuit = QuantumCircuit(qr, qc1, qc2) + circuit.x(0).c_if(qc1, 3) + circuit.x(0).c_if(qc2[0], 1) + + expected = "\n".join( + [ + " ┌───┐┌───┐", + " qr: |0>┤ X ├┤ X ├", + " └─╥─┘└─╥─┘", + "cr_0: 0 ══■════╬══", + " ║ ║ ", + "cr_2: 0 ══o════╬══", + " ║ ║ ", + " cr2: 0 ══╬════■══", + " ║ ", + "cr_1: 0 ══■═══════", + " 0x3 ", + ] + ) + + self.assertEqual( + str( + _text_circuit_drawer( + circuit, + vertical_compression="medium", + wire_order=[0, 1, 3, 4, 2], + cregbundle=False, + ) + ), + expected, + ) + class TestTextConditional(QiskitTestCase): """Gates with conditionals"""