Skip to content

Conversation

mtreinish
Copy link
Member

@mtreinish mtreinish commented Aug 10, 2023

Summary

This commit fixes a bug in the mpl visualization of control flow blocks. It was assuming the qubit order of the control flow block matched the qubit order in the outer circuit. This could result in the output visualization putting the gates from a control flow block on the incorrect qubits. This commit corrects this oversight by mapping the input qargs to the control flow op to the inner qubits in the block circuit. This results in placing the gates on the correct wires in the output visualization regardless of where the qubits in the circuit are.

Details and comments

Fixes #10601

Co-authored-by: Edwin Navarro enavarro@comcast.net

This commit fixes a bug in the mpl visualization of control flow blocks.
It was assuming the qubit order of the control flow block matched the
qubit order in the outer circuit. This could result in the output
visualization putting the gates from a control flow block on the
incorrect qubits. This commit corrects this oversight by mapping the
input qargs to the control flow op to the inner qubits in the block
circuit. This results in placing the gates on the correct wires in the
output visualization regardless of where the qubits in the circuit are.

Fixes Qiskit#10601
@mtreinish mtreinish added the Changelog: Bugfix Include in the "Fixed" section of the changelog label Aug 10, 2023
@mtreinish mtreinish added this to the 0.25.1 milestone Aug 10, 2023
@mtreinish mtreinish requested review from nonhermitian and a team as code owners August 10, 2023 13:43
@qiskit-bot
Copy link
Collaborator

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

@mtreinish mtreinish changed the title Fix gate placement inner gates for control flow blocks Fix inner gate placement for control flow blocks Aug 10, 2023
@coveralls
Copy link

coveralls commented Aug 10, 2023

Pull Request Test Coverage Report for Build 5894601842

  • 1 of 10 (10.0%) changed or added relevant lines in 1 file are covered.
  • 18 unchanged lines in 4 files lost coverage.
  • Overall coverage decreased (-0.02%) to 87.246%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/visualization/circuit/matplotlib.py 1 10 10.0%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/expr.rs 1 93.76%
qiskit/visualization/circuit/matplotlib.py 2 49.61%
crates/qasm2/src/lex.rs 3 91.14%
crates/qasm2/src/parse.rs 12 96.2%
Totals Coverage Status
Change from base Build 5893570960: -0.02%
Covered Lines: 74349
Relevant Lines: 85218

💛 - Coveralls

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 right to me, but can you add a visual test for it that would catch it from happening again?

It'd be worth including a nested conditional as well, just to check that this binds through two layers of the wire map.

@jakelishman
Copy link
Member

also needs a reno

@mtreinish mtreinish added the stable backport potential The bug might be minimal and/or import enough to be port to stable label Aug 15, 2023
Copy link
Contributor

@enavarro51 enavarro51 left a comment

Choose a reason for hiding this comment

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

I think this might be a deeper problem. This circuit

qreg = QuantumRegister(2, "qr")
creg = ClassicalRegister(2, "cr")
qc = QuantumCircuit(qreg, creg)

qc.h([0, 1])
qc.h([0, 1])
qc.h([0, 1])
with qc.if_test((creg, 1)):
    qc.x(0)
    with qc.if_test((creg, 2)):
        qc.z(0)

backend = FakeBelemV2()
backend.target.add_instruction(IfElseOp, name="if_else")

transpile(qc, backend, optimization_level=2, seed_transpiler=671_42).draw('mpl')

gives this error,

~/qiskit/qiskit-terra/qiskit/visualization/circuit/matplotlib.py in <dictcomp>(.0)
    513                         flow_wire_map = {
--> 514                             inner: wire_map[outer]
    515                             for outer, inner in zip(node.qargs, circuit.qubits)
    516                             if inner not in wire_map

KeyError: Qubit(QuantumRegister(2, 'qr'), 0)

The wire map at this point is

{Qubit(QuantumRegister(5, 'q'), 0): 0, Qubit(QuantumRegister(5, 'q'), 1): 1, Qubit(QuantumRegister(5, 'q'), 2): 2, Qubit(QuantumRegister(5, 'q'), 3): 3, Qubit(QuantumRegister(5, 'q'), 4): 4, Clbit(ClassicalRegister(2, 'cr'), 0): 5, Clbit(ClassicalRegister(2, 'cr'), 1): 6}

@mtreinish
Copy link
Member Author

Hmm, yeah it looks like the recursive mapping isn't working with nested control flow. I'll take a look and see if I can figure out what's going on.

@mtreinish mtreinish modified the milestones: 0.25.1, 0.25.2 Aug 17, 2023
@enavarro51
Copy link
Contributor

Not sure why the fold_with_conditions is failing. It works on my local with these changes. Maybe reference needs to be updated??

@mtreinish
Copy link
Member Author

Yeah, I think part of it is just register naming changing now because there are more tests being executed and the implicit naming goes from something like q3 to q17. This is a general issue in the visual tests as they're sensitive to execution order since they don't set names on the registers. But I'll just update the references for this PR and in a separate one we can fix the test suite.

@enavarro51
Copy link
Contributor

Ah, yes. Implicit naming has caught me a few times.

Comment on lines 524 to +527
if not flow_wire_map:
flow_wire_map = wire_map
else:
flow_wire_map.update(wire_map)
Copy link
Contributor

@enavarro51 enavarro51 Aug 21, 2023

Choose a reason for hiding this comment

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

Suggested change
if not flow_wire_map:
flow_wire_map = wire_map
else:
flow_wire_map.update(wire_map)
flow_wire_map.update(wire_map)

Not really necessary to test.

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'm not sure why this update is necessary; when entering an inner scope, the only bits that I think should be accessible are the ones defined by the bit-binding relations in lines 511 to 523.

Copy link
Contributor

Choose a reason for hiding this comment

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

In this circuit, the qc2 circuit can use either existing qr, cr or new qr2, cr2. If for example cr is used, the inner map will not contain this since it's already in the wire_map. This means without the combination above, it won't be available for the condition code.

qr = QuantumRegister(3, "q")
cr = ClassicalRegister(2, "cr")
qc = QuantumCircuit(qr, cr)
qc.h(0)

qr2 = QuantumRegister(3, "qr2")
cr2 = ClassicalRegister(2, "cr2")
qc2 = QuantumCircuit(qr, cr) # Can be (qr2, cr2)
qc2.x(1).c_if(cr[0], 1) # If cr2, change to cr2

with qc.if_test((cr[0], 1)):
    qc.x(0)
    qc.if_else((cr[1], 1), qc2, None, [0, 1, 2], [0, 1])
qc.y(0).c_if(cr[0], 0)
qc.draw("mpl", style={"showindex": True})

Copy link
Member

Choose a reason for hiding this comment

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

If you make the changes suggested in your comments there, you'd have made an invalid circuit; it's not permitted to use a register condition that's not defined in the outermost scoped circuit. It's an unfortunate restriction and a gotcha, but it's required at the moment because we don't have any way of defining scoped storage locations for hardware, nor any way of defining a "binding" of registers from an outer circuit to an inner scope in the same way that we do for bits.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, OK. It doesn't raise an error, so I assumed it was acceptable and the inner circuit qubits/clbits would just get mapped to the outer ones.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, there's no error checking of it; I guess we'd have to put a special case into QuantumCircuit.append to do that (it's not enough to put it in if_test / if_else because append is a valid entry point too, but _append's contract is that the user is explicitly responsible for all error checking). Maybe that would actually be a good idea - it shouldn't be too expensive, and it would help prevent common issues.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I'd missed that we still have the if inner not in wire_map line in both dictionary comprehensions - my comment goes along with removing that restriction; the inner scopes should be exactly the bits in the zip(node.qargs, block.qubits) (and clbits) regardless of the inner one is already in the outer map or not.

Copy link
Contributor

@enavarro51 enavarro51 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 good to me with the 1 change. The nested test looks good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: Bugfix Include in the "Fixed" section of the changelog stable backport potential The bug might be minimal and/or import enough to be port to stable
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Control flow mpl visualization not respecting layout
5 participants