-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add control flow to CommutativeCancellation pass #9143
add control flow to CommutativeCancellation pass #9143
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
Pull Request Test Coverage Report for Build 5548115012
💛 - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does actually seem much more straightforwards than I was worried about - recursing a PassManager
itself isn't ideal, but it's not horrendous. We'll also need a release note for this at some point as well.
Can we also add some tests that the commutative cancellation will never try to "commute" some gates out of a control-flow scope, or between control-flow scopes? For example, in the circuit
qc = QuantumCircuit(2, 2)
qc.x(1)
with qc.if_test((0, False)):
qc.cx(0, 1)
qc.x(1)
we want to make sure that that's not reduced.
dag = self._handle_control_flow_ops(dag) | ||
|
||
return dag | ||
|
||
def _handle_control_flow_ops(self, dag): | ||
""" | ||
This is similar to transpiler/passes/utils/control_flow.py except that the | ||
commutation analysis is redone for the control flow blocks. | ||
""" | ||
from qiskit.transpiler import PassManager | ||
|
||
for node in dag.op_nodes(ControlFlowOp): | ||
mapped_blocks = [] | ||
for block in node.op.blocks: | ||
pass_manager = PassManager(self.__class__(self.basis)) | ||
new_circ = pass_manager.run(block) | ||
mapped_blocks.append(new_circ) | ||
node.op = node.op.replace_blocks(mapped_blocks) | ||
return dag |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can just build a single PassManager
in the state of self
when required here - the requires
mechanism should then work on each call.
I agree that this is ready to go with a release note. And the additional tests for not crossing a block boundary are not unreasonable. |
In _handle_control_flow_ops, a new PassManager was created for each ControlFlowOp found. This commit moves the creation out of the loop over nodes. This should be a bit more efficient. I also considered mapped_blocks.clear() rather than reallocating. But in some simpler tests of clearing rather than reallocating, this is actually less performant. I think reallocating renders the code slightly more understandable.
These were requested in a review comment. The previous commit was also requested in a review comment.
EDIT: See note below |
Looking closer at this, I don't see how the commutation analysis is run for the blocks in the control flow ops. The tests pass, so I assume it is in fact being run. A slightly confusing circumstance is that in tests (and perhaps other code), the The tests include this line In any case, it seems that the approach used here obviates the need to add explicit support for control flow ops in |
bf7e8e1
to
bd78e5a
Compare
Arg. I somehow merged commits from main in this branch. resetting ... |
bd78e5a
to
b6cdab4
Compare
…locks A new PassManager for CommutativeCancellation is constructed when descending into control flow blocks. This commit explicitly includes a CommutativeAnalysis pass in this construction rather than relying on `requires`. This change, while not necessary, is made for consistency with other explicit constructions of PassManagers containing these passes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this LGTM, the approach of using a nested pass manager is clever to handle running both passes in the nested context. Just a couple of small questions inline.
This is similar to transpiler/passes/utils/control_flow.py except that the | ||
commutation analysis is redone for the control flow blocks. | ||
""" | ||
from qiskit.transpiler import PassManager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a cyclical import issue that requires us to do this at runtime?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can improve this the same way it's done in #10355
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EDIT: reverted the following and committed suggestion above.
Fixed in f8c2aaf.
qiskit/transpiler/passes/optimization/commutative_cancellation.py
Outdated
Show resolved
Hide resolved
A previous commit made a copy of the pass being used in the parent context to be used in the blocks of a control flow op. With this commit, we reuse the existing pass, as it is immutable.
Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LGTM, I think this works as intended and the test coverage that @jakelishman asked for earlier is present now. That being said I left one inline comment, it's not necessarily a blocker and it's something we can experiment with later. But I'm curious your thoughts about it.
commutation analysis is redone for the control flow blocks. | ||
""" | ||
|
||
pass_manager = PassManager([CommutationAnalysis(), self]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was debating asking to move this to an instance scope, something like: self.control_flow_pm
because CommutationAnalysis
maintains a cache of commutation relationships which might be useful between multiple runs for >1 control flow block. But my hesitation is around whether sharing the cache between the blocks is sound or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be safe to reuse the cache between different blocks or even runs, since all it stores are gate identities like "CX(0, 1) commutes with Z(0)"; "CX(0, 1) does not commute with S(0)", etc.. I don't know if sharing this information would lead to a performance improvement, I guess it depends how many blocks there are and how similar these are in terms of gates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move forward here, we can always add this in a quick follow up without too much concern then
Summary
This adds control flow handling to the CommutativeCancellation pass. In this version the body of the control flow instructions are treated independently of the containing circuit such that the commutivity of the control flow instruction itself is not considered. Also, commutivity across the instruction block boundary is left for a future improvement.
Details and comments