Routing with t|ket>¶
We wrap tket’s compilation unit framework to keep track of qubit mappings and work with generic devices.
[1]:
import cirq
import recirq
import networkx as nx
from cirq.contrib.svg import SVGCircuit
import numpy as np
[2]:
from pytket.predicates import CompilationUnit, ConnectivityPredicate
from pytket.passes import SequencePass, RoutingPass, DecomposeSwapsToCXs
from pytket.routing import GraphPlacement
Example circuit¶
We’ll route a 3-regular circuit to Sycamore23. To try to clear up some of the confusion about which indices are which, we’ll construct the initial circuit with LineQubits
10 through 19 which should be thought of as “logical indices”.
[3]:
from recirq.qaoa.problem_circuits import get_generic_qaoa_circuit
from recirq.qaoa.gates_and_compilation import compile_problem_unitary_to_arbitrary_zz, \
compile_driver_unitary_to_rx
problem_graph = nx.random_regular_graph(d=3, n=10)
nx.set_edge_attributes(problem_graph, values=1, name='weight')
circuit_qubits = cirq.LineQubit.range(10, 20)
gammas = np.random.randn(2)
betas = np.random.randn(2)
circuit = get_generic_qaoa_circuit(
problem_graph=problem_graph,
qubits=circuit_qubits,
gammas=gammas,
betas=betas)
circuit = compile_problem_unitary_to_arbitrary_zz(circuit)
circuit = compile_driver_unitary_to_rx(circuit)
SVGCircuit(circuit)
findfont: Font family ['Arial'] not found. Falling back to DejaVu Sans.
[3]:
We need to “route” this circuit¶
Let’s look at the “connectivity graph” of the circuit vs. that of the device
[4]:
import cirq.contrib.routing as ccr
uncompiled_c_graph = ccr.get_circuit_connectivity(circuit)
nx.draw_networkx(uncompiled_c_graph)
[5]:
import cirq.google as cg
dev_graph = ccr.xmon_device_to_graph(cg.Sycamore23)
nx.draw_networkx(dev_graph)
[6]:
# alias for the device. If this notebook were wrapped
# in a function, `circuit` and `device` would be the arguments
device = cg.Sycamore23
Mapping to device indices¶
We’ll keep a set of secret indices that number device qubits contiguously from zero instead of (row, col)
[7]:
index_to_qubit = sorted(device.qubit_set())
qubit_to_index = {q: i for i, q in enumerate(index_to_qubit)}
Convert to pytket Device
¶
The provided function doesn’t work with SerializableDevice
. We use existing functionality to turn Devices into graphs to provide a more robust solution.
[8]:
import pytket
from pytket.circuit import Node
def _qubit_index_edges():
dev_graph = ccr.xmon_device_to_graph(device)
for n1, n2 in dev_graph.edges:
#yield Node('q', n1.row, n1.col), Node('q', n2.row, n2.col)
yield (qubit_to_index[n1], qubit_to_index[n2])
def _device_to_tket_device():
arc = pytket.routing.Architecture(
list(_qubit_index_edges())
)
return pytket.device.Device({}, {}, arc)
tk_circuit = pytket.cirq.cirq_to_tk(circuit)
tk_device = _device_to_tket_device()
tket understands LineQubit and uses our strange indexing convention¶
[9]:
tk_circuit.qubits
[9]:
[q[15], q[18], q[11], q[17], q[14], q[10], q[13], q[19], q[16], q[12]]
but our device uses our secret indices¶
There seems to be a bug if you use their built-in support for two-index qubits (nodes): Existing register q cannot support id: q[6, 1]
[10]:
tk_device.coupling
[10]:
[(node[0], node[2]),
(node[2], node[1]),
(node[2], node[3]),
(node[2], node[6]),
(node[1], node[5]),
(node[3], node[7]),
(node[6], node[7]),
(node[6], node[10]),
(node[5], node[6]),
(node[5], node[4]),
(node[5], node[9]),
(node[9], node[10]),
(node[7], node[8]),
(node[7], node[11]),
(node[10], node[11]),
(node[10], node[14]),
(node[8], node[12]),
(node[11], node[12]),
(node[11], node[15]),
(node[14], node[15]),
(node[12], node[13]),
(node[12], node[16]),
(node[15], node[16]),
(node[15], node[19]),
(node[13], node[17]),
(node[16], node[17]),
(node[16], node[20]),
(node[19], node[20]),
(node[17], node[18]),
(node[17], node[21]),
(node[20], node[21]),
(node[20], node[22])]
Placement and Routing pass¶
[11]:
from pytket.predicates import CompilationUnit, ConnectivityPredicate
from pytket.passes import SequencePass, RoutingPass, DecomposeSwapsToCXs, PlacementPass
from pytket.routing import GraphPlacement
[12]:
unit = CompilationUnit(tk_circuit, [ConnectivityPredicate(tk_device)])
passes = SequencePass([
PlacementPass(GraphPlacement(tk_device)),
RoutingPass(tk_device)])
passes.apply(unit)
valid = unit.check_all_predicates()
assert valid
The initial mapping¶
This maps from logical LineQubits to secret device indices
[13]:
unit.initial_map
[13]:
{q[10]: node[2],
q[11]: node[3],
q[12]: node[5],
q[13]: node[8],
q[14]: node[0],
q[15]: node[7],
q[16]: node[4],
q[17]: node[1],
q[18]: node[10],
q[19]: node[6]}
Bookkept initial mapping¶
We “decode” our tket conventions back into Cirq idioms.
[14]:
def tk_to_i(tk):
i = tk.index
assert len(i) == 1, i
return i[0]
initial_map = {cirq.LineQubit(tk_to_i(n1)): index_to_qubit[tk_to_i(n2)] for n1, n2 in unit.initial_map.items()}
initial_map
[14]:
{cirq.LineQubit(10): cirq.GridQubit(4, 2),
cirq.LineQubit(11): cirq.GridQubit(4, 3),
cirq.LineQubit(12): cirq.GridQubit(5, 1),
cirq.LineQubit(13): cirq.GridQubit(5, 4),
cirq.LineQubit(14): cirq.GridQubit(3, 2),
cirq.LineQubit(15): cirq.GridQubit(5, 3),
cirq.LineQubit(16): cirq.GridQubit(5, 0),
cirq.LineQubit(17): cirq.GridQubit(4, 1),
cirq.LineQubit(18): cirq.GridQubit(6, 2),
cirq.LineQubit(19): cirq.GridQubit(5, 2)}
The final mapping¶
This maps from logical LineQubits to final secret device indices.
[15]:
unit.final_map
[15]:
{q[10]: node[2],
q[11]: node[3],
q[12]: node[6],
q[13]: node[7],
q[14]: node[1],
q[15]: node[10],
q[16]: node[8],
q[17]: node[0],
q[18]: node[5],
q[19]: node[4]}
[16]:
final_map = {cirq.LineQubit(tk_to_i(n1)): index_to_qubit[tk_to_i(n2)]
for n1, n2 in unit.final_map.items()}
final_map
[16]:
{cirq.LineQubit(10): cirq.GridQubit(4, 2),
cirq.LineQubit(11): cirq.GridQubit(4, 3),
cirq.LineQubit(12): cirq.GridQubit(5, 2),
cirq.LineQubit(13): cirq.GridQubit(5, 3),
cirq.LineQubit(14): cirq.GridQubit(4, 1),
cirq.LineQubit(15): cirq.GridQubit(6, 2),
cirq.LineQubit(16): cirq.GridQubit(5, 4),
cirq.LineQubit(17): cirq.GridQubit(3, 2),
cirq.LineQubit(18): cirq.GridQubit(5, 1),
cirq.LineQubit(19): cirq.GridQubit(5, 0)}
The compilation unit applies the mapping¶
So our circuit qubits use secret device indices
[17]:
unit.circuit.qubits
[17]:
[node[0],
node[1],
node[2],
node[3],
node[4],
node[5],
node[6],
node[7],
node[8],
node[10]]
So we map the circuit to Grid Qubits¶
[18]:
routed_circuit = pytket.cirq.tk_to_cirq(unit.circuit)
routed_circuit = routed_circuit.transform_qubits(lambda q: index_to_qubit[q.x])
SVGCircuit(routed_circuit)
[18]:
Now it’s nice and compiled¶
[19]:
routed_c_graph = ccr.get_circuit_connectivity(routed_circuit)
nx.draw_networkx(routed_c_graph)
Check that circuits are equivalent¶
[20]:
for _, op, _ in routed_circuit.findall_operations_with_gate_type(cirq.TwoQubitGate):
a, b = op.qubits
assert a.is_adjacent(b)
[21]:
import cirq.contrib.acquaintance as cca
def permute_gate(qubits, permutation):
return cca.LinearPermutationGate(
num_qubits=len(qubits),
permutation={i: permutation[i] for i in range(len(permutation))}
).on(*qubits)
final_to_initial_map = {final_map[cq]: initial_map[cq]
for cq in circuit_qubits}
initial_qubits = [initial_map[cq] for cq in circuit_qubits]
final_permutation = [initial_qubits.index(final_to_initial_map[q])
for q in initial_qubits]
rcircuit_with_perm = routed_circuit.copy()
rcircuit_with_perm.append(permute_gate(initial_qubits, final_permutation))
expected = circuit.unitary(qubit_order=cirq.QubitOrder.explicit(circuit_qubits))
actual = rcircuit_with_perm.unitary(qubit_order=cirq.QubitOrder.explicit(initial_qubits))
cirq.testing.assert_allclose_up_to_global_phase(expected, actual, atol=1e-8)