Skip to content

Conversation

1ucian0
Copy link
Member

@1ucian0 1ucian0 commented May 9, 2022

The pass Commuting2qGateGrouper groups the commuting multi-qubit gates into Commuting2qBlock.

from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import CommutationAnalysis
from qiskit.transpiler.passes.routing import Commuting2qGateGrouper

from qiskit.circuit.library import TwoLocal

circuit = TwoLocal(5, "ry", "cz", entanglement='pairwise').decompose()
print(circuit.draw('text', fold=-1))

passmanager = PassManager()
passmanager.append([Commuting2qGateGrouper()])
result = passmanager.run(circuit)
print(result.draw('text', fold=-1))
     ┌──────────┐   ┌──────────┐               ┌───────────┐                ┌───────────┐             
q_0: ┤ Ry(θ[0]) ├─■─┤ Ry(θ[5]) ├─────────────■─┤ Ry(θ[10]) ├──────────────■─┤ Ry(θ[15]) ├─────────────
     ├──────────┤ │ └──────────┘┌──────────┐ │ └───────────┘┌───────────┐ │ └───────────┘┌───────────┐
q_1: ┤ Ry(θ[1]) ├─■──────■──────┤ Ry(θ[6]) ├─■───────■──────┤ Ry(θ[11]) ├─■───────■──────┤ Ry(θ[16]) ├
     ├──────────┤        │      ├──────────┤         │      ├───────────┤         │      ├───────────┤
q_2: ┤ Ry(θ[2]) ├─■──────■──────┤ Ry(θ[7]) ├─■───────■──────┤ Ry(θ[12]) ├─■───────■──────┤ Ry(θ[17]) ├
     ├──────────┤ │             ├──────────┤ │              ├───────────┤ │              ├───────────┤
q_3: ┤ Ry(θ[3]) ├─■──────■──────┤ Ry(θ[8]) ├─■───────■──────┤ Ry(θ[13]) ├─■───────■──────┤ Ry(θ[18]) ├
     ├──────────┤        │      ├──────────┤         │      ├───────────┤         │      ├───────────┤
q_4: ┤ Ry(θ[4]) ├────────■──────┤ Ry(θ[9]) ├─────────■──────┤ Ry(θ[14]) ├─────────■──────┤ Ry(θ[19]) ├
     └──────────┘               └──────────┘                └───────────┘                └───────────┘
     ┌──────────┐┌─────────────────────┐┌──────────┐┌─────────────────────┐┌───────────┐┌─────────────────────┐┌───────────┐
q_0: ┤ Ry(θ[0]) ├┤0                    ├┤ Ry(θ[5]) ├┤0                    ├┤ Ry(θ[10]) ├┤0                    ├┤ Ry(θ[15]) ├
     ├──────────┤│                     │├──────────┤│                     │├───────────┤│                     │├───────────┤
q_1: ┤ Ry(θ[1]) ├┤1                    ├┤ Ry(θ[6]) ├┤1                    ├┤ Ry(θ[11]) ├┤1                    ├┤ Ry(θ[16]) ├
     ├──────────┤│                     │├──────────┤│                     │├───────────┤│                     │├───────────┤
q_2: ┤ Ry(θ[2]) ├┤2 Commuting 2q gates ├┤ Ry(θ[7]) ├┤2 Commuting 2q gates ├┤ Ry(θ[12]) ├┤2 Commuting 2q gates ├┤ Ry(θ[17]) ├
     ├──────────┤│                     │├──────────┤│                     │├───────────┤│                     │├───────────┤
q_3: ┤ Ry(θ[3]) ├┤3                    ├┤ Ry(θ[8]) ├┤3                    ├┤ Ry(θ[13]) ├┤3                    ├┤ Ry(θ[18]) ├
     ├──────────┤│                     │├──────────┤│                     │├───────────┤│                     │├───────────┤
q_4: ┤ Ry(θ[4]) ├┤4                    ├┤ Ry(θ[9]) ├┤4                    ├┤ Ry(θ[14]) ├┤4                    ├┤ Ry(θ[19]) ├
     └──────────┘└─────────────────────┘└──────────┘└─────────────────────┘└───────────┘└─────────────────────┘└───────────┘

Testing is needed

@qiskit-bot
Copy link
Collaborator

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:

  • @Qiskit/terra-core

@coveralls
Copy link

coveralls commented May 9, 2022

Pull Request Test Coverage Report for Build 2423044318

  • 76 of 77 (98.7%) changed or added relevant lines in 5 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.02%) to 84.408%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_grouper.py 66 67 98.51%
Totals Coverage Status
Change from base Build 2409841148: 0.02%
Covered Lines: 54635
Relevant Lines: 64727

💛 - Coveralls

@ajavadia
Copy link
Member

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 Collect2qBlocks the condition is being a 2-qubit gate. Soon we will need to collect all cliffords. etc.

@1ucian0
Copy link
Member Author

1ucian0 commented May 10, 2022

we should really do the generic things described here: #5775

I agree, at least with the general principle. However, that seems to be a bigger project than this one.

@eggerdj
Copy link
Contributor

eggerdj commented May 25, 2022

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

image

into this

image

@eggerdj
Copy link
Contributor

eggerdj commented May 25, 2022

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

q_0: ─■──■─────■─
      │  │     │ 
q_1: ─■──┼──■──┼─
         │  │  │ 
q_2: ─■──■──┼──┼─
      │     │  │ 
q_3: ─■─────■──■─
                 
q_4: ────────────
                 
q_5: ─■─────■────
      │     │    
q_6: ─■──■──┼────
         │  │    
q_7: ────■──■────
                 
     ┌─────────────────────┐
q_0: ┤0                    ├
     │                     │
q_1: ┤1                    ├
     │  Commuting 2q gates │
q_2: ┤2                    ├
     │                     │
q_3: ┤3                    ├
     └─────────────────────┘
q_4: ───────────────────────
     ┌─────────────────────┐
q_5: ┤0                    ├
     │                     │
q_6: ┤1 Commuting 2q gates ├
     │                     │
q_7: ┤2                    ├
     └─────────────────────┘

@1ucian0 1ucian0 added this to the 0.21 milestone May 28, 2022
@1ucian0 1ucian0 marked this pull request as ready for review May 28, 2022 20:06
@1ucian0 1ucian0 requested a review from a team as a code owner May 28, 2022 20:06
@1ucian0 1ucian0 force-pushed the commutative_2q_analysis branch from bf8521e to 9112225 Compare June 1, 2022 16:38
@ShellyGarion ShellyGarion modified the milestone: 0.21 Jun 14, 2022
@mtreinish mtreinish self-assigned this Jun 14, 2022
@ajavadia
Copy link
Member

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.

@ajavadia
Copy link
Member

I agree, at least with the general principle. However, that seems to be a bigger project than this one.

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.
- |
Copy link
Member

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`."""
Copy link
Member

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:
Copy link
Member

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?

Comment on lines +56 to +64
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],
)
Copy link
Member

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.

Copy link
Member Author

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?

Copy link
Member

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.

Comment on lines +49 to +51
new_dag = dag.copy_empty_like()

for nd in dag.layered_topological_op_nodes():
Copy link
Member

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):
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants