Download

Download this file as Jupyter notebook: topology.ipynb.

Example: Topological Compilation

This example demonstrates how the built-in compiler can be used to ensure that circuits obey the qudit connectivity of a system.

Connectivity Graphs

Connectivity graphs for our system can be declared through Graph. We can instantiate custom connectivity arrangements or use pre-configured systems.

[2]:
import trueq as tq
import trueq.compilation as tqc
import trueq.visualization as tqv

# create an instance of the Regetti Aspen-11 chip.
graph = tqv.Graph.aspen_11(show_labels=True)
graph.plot()
../../_images/guides_compilation_topology_2_0.png

Allocating Labels

We can begin by relabeling our circuit so that their labels match that of our connectivity graphs using AllocateLabels.

[3]:
circ = tq.Circuit(
    [
        {(0, 1): tq.Gate.cx},
        {(0, 2): tq.Gate.random(4)},
        {(0, 3): tq.Gate.cx},
        {(0, 4): tq.Gate.random(4)},
    ]
)

alloc = tqc.AllocateLabels(graph)
circ = tq.Compiler([alloc]).compile(circ)
circ.draw()
[3]:
10 11 25 26 27 Key: relabeling: ((0, 1, 2, 3, 4), (26, 11, 25, 27, 10)) Labels: (26, 11) Name: Gate.cx Aliases: Gate.cx Gate.cnot Locally Equivalent: CNOT Generators: ZX: -90.00 IX: 90.00 ZI: 90.00 1.00 1.00 1.00 1.00 CX CX Labels: (26, 25) Name: Gate Locally Equivalent: Non-Clifford Generators: XZ: -121.30 ZY: -95.01 YI: -85.00 ZZ: -53.97 XY: -53.75 ZX: -36.03 ZI: 33.93 ... -0.16 0.27j 0.22 -0.01j 0.43 0.44j 0.64 -0.25j -0.28 0.19j 0.18 -0.45j -0.47 -0.51j 0.41 -0.06j -0.46 0.62j -0.01 0.56j -0.06 -0.14j -0.26 -0.08j -0.21 0.38j -0.25 -0.59j 0.33 0.13j -0.26 0.46j Labels: (26, 27) Name: Gate.cx Aliases: Gate.cx Gate.cnot Locally Equivalent: CNOT Generators: ZX: -90.00 IX: 90.00 ZI: 90.00 1.00 1.00 1.00 1.00 CX CX Labels: (26, 10) Name: Gate Locally Equivalent: Non-Clifford Generators: ZZ: 139.60 ZY: -99.98 YZ: 76.64 XY: -61.41 ZI: 58.58 XZ: -40.63 XI: -36.52 ... -0.69 -0.41j 0.15 0.14j 0.06 -0.03j -0.20 -0.52j -0.22 0.34j 0.39 0.25j 0.16 -0.36j 0.68 -0.02j 0.20 0.10j 0.48 0.60j -0.21 0.55j -0.14 -0.05j 0.26 0.26j -0.27 0.30j 0.70 -0.05j -0.20 -0.40j

We see that labels 0, 1, 2, 3, 4 of our input circuit got respectively mapped to labels 26, 11, 25, 27, 10. AllocateLabels's remapping attempts to maximize the number of two-qudit gates that act on valid connections on the targeted graph. In the above example (26, 11), (26, 25), and (26, 27) are all connected on the graph. However, since no qudit in the Aspen-11 chip contains more than three connections, we cannot make this circuit obey the couplings through relabeling alone. Therefore the final CNOT gate of (26, 10) is not valid connection. To ensure that all gates obey the connectivity we need to also use DeferredSwapper.

Deferred Swapper

The DeferredSwapper is used to compile circuits so that every gate obeys the topology of our graph. After relabeling the circuit to match that of our graph, the Deferred Swapper will insert swaps in order to ensure that all two-qudit gates are only between labels that can be coupled by the system.

[4]:

swapper = tqc.DeferredSwapper(graph) circ = tq.Compiler([swapper]).compile(circ) circ.draw()
[4]:
10 11 25 26 27 Key: relabeling: ((0, 1, 2, 3, 4), (26, 11, 25, 27, 10)) Labels: (26, 11) Name: Gate.cx Aliases: Gate.cx Gate.cnot Locally Equivalent: CNOT Generators: ZX: -90.00 IX: 90.00 ZI: 90.00 1.00 1.00 1.00 1.00 CX CX Labels: (26, 25) Name: Gate Locally Equivalent: Non-Clifford Generators: XZ: -121.30 ZY: -95.01 YI: -85.00 ZZ: -53.97 XY: -53.75 ZX: -36.03 ZI: 33.93 ... -0.16 0.27j 0.22 -0.01j 0.43 0.44j 0.64 -0.25j -0.28 0.19j 0.18 -0.45j -0.47 -0.51j 0.41 -0.06j -0.46 0.62j -0.01 0.56j -0.06 -0.14j -0.26 -0.08j -0.21 0.38j -0.25 -0.59j 0.33 0.13j -0.26 0.46j Labels: (26, 27) Name: Gate.cx Aliases: Gate.cx Gate.cnot Locally Equivalent: CNOT Generators: ZX: -90.00 IX: 90.00 ZI: 90.00 1.00 1.00 1.00 1.00 CX CX Labels: (10, 11) Name: Gate.swap Aliases: Gate.swap Locally Equivalent: SWAP Generators: YY: 90.00 XX: 90.00 ZZ: 90.00 1.00 1.00 1.00 1.00 SW SW Labels: (26, 11) Name: Gate Locally Equivalent: Non-Clifford Generators: ZZ: 139.60 ZY: -99.98 YZ: 76.64 XY: -61.41 ZI: 58.58 XZ: -40.63 XI: -36.52 ... -0.69 -0.41j 0.15 0.14j 0.06 -0.03j -0.20 -0.52j -0.22 0.34j 0.39 0.25j 0.16 -0.36j 0.68 -0.02j 0.20 0.10j 0.48 0.60j -0.21 0.55j -0.14 -0.05j 0.26 0.26j -0.27 0.30j 0.70 -0.05j -0.20 -0.40j Labels: (11, 10) Name: Gate.swap Aliases: Gate.swap Locally Equivalent: SWAP Generators: YY: 90.00 XX: 90.00 ZZ: 90.00 1.00 1.00 1.00 1.00 SW SW

Here the Deferred Swapper inserted swaps before the final Cycle in order to make the two-qubit gate on (26, 10) valid. Additionally, this swapping algorithm will combine gates into swap sections where its optimal to do so. For example:

[5]:

circ = tq.Circuit([{(26, 10): tq.Gate.random(4)}, {(10, 26): tq.Gate.random(4)}]) circ = tq.Compiler([swapper]).compile(circ) circ.draw()
[5]:
10 11 26 Key: Labels: (10, 11) Name: Gate.swap Aliases: Gate.swap Locally Equivalent: SWAP Generators: YY: 90.00 XX: 90.00 ZZ: 90.00 1.00 1.00 1.00 1.00 SW SW Labels: (26, 11) Name: Gate Locally Equivalent: Non-Clifford Generators: IZ: -73.24 XI: -72.57 YX: 64.32 YY: 60.88 XY: 49.79 IX: -43.09 XX: 42.66 ... 0.14 0.24j 0.49 -0.05j -0.25 0.12j -0.73 0.28j 0.08 0.45j 0.45 -0.25j -0.03 -0.61j 0.39 -0.02j -0.63 0.41j 0.04 -0.32j -0.19 0.49j 0.16 -0.13j 0.37 -0.11j 0.57 0.25j 0.04 0.52j 0.37 -0.25j Labels: (11, 26) Name: Gate Locally Equivalent: Non-Clifford Generators: IZ: 117.10 ZY: 71.81 YZ: -68.06 YY: 67.64 ZI: 63.53 YI: -61.40 XI: 59.50 ... -0.44 -0.23j -0.20 -0.08j 0.17 -0.71j 0.18 0.37j 0.25 0.34j 0.17 0.68j 0.48 -0.19j 0.27 0.04j -0.60 0.18j 0.68 -0.03j 0.09 0.28j 0.25j 0.33 0.27j 0.06 0.04j -0.31 -0.14j -0.44 0.71j Labels: (11, 10) Name: Gate.swap Aliases: Gate.swap Locally Equivalent: SWAP Generators: YY: 90.00 XX: 90.00 ZZ: 90.00 1.00 1.00 1.00 1.00 SW SW

Here both of the two-qudit gates get placed in the same opening and closing pair of swap gates.

Clifford Decomposition

When the Deferred Swapper encounters a Clifford gate, it may perform a Clifford decomposition when it sees that it is optimal to do so.

[6]:
circ = tq.Circuit([{(26, 10): tq.Gate.cx}])
circ = tq.Compiler([swapper]).compile(circ)
circ.draw()
[6]:
10 11 26 Key: Labels: (11, 10) Name: Gate.cz Aliases: Gate.cz Locally Equivalent: CNOT Generators: ZZ: -90.00 ZI: 90.00 IZ: 90.00 1.00 1.00 1.00 -1.00 CZ CZ Labels: (10,) Name: Gate.h Aliases: Gate.h Gate.f Gate.cliff12 Generators: Z: 127.28 X: 127.28 0.71 0.71 0.71 -0.71 H Labels: (11,) Name: Gate.h Aliases: Gate.h Gate.f Gate.cliff12 Generators: Z: 127.28 X: 127.28 0.71 0.71 0.71 -0.71 H Labels: (11, 10) Name: Gate.cz Aliases: Gate.cz Locally Equivalent: CNOT Generators: ZZ: -90.00 ZI: 90.00 IZ: 90.00 1.00 1.00 1.00 -1.00 CZ CZ Labels: (11,) Name: Gate.h Aliases: Gate.h Gate.f Gate.cliff12 Generators: Z: 127.28 X: 127.28 0.71 0.71 0.71 -0.71 H Labels: (26, 11) Name: Gate.cz Aliases: Gate.cz Locally Equivalent: CNOT Generators: ZZ: -90.00 ZI: 90.00 IZ: 90.00 1.00 1.00 1.00 -1.00 CZ CZ Labels: (11,) Name: Gate.h Aliases: Gate.h Gate.f Gate.cliff12 Generators: Z: 127.28 X: 127.28 0.71 0.71 0.71 -0.71 H Labels: (11, 10) Name: Gate.cz Aliases: Gate.cz Locally Equivalent: CNOT Generators: ZZ: -90.00 ZI: 90.00 IZ: 90.00 1.00 1.00 1.00 -1.00 CZ CZ Labels: (10,) Name: Gate.h Aliases: Gate.h Gate.f Gate.cliff12 Generators: Z: 127.28 X: 127.28 0.71 0.71 0.71 -0.71 H Labels: (11,) Name: Gate.h Aliases: Gate.h Gate.f Gate.cliff12 Generators: Z: 127.28 X: 127.28 0.71 0.71 0.71 -0.71 H Labels: (11, 10) Name: Gate.cz Aliases: Gate.cz Locally Equivalent: CNOT Generators: ZZ: -90.00 ZI: 90.00 IZ: 90.00 1.00 1.00 1.00 -1.00 CZ CZ

Here, the Deferred Swapper determines that tq.Gate.cx is a Clifford gate and chooses to decompose it as such. The output is a series of one- and two-qubit Cliffords gates that obey our graph topology.

Additionally, this Clifford decomposition algorithm can be ran directly by using decompose_clifford.


Download

Download this file as Jupyter notebook: topology.ipynb.