Skip to content

Commit

Permalink
Follow-up on routing commuting 2q gates (#7979)
Browse files Browse the repository at this point in the history
* * Renamed directory.

* * Typo.

* * Commuting2qBlocks name

* * logical -> virtual.

* * from_line in the example.

* * Reno name fix.

* * Reno rewording.

* * removed unnecessary line in example.

* * Black.

* * Started designing test on non-line graph.

* * Added test on a T device.

* * Renamed Communting2qBlocks to Commuting2qBlock

* * Made test robust to arbitraryness of commuting gate order.
* Added test.

* * Added test and black.

* * Fix missing gate test exception refactor.

* * Bug fix with test.

* * Removed SwapStrategy from the passes init.

* * black
  • Loading branch information
eggerdj authored May 11, 2022
1 parent 7726d47 commit c8ad6cf
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 39 deletions.
2 changes: 0 additions & 2 deletions qiskit/transpiler/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
StochasticSwap
SabreSwap
BIPMapping
SwapStrategy
Commuting2qGateRouter
Basis Change
Expand Down Expand Up @@ -190,7 +189,6 @@
from .routing import StochasticSwap
from .routing import SabreSwap
from .routing import BIPMapping
from .routing import SwapStrategy
from .routing import Commuting2qGateRouter

# basis change
Expand Down
4 changes: 2 additions & 2 deletions qiskit/transpiler/passes/routing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@
from .stochastic_swap import StochasticSwap
from .sabre_swap import SabreSwap
from .bip_mapping import BIPMapping
from .swap_strategies.commuting_2q_gate_router import Commuting2qGateRouter
from .swap_strategies.swap_strategy import SwapStrategy
from .commuting_2q_gate_routing.commuting_2q_gate_router import Commuting2qGateRouter
from .commuting_2q_gate_routing.swap_strategy import SwapStrategy
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from qiskit.dagcircuit import DAGOpNode


class Commuting2qBlocks(Gate):
class Commuting2qBlock(Gate):
"""A gate made of commuting two-qubit gates.
This gate is intended for use with commuting swap strategies to make it convenient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@
from qiskit.dagcircuit import DAGCircuit, DAGOpNode
from qiskit.transpiler import TransformationPass, Layout, TranspilerError

from qiskit.transpiler.passes.routing.swap_strategies.swap_strategy import SwapStrategy
from qiskit.transpiler.passes.routing.swap_strategies.commuting_2q_block import Commuting2qBlocks
from qiskit.transpiler.passes.routing.commuting_2q_gate_routing.swap_strategy import SwapStrategy
from qiskit.transpiler.passes.routing.commuting_2q_gate_routing.commuting_2q_block import (
Commuting2qBlock,
)


class Commuting2qGateRouter(TransformationPass):
"""A class to swap route one or more commuting gates to the coupling map.
This pass routs blocks of commuting two-qubit gates encapsulated as
:class:`.Commuting2qBlocks` instructions. This pass will not apply to other instructions.
This pass routes blocks of commuting two-qubit gates encapsulated as
:class:`.Commuting2qBlock` instructions. This pass will not apply to other instructions.
The mapping to the coupling map is done using swap strategies, see :class:`.SwapStrategy`.
The swap strategy should suit the problem and the coupling map. This transpiler pass
should ideally be executed before the quantum circuit is enlarged with any idle ancilla
Expand All @@ -48,7 +50,7 @@ class Commuting2qGateRouter(TransformationPass):
4
To do this we use a line swap strategy for qubits 0, 1, 3, and 4 defined it in terms
of logical qubits 0, 1, 2, and 3.
of virtual qubits 0, 1, 2, and 3.
.. code-block:: python
Expand All @@ -61,20 +63,19 @@ class Commuting2qGateRouter(TransformationPass):
from qiskit.transpiler.passes import ApplyLayout
from qiskit.transpiler.passes import SetLayout
from qiskit.transpiler.passes.routing.swap_strategies import (
from qiskit.transpiler.passes.routing.commuting_2q_gate_routing import (
SwapStrategy,
FindCommutingPauliEvolutions,
Commuting2qGateRouter,
)
# Define the circuit on logical qubits
# Define the circuit on virtual qubits
op = PauliSumOp.from_list([("IZZI", 1), ("ZIIZ", 2), ("ZIZI", 3)])
circ = QuantumCircuit(4)
circ.append(PauliEvolutionGate(op, 1), range(4))
# Define the swap strategy on qubits before the initial_layout is applied.
swap_cmap = CouplingMap(couplinglist=[(0, 1), (1, 2), (2, 3)])
swap_strat = SwapStrategy(swap_cmap, swap_layers=[[(0, 1), (2, 3)], [(1, 2)]])
swap_strat = SwapStrategy.from_line([0, 1, 2, 3])
# Chose qubits 0, 1, 3, and 4 from the backend coupling map shown above.
backend_cmap = CouplingMap(couplinglist=[(0, 1), (1, 2), (1, 3), (3, 4)])
Expand Down Expand Up @@ -156,7 +157,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
self._bit_indices = {bit: index for index, bit in enumerate(dag.qubits)}

for node in dag.topological_op_nodes():
if isinstance(node.op, Commuting2qBlocks):
if isinstance(node.op, Commuting2qBlock):

# Check that the swap strategy creates enough connectivity for the node.
self._check_edges(node, swap_strategy)
Expand Down Expand Up @@ -201,15 +202,15 @@ def _compose_non_swap_nodes(
return new_dag.copy_empty_like()

def _position_in_cmap(self, j: int, k: int, layout: Layout) -> Tuple[int, ...]:
"""A helper function to track the movement of logical qubits through the swaps.
"""A helper function to track the movement of virtual qubits through the swaps.
Args:
j: The index of decision variable j (i.e. logical qubit).
k: The index of decision variable k (i.e. logical qubit).
j: The index of decision variable j (i.e. virtual qubit).
k: The index of decision variable k (i.e. virtual qubit).
layout: The current layout that takes into account previous swap gates.
Returns:
The position in the coupling map of the logical qubits j and k as a tuple.
The position in the coupling map of the virtual qubits j and k as a tuple.
"""
bit0 = self._bit_indices[layout.get_physical_bits()[j]]
bit1 = self._bit_indices[layout.get_physical_bits()[k]]
Expand Down Expand Up @@ -254,20 +255,20 @@ def _build_sub_layers(current_layer: Dict[tuple, Gate]) -> List[Dict[tuple, Gate
def swap_decompose(
self, dag: DAGCircuit, node: DAGOpNode, current_layout: Layout, swap_strategy: SwapStrategy
) -> DAGCircuit:
"""Take an instance of :class:`.Commuting2qBlocks` and map it to the coupling map.
"""Take an instance of :class:`.Commuting2qBlock` and map it to the coupling map.
The mapping is done with the swap strategy.
Args:
dag: The dag which contains the :class:`.Commuting2qBlocks` we route.
node: A node whose operation is a :class:`.Commuting2qBlocks`.
dag: The dag which contains the :class:`.Commuting2qBlock` we route.
node: A node whose operation is a :class:`.Commuting2qBlock`.
current_layout: The layout before the swaps are applied. This function will
modify the layout so that subsequent gates can be properly composed on the dag.
swap_strategy: The swap strategy used to decompose the node.
Returns:
A dag that is compatible with the coupling map where swap gates have been added
to map the gates in the :class:`.Commuting2qBlocks` to the hardware.
to map the gates in the :class:`.Commuting2qBlock` to the hardware.
"""
trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values())
gate_layers = self._make_op_layers(dag, node.op, current_layout, swap_strategy)
Expand Down Expand Up @@ -305,7 +306,7 @@ def swap_decompose(
return circuit_to_dag(circuit_with_swap)

def _make_op_layers(
self, dag: DAGCircuit, op: Commuting2qBlocks, layout: Layout, swap_strategy: SwapStrategy
self, dag: DAGCircuit, op: Commuting2qBlock, layout: Layout, swap_strategy: SwapStrategy
) -> Dict[int, Dict[tuple, Gate]]:
"""Creates layers of two-qubit gates based on the distance in the swap strategy."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
from qiskit.dagcircuit import DAGCircuit
from qiskit.transpiler import TransformationPass
from qiskit.quantum_info import SparsePauliOp, Pauli
from qiskit.transpiler.passes.routing.swap_strategies.commuting_2q_block import Commuting2qBlocks
from qiskit.transpiler.passes.routing.commuting_2q_gate_routing.commuting_2q_block import (
Commuting2qBlock,
)


class FindCommutingPauliEvolutions(TransformationPass):
Expand All @@ -48,9 +50,8 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
if self.summands_commute(node.op.operator):
sub_dag = self._decompose_to_2q(dag, node.op)

block_op = Commuting2qBlocks(set(sub_dag.op_nodes()))
register = dag.qregs["q"]
wire_order = {qubit: register.index(qubit) for qubit in block_op.qubits}
block_op = Commuting2qBlock(set(sub_dag.op_nodes()))
wire_order = {wire: idx for idx, wire in enumerate(dag.qubits)}
dag.replace_block_with_op([node], block_op, wire_order)

return dag
Expand Down Expand Up @@ -99,7 +100,7 @@ def _pauli_to_edge(pauli: Pauli) -> Tuple[int, ...]:
Returns:
A tuple representing where the Paulis are. For example, the Pauli "IZIZ" will
return (0, 2) since logical qubits 0 and 2 interact.
return (0, 2) since virtual qubits 0 and 2 interact.
Raises:
QiskitError: If the pauli does not exactly have two non-identity terms.
Expand Down
15 changes: 8 additions & 7 deletions releasenotes/notes/swap-strategies-3ab013ca60f02b36.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
---
features:
- |
New transpiler passes have been added. The transpiler pass :class:`.SwapStrategyRouter`
New transpiler passes have been added. The transpiler pass :class:`.Commuting2qGateRouter`
uses swap strategies to route a block of commuting gates to the coupling map. Indeed, routing
is a hard problem but is significantly easier when the gates commute. This is often found in
variational algorithms such as QAOA. Such cases can be dealt with using swap strategies that
apply a predefined set of layers of SWAP gates. Furthermore, the new transpiler pass
:class:`.FindCommutingPauliEvolutions` identifies blocks of Pauli evolutions 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.
is a hard problem but is significantly easier when the gates commute as in CZ networks.
Blocks of commuting gates are also typically found in QAOA. Such cases can be dealt with
using swap strategies that apply a predefined set of layers of SWAP gates. Furthermore, the new
transpiler pass :class:`.FindCommutingPauliEvolutions` identifies blocks of Pauli evolutions
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.
2 changes: 1 addition & 1 deletion test/python/circuit/test_gate_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def setUpClass(cls):
"ClassicalElement",
"StatePreparation",
"LinearFunction",
"Commuting2qBlocks",
"Commuting2qBlock",
}
cls._gate_classes = []
for aclass in class_list:
Expand Down
15 changes: 14 additions & 1 deletion test/python/transpiler/test_swap_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from qiskit.test import QiskitTestCase
from qiskit.transpiler import CouplingMap

from qiskit.transpiler.passes.routing.swap_strategies import SwapStrategy
from qiskit.transpiler.passes.routing.commuting_2q_gate_routing import SwapStrategy


@ddt
Expand Down Expand Up @@ -152,6 +152,19 @@ def test_reaches_full_connectivity(self):
strat = SwapStrategy(CouplingMap(ll27_map), tuple(swap_strat_ll))
self.assertEqual(len(strat.missing_couplings) == 0, result)

def test_new_connections(self):
"""Test the new connections method."""
new_cnx = self.line_strategy.new_connections(0)
expected = [{1, 0}, {2, 1}, {3, 2}, {4, 3}]

self.assertListEqual(new_cnx, expected)

# Test after first swap layer (0, 1) first
new_cnx = self.line_strategy.new_connections(1)
expected = [{3, 0}, {4, 2}]

self.assertListEqual(new_cnx, expected)

def test_possible_edges(self):
"""Test that possible edges works as expected."""
coupling_map = CouplingMap(couplinglist=[(0, 1), (1, 2), (2, 3)])
Expand Down
Loading

0 comments on commit c8ad6cf

Please sign in to comment.