Skip to content

Conversation

enavarro51
Copy link
Contributor

@enavarro51 enavarro51 commented Jul 11, 2023

Summary

Displays the circuits inside ControlFlowOps instead of just displaying a box.

Details and comments

This PR adds displays of internal circuits for ControlFlowOps to the circuit text drawer. This is similar to the capability in the mpl drawer in #10207. The main differences are,

  • In order to not recreate the wheel, the basic structure of the text drawer was maintained. Doing this meant it would have been difficult to draw complete boxes around the circuits and have them wrap onto multiple lines in the way they were done in the mpl drawer. Instead the flow circuits are bracketed at the beginning and end. See examples below.
  • When nesting control flow ops inside others, the mpl drawer used colors to indicate where the nesting was taking place. For the text drawer, a nesting number was used. So If-1, End-1 would appear inside If-0, End-0.

Otherwise all the previous functionality of the text drawer is intact. This PR incorporates the changes to utils.py that were made in #10207.

  • Add tests for text drawer
  • Release note
qr = QuantumRegister(5, "q")
cr = ClassicalRegister(3, "cr")
qc = QuantumCircuit(qr, cr)

qc.h(0)
qc.h(1)

with qc.if_test((cr, 1)):
    qc.y(1)
    qc.x(2)
    qc.z(3)

      ┌───┐                       
 q_0: ┤ H ├───────────────────────
      ├───┤┌─────  ┌───┐  ──────┐ 
 q_1: ┤ H ├┤       ┤ Y ├        ├─
      └───┘│       ├───┤        │ 
 q_2: ─────┤ If-0  ┤ X ├  End-0 ├─
           │       ├───┤        │ 
 q_3: ─────┤       ┤ Z ├        ├─
           └──╥──  └───┘  ──────┘ 
 q_4: ────────╫───────────────────
              ║                   
cr_0: ════════■═══════════════════
              ║                   
cr_1: ════════o═══════════════════
              ║                   
cr_2: ════════o═══════════════════
             0x1                  
qr = QuantumRegister(4, "q")
cr = ClassicalRegister(3, "cr")
qc = QuantumCircuit(qr, cr)

a = Parameter("a")
qc.h(0)
qc.measure(0, 2)
with qc.for_loop((2, 4, 8, 16), loop_parameter=a):
    qc.h(0)
    qc.cx(0, 1)
    qc.rx(pi / a, 1)
    qc.measure(0, 0)
    with qc.if_test((cr[2], 1)):
        qc.z(0)
qc.draw("text")

      ┌───┐┌─┐┌────────────────────  ┌───┐                ┌─┐┌────── ┌───┐»
 q_0: ┤ H ├┤M├┤                      ┤ H ├──■─────────────┤M├┤ If-1  ┤ Z ├»
      └───┘└╥┘│ For-0 (2, 4, 8, 16)  └───┘┌─┴─┐┌─────────┐└╥┘└──╥─── └───┘»
 q_1: ──────╫─┤                      ─────┤ X ├┤ Rx(π/a) ├─╫────╫─────────»
            ║ └────────────────────       └───┘└─────────┘ ║    ║         »
 q_2: ──────╫──────────────────────────────────────────────╫────╫─────────»
            ║                                              ║    ║         »
 q_3: ──────╫──────────────────────────────────────────────╫────╫─────────»
            ║                                              ║    ║         »
cr_0: ══════╬══════════════════════════════════════════════╩════╬═════════»
            ║                                                   ║         »
cr_1: ══════╬═══════════════════════════════════════════════════╬═════════»
            ║                                                   ║         »
cr_2: ══════╩═══════════════════════════════════════════════════■═════════»
                                                                          »
«       ───────┐   ──────┐ 
« q_0:   End-1 ├─        ├─
«       ───────┘   End-0 │ 
« q_1: ──────────        ├─
«                  ──────┘ 
« q_2: ────────────────────
«                          
« q_3: ────────────────────
«                          
«cr_0: ════════════════════
«                          
«cr_1: ════════════════════
«                          
«cr_2: ════════════════════
«                          

@enavarro51 enavarro51 requested review from a team and nonhermitian as code owners July 11, 2023 16:22
@qiskit-bot
Copy link
Collaborator

One or more of the the following people are requested to review this:

@mtreinish mtreinish added Changelog: New Feature Include in the "Added" section of the changelog mod: visualization qiskit.visualization labels Jul 11, 2023
@mtreinish mtreinish added this to the 0.45.0 milestone Jul 11, 2023
@coveralls
Copy link

coveralls commented Jul 11, 2023

Pull Request Test Coverage Report for Build 5860928068

  • 214 of 221 (96.83%) changed or added relevant lines in 1 file are covered.
  • 14 unchanged lines in 2 files lost coverage.
  • Overall coverage increased (+0.01%) to 87.266%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/visualization/circuit/text.py 214 221 96.83%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/lex.rs 2 90.63%
crates/qasm2/src/parse.rs 12 96.67%
Totals Coverage Status
Change from base Build 5860553430: 0.01%
Covered Lines: 74442
Relevant Lines: 85305

💛 - Coveralls

@enavarro51
Copy link
Contributor Author

@jakelishman I updated the text drawer to deal with getting the circuits from node.op.blocks and proper usage of the inner_wire_map so it works as the mpl drawer does. cc8b7b7

@enavarro51
Copy link
Contributor Author

Update: The #10601 issue for the mpl drawer has now been fixed for the text drawer as well. An additional test was added.

@enavarro51
Copy link
Contributor Author

@jakelishman This latest update incorporates the changes to the mpl drawer in #10602. This included expanding the use of the wire_map and refactoring the conditional section.

Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm super super excited to have this in, thanks so much for working on it!

We spoke about this offline, but for posterity for others: I noticed this edge case produces unexpected output for me - an empty control-flow op doesn't print at all, but I'd still expect to see the brackets appear.

from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister

cr = ClassicalRegister(2)
qc = QuantumCircuit(QuantumRegister(2), cr)
with qc.if_test((cr, 0)):
   pass
qc.draw()
q3_0: ─

q3_1: ─

c6: 2/═

We then realised that this is because an empty control-flow block acts on zero qubits, so the problem is part of a larger issue around visualisation of zero-qubit instructions (#9962) and there's no action needed in this PR.

Comment on lines +43 to +47
# Indicators for left, middle, and right of control flow gates
CF_LEFT = 0
CF_MID = 1
CF_RIGHT = 2

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python's got a standard-library type enum.Enum for things like this in general, but it's probably not worth bothering changing here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted.

class FlowOnQuWire(DrawElement):
"""Draws a box for a ControlFlowOp using a single qubit."""

def __init__(self, label="", top_connect="─", conditional=False, section=CF_LEFT):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor style: here and in the other new classes, it feels a bit weird to have section be optional; I don't think there's any default value that makes sense and the outer context should always known whether it's left/mid/right.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 9b82cd7.

Comment on lines +330 to +337
if section == CF_RIGHT:
self.top_format = " ─%s─┐"
self.mid_format = " %s ├"
self.bot_format = " ─%s─┘"
else:
self.top_format = "┌─%s─ "
self.mid_format = "┤ %s "
self.bot_format = "└─%s─ "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: how do you think it'd look if we had "mid" boxes display neither left nor right edge, so they appear more like a full continuation? At the moment it looks a shade unbalanced to me that we get more opening "brackets" than closing ones for if/else and switch constructs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After several tries, decided this is fraught with lots of formatting issues, so left it with left edges.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine by me - it's all a bit of bike-shedding anyway.

Comment on lines 790 to 791
# In case there is an exception, this prevents it from printing twice.
self._single_string = " "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tbh I'd probably skip this; these exceptions aren't actionable by a user, and an exception here would always indicate either an invalid circuit or a problem in our code, and we don't need to be too worried about optimising the return speed in those cases. I'm worried that setting to the empty string implies there's some meaning to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 9b82cd7.

Comment on lines 320 to +346
"""Test the wire_order option"""
expected = "\n".join(
[
" ",
"q_2: |0>────────────",
" ┌───┐ ",
"q_1: |0>┤ X ├───────",
" ├───┤ ┌───┐ ",
"q_3: |0>┤ H ├─┤ X ├─",
" ├───┤ └─╥─┘ ",
"q_0: |0>┤ H ├───╫───",
" └───┘┌──╨──┐",
" c: 0 4/═════╡ 0xa ╞",
" └─────┘",
"ca: 0 2/════════════",
" ",
" ",
"q_2: |0>──────────",
" ┌───┐ ",
"q_1: |0>┤ X ├─────",
" ├───┤┌───┐",
"q_3: |0>┤ H ├┤ X ├",
" ├───┤└─╥─┘",
"q_0: |0>┤ H ├──╫──",
" └───┘ ║ ",
" c_2: 0 ═══════o══",
" ║ ",
"ca_0: 0 ═══════╬══",
" ║ ",
"ca_1: 0 ═══════╬══",
" ║ ",
" c_1: 0 ═══════■══",
" ║ ",
" c_0: 0 ═══════o══",
" ║ ",
" c_3: 0 ═══════■══",
" 0xa ",
]
)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test of wire_order looks unrelated; how come it needs changing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I think I had an early problem with wire_order and cregbundle off, but that went away. I'll change it back.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think there is a fundamental problem with wire_order changes to clbits and cregbundle on. How do you display the order if you have 3 qubits, and two 2-clbit registers with a wire order of [0, 2, 1, 6, 3, 4, 5]? I noticed that the tests for wire_order that are done in test_circuit_visualization only reorder the qubits. The reason this is an issue now is that with the control flow changes, the wire_map is used much more to access bit position than for example, self.qubits or self.clbits.

My suggestion would be to force cregbundle off if clbits are reordered.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually (actually) I just realized this is that crazy problem with the fact that the text drawer tests unlike the other drawers bypass the call to circuit_drawer which is where all the checks to force cregbundle false are done.

The reality is that this is a bad test, since cregbundle would normally be forced off by circuit_drawer before the call to the text drawer. So I say, either ditch the test or do what I did here, which is explicitly make cregbundle false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So left this with cregbundle false which is the only way it should be tested. To avoid problems in the future, I think it's worthwhile to switch to using circuit_drawer for text_drawer testing.

Copy link
Member

@jakelishman jakelishman Aug 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, 100% agree. Let's make that change in a separate PR with no feature changes, though, since it's a large-scale change to the testing structure.

Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great - I'm super keen to get this merged and in so we can start using it, if you're also happy with it.

Comment on lines -1060 to +1064
def controlled_wires(node, layer):
def controlled_wires(node, wire_map):
"""
Analyzes the node in the layer and checks if the controlled arguments are in
the box or out of the box.

Args:
node (DAGNode): node to analyse
layer (Layer): The layer in which the node is inserted.
wire_map (dict): map of qubits/clbits to position
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you managed to get this working? I'm pleased to see this - it looks generally more robust with the new idea of scopes.

@jakelishman jakelishman added this pull request to the merge queue Aug 29, 2023
Merged via the queue into Qiskit:main with commit 0a0e262 Aug 29, 2023
SameerD-phys pushed a commit to SameerD-phys/qiskit-terra that referenced this pull request Sep 7, 2023
…iskit#10414)

* Update text.py

* Update utils.py

* Fix test using text

* Lint

* Lint

* Fix if with body bug and first tests

* Finish tests

* Reno

* Output=text

* Cleanup

* Fix repr and repr_html issue with jupyter

* Cache _single_string to fix __repr__ and _repr_html__ issue

* Get circuits from blocks and fix inner_wire_map

* Fix gate on qubit issue and add test

* Lint

* First work on transpiled nested

* Fix condition for flow ops

* Intermediate flow_wire_map testing

* Fix clbit bug

* Finish transpiled bits bug

* Final fix for conditions and transpiles

* Fix review issues 1

* Revert wire_order test

* Fix control gates with wire_map and test changes

* Further case and else

* Fix review items 2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog mod: visualization qiskit.visualization
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants