Skip to content

Commit

Permalink
remove redefiing coupling map in control flow context
Browse files Browse the repository at this point in the history
  • Loading branch information
ewinston committed Sep 8, 2022
1 parent ca4f477 commit 33e0007
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 77 deletions.
6 changes: 4 additions & 2 deletions qiskit/dagcircuit/dagnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ def semantic_eq(node1, node2, bit_indices1=None, bit_indices2=None):
if "barrier" == node1.op.name == node2.op.name:
return set(node1_qargs) == set(node2_qargs)

if node1_qargs == node2_qargs:
if node1_qargs == node2_qargs or (
(node1.op.name == node2.op.name in {"swap", "cz"})
and (set(node1_qargs) == set(node2_qargs))
):
if node1_cargs == node2_cargs:
if getattr(node1.op, "condition", None) == getattr(node2.op, "condition", None):
if node1.op == node2.op:
Expand All @@ -87,7 +90,6 @@ def semantic_eq(node1, node2, bit_indices1=None, bit_indices2=None):
):
if bit_indices1.get(node1.wire, None) == bit_indices2.get(node2.wire, None):
return True

return False


Expand Down
14 changes: 12 additions & 2 deletions qiskit/transpiler/passes/routing/layout_transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def __init__(
to_layout: Union[Layout, str],
seed: Union[int, np.random.default_rng] = None,
trials=4,
inplace=True,
):
"""LayoutTransformation initializer.
Expand All @@ -55,6 +56,10 @@ def __init__(
trials (int):
How many randomized trials to perform, taking the best circuit as output.
inplace (bool): Whether to apply transform to dag in-place. If False, the
orginal dag is returned. The swap network on the physical qubits, however,
can be retrieved from the ``perm_circ`` attribute.
"""
super().__init__()
self.from_layout = from_layout
Expand All @@ -67,6 +72,9 @@ def __init__(
graph = self.coupling_map.graph.to_undirected()
self.token_swapper = ApproximateTokenSwapper(graph, seed)
self.trials = trials
self.perm_circ = None
self.perm_qubits = None
self.inplace = inplace

def run(self, dag):
"""Apply the specified partial permutation to the circuit.
Expand Down Expand Up @@ -106,9 +114,11 @@ def run(self, dag):
pqubit: to_layout.get_virtual_bits()[vqubit]
for vqubit, pqubit in from_layout.get_virtual_bits().items()
}

perm_circ = self.token_swapper.permutation_circuit(permutation, self.trials)

qubits = [dag.qubits[i[0]] for i in sorted(perm_circ.inputmap.items(), key=lambda x: x[0])]
dag.compose(perm_circ.circuit, qubits=qubits)
self.perm_circ = perm_circ
self.perm_qubits = qubits

dag.compose(perm_circ.circuit, qubits=qubits, inplace=self.inplace)
return dag
53 changes: 30 additions & 23 deletions qiskit/transpiler/passes/routing/stochastic_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
from qiskit.circuit.library.standard_gates import SwapGate
from qiskit.transpiler.layout import Layout
from qiskit.transpiler.passes.routing.utils import (
transpile_cf_multiblock,
transpile_cf_looping,
layout_transform,
route_cf_multiblock,
route_cf_looping,
get_ordered_virtual_qubits,
combine_permutations,
)
from qiskit.circuit import IfElseOp, WhileLoopOp, ForLoopOp, ControlFlowOp

Expand All @@ -49,7 +50,9 @@ class StochasticSwap(TransformationPass):
the circuit.
"""

def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_layout=None):
def __init__(
self, coupling_map, trials=20, seed=None, fake_run=False, initial_layout=None, _depth=0
):
"""StochasticSwap initializer.
The coupling map is a connected graph
Expand All @@ -74,6 +77,7 @@ def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_l
self.trivial_layout = None
self.initial_layout = initial_layout
self._qubit_indices = None
self._depth = _depth

def run(self, dag):
"""Run the StochasticSwap pass on `dag`.
Expand All @@ -96,10 +100,13 @@ def run(self, dag):
raise TranspilerError("The layout does not match the amount of qubits in the DAG")

canonical_register = dag.qregs["q"]
if self.initial_layout:
self.trivial_layout = self.initial_layout
else:
self.trivial_layout = Layout.generate_trivial_layout(canonical_register)
# if self.initial_layout:
# self.trivial_layout = self.initial_layout
# else:
# self.trivial_layout = Layout.generate_trivial_layout(canonical_register)
self.trivial_layout = Layout.generate_trivial_layout(canonical_register)
if self.initial_layout is None:
self.initial_layout = self.trivial_layout
self._qubit_indices = {bit: idx for idx, bit in enumerate(dag.qubits)}

self.qregs = dag.qregs
Expand Down Expand Up @@ -215,8 +222,8 @@ def _layer_permutation(self, layer_partition, layout, qubit_subset, coupling, tr

edges = best_edges.edges()
for idx in range(len(edges) // 2):
swap_src = self.trivial_layout._p2v[edges[2 * idx]]
swap_tgt = self.trivial_layout._p2v[edges[2 * idx + 1]]
swap_src = self.initial_layout._p2v[edges[2 * idx]]
swap_tgt = self.initial_layout._p2v[edges[2 * idx + 1]]
trial_circuit.apply_operation_back(SwapGate(), [swap_src, swap_tgt], [])
best_circuit = trial_circuit

Expand Down Expand Up @@ -249,7 +256,10 @@ def _layer_update(self, dag, layer, best_layout, best_depth, best_circuit):
logger.debug("layer_update: there are no swaps in this layer")
# Output this layer
layer_circuit = layer["graph"]
order = layout.reorder_bits(dag.qubits)
order = combine_permutations(
get_ordered_virtual_qubits(self.initial_layout, dag.qregs),
get_ordered_virtual_qubits(layout, dag.qregs),
)
dag.compose(layer_circuit, qubits=order)

def _mapper(self, circuit_graph, coupling_graph, trials=20):
Expand All @@ -276,10 +286,10 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20):
for i, v in enumerate(layerlist):
logger.debug(" %d: %s", i, v["partition"])

qubit_subset = self.trivial_layout.get_virtual_bits().keys()
qubit_subset = self.initial_layout.get_virtual_bits().keys()

# Find swap circuit to precede each layer of input circuit
layout = self.trivial_layout.copy()
layout = self.initial_layout.copy()

# Construct an empty DAGCircuit with the same set of
# qregs and cregs as the input circuit
Expand All @@ -294,9 +304,7 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20):
cf_layer = False
cf_layout = None
for node in subdag.op_nodes(op=ControlFlowOp):
updated_ctrl_op, cf_layout = self._transpile_controlflow_op(
node.op, layout, circuit_graph.qubits
)
updated_ctrl_op, cf_layout = self._transpile_controlflow_op(node.op, layout)
node.op = updated_ctrl_op
cf_layer = True
if cf_layer:
Expand Down Expand Up @@ -368,15 +376,14 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20):
return circuit_graph
return dagcircuit_output

def _transpile_controlflow_op(self, cf_op, current_layout, qubits):
def _transpile_controlflow_op(self, cf_op, current_layout, _depth=0):
"""handle controlflow ops by type"""
new_coupling = layout_transform(self.coupling_map, current_layout, qubits)
_pass = self.__class__(new_coupling, initial_layout=None, seed=self.seed)
_pass = self.__class__(
self.coupling_map, initial_layout=current_layout, seed=self.seed, _depth=self._depth + 1
)
if isinstance(cf_op, IfElseOp):
return transpile_cf_multiblock(
_pass, cf_op, current_layout, new_coupling, self.qregs, seed=self.seed
)
return route_cf_multiblock(_pass, cf_op, current_layout, self.qregs, seed=self.seed)
elif isinstance(cf_op, (ForLoopOp, WhileLoopOp)):
return transpile_cf_looping(_pass, cf_op, current_layout, new_coupling, seed=self.seed)
return route_cf_looping(_pass, cf_op, current_layout, seed=self.seed)
else:
raise TranspilerError(f"unsupported control flow operation: {cf_op}")
82 changes: 40 additions & 42 deletions qiskit/transpiler/passes/routing/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@
"""Utility functions for routing"""

import numpy as np
from qiskit.transpiler.coupling import CouplingMap
from qiskit.transpiler.layout import Layout
from qiskit.circuit import QuantumRegister


def transpile_cf_multiblock(tpass, cf_op, current_layout, coupling, qregs, seed=None):
def route_cf_multiblock(tpass, cf_op, current_layout, qregs, seed=None):
"""Transpile control flow instructions which may contain multiple
blocks (e.g. IfElseOp). Since each control flow block may
induce a yield a different layout, this function applies swaps
Expand All @@ -28,7 +26,6 @@ def transpile_cf_multiblock(tpass, cf_op, current_layout, coupling, qregs, seed=
tpass (BasePass): Transpiler pass object to use recursively.
cf_op (IfElseOp): multiblock instruction.
current_layout (Layout): The current layout at the start of the instruction.
coupling (CouplingMap): the coupling map to use within the control flow instruction.
qregs (list(QuantumRegister)): quantum registers for circuit
seed (int): seed for RNG of internal layout transformation.
Expand All @@ -40,35 +37,47 @@ def transpile_cf_multiblock(tpass, cf_op, current_layout, coupling, qregs, seed=
from qiskit.transpiler.passes.routing.layout_transformation import LayoutTransformation
from qiskit.converters import dag_to_circuit, circuit_to_dag

block_circuits = [] # control flow circuit blocks
if current_layout is None:
canonical_register = qregs["q"]
current_layout = Layout.generate_trivial_layout(canonical_register)
coupling = tpass.coupling_map
block_dags = [] # control flow dag blocks
block_layouts = [] # control flow layouts

for i, block in enumerate(cf_op.blocks):
tpass.initial_layout = current_layout
dag_block = circuit_to_dag(block)
updated_dag_block = tpass.run(dag_block)

block_dags.append(updated_dag_block)
block_layouts.append(tpass.property_set["final_layout"].copy())

depth_cnt = [bdag.depth() for bdag in block_dags]
maxind = np.argmax(depth_cnt)
block_circuits = [None] * len(block_layouts)
p2v = current_layout._p2v
for i, dag in enumerate(block_dags):
if i == maxind:
block_circuits.append(dag_to_circuit(dag))
block_circuits[i] = dag_to_circuit(dag)
else:
layout_xform = LayoutTransformation(
coupling, block_layouts[i], block_layouts[maxind], seed=seed
coupling, block_layouts[i], block_layouts[maxind], seed=seed, inplace=False
)
match_dag = layout_xform.run(dag)
block_circuits.append(dag_to_circuit(match_dag))
final_permutation = combine_permutations(
get_ordered_virtual_qubits(current_layout, qregs),
get_ordered_virtual_qubits(block_layouts[maxind], qregs),
)
final_layout = Layout.from_intlist(final_permutation, *qregs.values())
layout_xform.run(dag)
physical_swap_dag = layout_xform.perm_circ.circuit
if physical_swap_dag.depth():
virtual_swap_dag = dag.copy_empty_like()
order = [
p2v[virtual_swap_dag.qubits.index(qubit)] for qubit in layout_xform.perm_qubits
]
virtual_swap_dag.compose(physical_swap_dag, qubits=order)
dag.compose(virtual_swap_dag)
block_circuits[i] = dag_to_circuit(dag)
final_layout = block_layouts[maxind]
return cf_op.replace_blocks(block_circuits), final_layout


def transpile_cf_looping(tpass, cf_op, current_layout, coupling, seed=None):
def route_cf_looping(tpass, cf_op, current_layout, seed=None):
"""For looping this pass adds a swap layer using LayoutTransformation
to the end of the loop body to bring the layout back to the
starting layout. This prevents reapplying layout changing
Expand All @@ -79,7 +88,6 @@ def transpile_cf_looping(tpass, cf_op, current_layout, coupling, seed=None):
cf_op (ForLoopOp, WhileLoopOp): looping instruction.
current_layout (Layout): The current layout at the start and by the
end of the instruction.
coupling (CouplingMap): the coupling map to use within the control flow instruction.
seed (int): seed for RNG of internal layout transformation.
Returns:
Expand All @@ -91,20 +99,29 @@ def transpile_cf_looping(tpass, cf_op, current_layout, coupling, seed=None):
from qiskit.transpiler.passes.routing.layout_transformation import LayoutTransformation
from qiskit.converters import dag_to_circuit, circuit_to_dag

coupling = tpass.coupling_map
dag_block = circuit_to_dag(cf_op.blocks[0])
start_qreg = QuantumRegister(coupling.size(), "q")
start_layout = Layout.generate_trivial_layout(start_qreg)
start_layout = current_layout
updated_dag_block = tpass.run(dag_block)
updated_layout = tpass.property_set["final_layout"].copy()

layout_xform = LayoutTransformation(coupling, updated_layout, start_layout, seed=seed)
match_dag = layout_xform.run(updated_dag_block)
match_circ = dag_to_circuit(match_dag)
return cf_op.replace_blocks([match_circ]), current_layout
layout_xform = LayoutTransformation(
coupling, updated_layout, start_layout, seed=seed, inplace=False
)
layout_xform.run(updated_dag_block)
physical_swap_dag = layout_xform.perm_circ.circuit
if physical_swap_dag.depth():
p2v = current_layout._p2v
virtual_swap_dag = updated_dag_block.copy_empty_like()
order = [p2v[virtual_swap_dag.qubits.index(qubit)] for qubit in layout_xform.perm_qubits]
virtual_swap_dag.compose(physical_swap_dag, qubits=order)
updated_dag_block.compose(virtual_swap_dag)
updated_circ_block = dag_to_circuit(updated_dag_block)
return cf_op.replace_blocks([updated_circ_block]), current_layout


def get_ordered_virtual_qubits(layout, qregs):
"""Get list of virtual qubits associated with odered list
"""Get list of virtual qubits associated with ordered list
of physical qubits.
Args:
Expand Down Expand Up @@ -132,22 +149,3 @@ def combine_permutations(*permutations):
for this_order in permutations[1:]:
order = [order[i] for i in this_order]
return order


def layout_transform(cmap, layout, qubits):
"""Transform coupling map according to layout.
Args:
cmap (CouplingMap): coupling map to transform
layout (Layout): device layout
qubits (list(Qubit)): ordered list of qubits
Returns:
CouplingMap: coupling map under specified layout.
"""
new_map = []
vmap = layout.get_virtual_bits()
for bit0, bit1 in cmap.get_edges():
qubit0, qubit1 = qubits[bit0], qubits[bit1]
new_map.append([vmap[qubit0], vmap[qubit1]])
return CouplingMap(couplinglist=new_map)
11 changes: 5 additions & 6 deletions qiskit/transpiler/passes/utils/check_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.circuit.controlflow import ControlFlowOp
from qiskit.transpiler.layout import Layout
from qiskit.transpiler.passes.routing.utils import layout_transform


class CheckMap(AnalysisPass):
Expand Down Expand Up @@ -71,11 +70,11 @@ def run(self, dag):
return
for cf_instr in dag.op_nodes(op=ControlFlowOp):
layout = Layout(dict(enumerate(cf_instr.qargs)))
check_map = CheckMap(layout_transform(self.coupling_map, layout, dag.qubits))
# check_map = CheckMap(layout_transform2(self.coupling_map, layout))
for block in cf_instr.op.blocks:
dag_block = circuit_to_dag(block)
check_map.run(dag_block)
if not check_map.property_set["is_swap_mapped"]:
self.property_set["is_swap_mapped"] = False
mapped_dag = dag_block.copy_empty_like()
order = layout.reorder_bits(mapped_dag.qubits)
mapped_dag.compose(dag_block, qubits=order)
self.run(mapped_dag)
if not self.property_set["is_swap_mapped"]:
return
3 changes: 3 additions & 0 deletions test/python/transpiler/test_check_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ def test_swap_mapped_cf_layout_change_false(self):
dag = circuit_to_dag(circuit)
pass_ = CheckMap(coupling)
pass_.run(dag)
print(circuit)
print(circuit.data[0].operation.params[0])
print(pass_.property_set["is_swap_mapped"])
self.assertFalse(pass_.property_set["is_swap_mapped"])

def test_swap_mapped_cf_layout_change_true(self):
Expand Down
6 changes: 4 additions & 2 deletions test/python/transpiler/test_stochastic_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,13 +793,13 @@ def test_controlflow_pre_intra_if_else(self, seed):
if seed == 1:
expected.h(0)
expected.x(1)
expected.swap(1, 2)
expected.swap(2, 1)
expected.cx(0, 1)
expected.measure(0, 0)

etrue_body.cx(0, 2)
etrue_body.swap(0, 2)
etrue_body.swap(1, 2)
etrue_body.swap(2, 1)
etrue_body.swap(4, 3)

efalse_body.swap(0, 2)
Expand All @@ -826,6 +826,7 @@ def test_controlflow_pre_intra_if_else(self, seed):
expected.measure(qreg, creg[[1, 2, 0, 4, 3]])
else:
raise ValueError(f"unsupported test seed={seed}")

self.assertEqual(cqc, expected)
check_map_pass = CheckMap(coupling)
check_map_pass.run(circuit_to_dag(expected))
Expand Down Expand Up @@ -880,6 +881,7 @@ def test_controlflow_pre_intra_post_if_else(self):
expected.cx(3, 2)
expected.barrier()
expected.measure(qreg, creg[[2, 4, 0, 3, 1]])

self.assertEqual(cqc, expected)
check_map_pass = CheckMap(coupling)
check_map_pass.run(circuit_to_dag(expected))
Expand Down

0 comments on commit 33e0007

Please sign in to comment.