-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commuting2qGateGrouper pass #8034
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
base: main
Are you sure you want to change the base?
Conversation
Thank you for opening a new pull request. Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient. While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone. One or more of the the following people are requested to review this:
|
Pull Request Test Coverage Report for Build 2423044318
💛 - Coveralls |
this is fine but at some point we should really do the generic things described here: #5775 Because all these things that we collect can be described by some condition on the gates inspected, to see whether they should be placed in the bucket or not. Here the condition is being a 2-qubit gate and commuting with the block, in #7361 the condition is being a linear gate (cx, swap, etc.), in |
I agree, at least with the general principle. However, that seems to be a bigger project than this one. |
Some preliminary testing from my end. First comment is that this plays nicely with the commuting gate router. For instance: from qiskit.transpiler import PassManager, CouplingMap, Layout
from qiskit.transpiler.passes import CommutationAnalysis
from qiskit.transpiler.passes.routing.swap_strategies import Commuting2qGateGrouper, SwapStrategy
from qiskit.transpiler.passes import FullAncillaAllocation
from qiskit.transpiler.passes import EnlargeWithAncilla
from qiskit.transpiler.passes import ApplyLayout
from qiskit.transpiler.passes import SetLayout
from qiskit.transpiler.passes import Commuting2qGateRouter
from qiskit.circuit.library import TwoLocal
circuit = TwoLocal(5, "ry", "cz", entanglement='full', reps=2).decompose()
circuit.draw('mpl', fold=-1)
swap_strat = SwapStrategy.from_line([0, 1, 2, 3, 4])
backend_cmap = CouplingMap(couplinglist=[(0, 1), (1, 2), (1, 3), (3, 4), (4, 5), (5, 6)])
initial_layout = Layout.from_intlist([0, 1, 3, 4, 5], *circuit.qregs)
passmanager = PassManager(
[
CommutationAnalysis(),
Commuting2qGateGrouper(),
Commuting2qGateRouter(swap_strat),
SetLayout(initial_layout),
FullAncillaAllocation(backend_cmap),
EnlargeWithAncilla(),
ApplyLayout(),
]
)
result = passmanager.run(circuit)
result.draw('mpl', fold=-1) transpiles this into this |
This also looks good: from qiskit import QuantumCircuit
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import CommutationAnalysis
from qiskit.transpiler.passes.routing.swap_strategies import Commuting2qGateGrouper
circ = QuantumCircuit(8)
circ.cz(0, 1)
circ.cz(2, 3)
circ.cz(0, 2)
circ.cz(1, 3)
circ.cz(0, 3)
circ.cz(5, 6)
circ.cz(6, 7)
circ.cz(5, 7)
print(circ.draw("text"))
passmanager = PassManager(
[
CommutationAnalysis(),
Commuting2qGateGrouper(),
]
)
print(passmanager.run(circ).draw("text")) results in
|
…s into Commuting2qBlock.
bf8521e
to
9112225
Compare
The second example posted by @eggerdj highlights that this is not quite grouping the commuting 2-qubit gates (because the top gates and bottom gates also commute). Rather it is grouping 2q gates that commute and act on contiguous qubits. This should be in the docstrings. |
Only slightly bigger. But it simplifies the transpiler quite a lot and prevents a bunch of ad-hoc passes that keep creeping in. I just saw another pass today that groups 2-qubit clifford gates. |
made of commuting two-qubit terms. Here, a swap strategy is specified by the class | ||
:class:`.SwapStrategy`. Swap strategies need to be tailored to the coupling map and, ideally, | ||
the circuit for the best results. | ||
- | |
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.
You need a release note for the new DAGCircuit
method too since that's a new feature on that interface
|
||
|
||
class Commuting2qGateGrouper(TransformationPass): | ||
"""A pass that collapses 2-qubit gates that commute into :class:`.Commuting2qBlock`.""" |
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.
You should update the docstring here to add the clarification that @ajavadia asked for in #8034 (comment)
new_dag = dag.copy_empty_like() | ||
|
||
for nd in dag.layered_topological_op_nodes(): | ||
if nd in self._done_nodes: |
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.
Why is this instance level? Isn't it per dag? What happens if this pass is run twice couldn't this accidentally eval true because of leftover instance state?
else: | ||
commuting_op_set = self._commutes_with(nd) | ||
used_qarg = {qarg for node in commuting_op_set for qarg in node.qargs} | ||
used_carg = {carg for node in commuting_op_set for carg in node.cargs} | ||
new_dag.apply_operation_back( | ||
Commuting2qBlock(commuting_op_set), | ||
[qbit for qbit in dag.qubits if qbit in used_qarg], | ||
[cbit for cbit in dag.clbits if cbit in used_carg], | ||
) |
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'm a bit confused here, what happens if there is a 2q gate that's not in a commuting set? Like if you have a standalone swap on 2 qubits won't this collapse that into a Commuting2qBlock
of a gate? That doesn't seem desirable. For example:
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import PassManager, CouplingMap
from qiskit.transpiler.passes import Commuting2qGateGrouper
circ = QuantumCircuit(4)
circ.cx(0, 1)
circ.cx(1, 2)
cmap = CouplingMap([[0, 1], [1, 2], [1, 3], [3, 4]])
cmap.make_symmetric()
passmanager = PassManager([Commuting2qGateGrouper()])
res = passmanager.run(circ)
print(res)
outputs:
┌─────────────────────┐
q_0: ┤0 ├───────────────────────
│ Commuting 2q gates │┌─────────────────────┐
q_1: ┤1 ├┤0 ├
└─────────────────────┘│ Commuting 2q gates │
q_2: ───────────────────────┤1 ├
└─────────────────────┘
q_3: ──────────────────────────────────────────────
and each group just contains a single cx gate.
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 is a good point. A communitation block with a single gate should not be created. Is that what you say?
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.
Yeah I think that would address it, basically we should leave gates alone if they're not part of a commuting set since wrapping them just to unwrap them adds a bunch of unnecessary overhead.
new_dag = dag.copy_empty_like() | ||
|
||
for nd in dag.layered_topological_op_nodes(): |
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.
Instead of creating a new dag and iteratively adding all the operations to it like this, did you consider do an in place modification using replace_block_with_op
? We do this in consolidate blocks (which seems very similar to this pass): https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/transpiler/passes/optimization/consolidate_blocks.py#L116 I'm not sure which would have better performance but it's worth looking at.
from qiskit.test import QiskitTestCase | ||
|
||
|
||
class TestCommuting2qGateGrouperRouting(QiskitTestCase): |
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 feel like the tests here could be a bit more thorough and targetted to just the new Commuting2qGateGrouper
pass's behavior. Like it'd be good to test what the pass does with an empty dag, dags that have no 2q gates, that commuting gates are correctly grouped, etc. having the full path tests are good but I think we should test things in isolation on the new feature here too.
The pass
Commuting2qGateGrouper
groups the commuting multi-qubit gates intoCommuting2qBlock
.Testing is needed