Compilation

Native gates and costs

Each set of single-qubit gates in a sentence is a local layer, so a sentence of \(n\) native gates has \(n+1\) local layers. The cost of a sentence is

\[C(g_1,\ldots,g_n)=\sum_{i=1}^n c(g_i)+(n+1)c_{\mathrm{local}},\]

where \(c(g)\) is the cost of native gate \(g\) and \(c_{\mathrm{local}}\) is local_layer_cost, which defaults to zero. When costs are durations, set local_layer_cost to the duration of one layer of single-qubit gates. It can also model a fixed cost per native gate, such as pulse ramp times.

A local-layer charge can make a shorter sentence of more expensive native gates the cheapest. For this target, four \(\sqrt{\mathrm{CX}}\) gates cost 360 ns, and one \(\sqrt{\mathrm{CX}}\) with two \(\sqrt[3]{\mathrm{iSWAP}}\) gates costs 370 ns. A 20 ns charge for each local layer adds 100 ns to the first sentence and 80 ns to the second, so the three-gate sentence wins:

from qiskit.circuit.library import CSXGate, iSwapGate
from qiskit.quantum_info import Operator, random_unitary
from gulps.decomposition import GulpsDecomposer

gates = [CSXGate(), iSwapGate().power(1 / 3)]
target_unitary = random_unitary(4, seed=3)
for local_layer_cost in [0, 20]:
    decomposer = GulpsDecomposer(
        gates, costs=[90, 140], local_layer_cost=local_layer_cost
    )
    cost, sentence = decomposer.select(target_unitary)
    print(f"local_layer_cost={local_layer_cost}: cost {cost:g} ns,",
          [gate.name for gate in sentence])
local_layer_cost=0: cost 360 ns, ['csx', 'csx', 'csx', 'csx']
local_layer_cost=20: cost 450 ns, ['csx', 'xx_plus_yy', 'xx_plus_yy']
from qiskit.quantum_info import process_fidelity

compiled = decomposer(target_unitary)
print(f"Process fidelity: {process_fidelity(Operator(compiled), target_unitary):.12f}")
compiled.draw("mpl")
Process fidelity: 1.000000000000
A two-qubit circuit with one √CX gate, two ∛iSWAP gates, and four layers of single-qubit gates.

Qiskit circuits

GulpsDecompositionPass groups the gates of a circuit into two-qubit blocks and synthesizes each block with the decomposer.

from qiskit.circuit.library import quantum_volume
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
    HighLevelSynthesis, Unroll3qOrMore, Optimize1qGatesDecomposition,
)
from gulps.transpiler import GulpsDecompositionPass

decomposer = GulpsDecomposer(gates, costs=[90, 140], local_layer_cost=20)
circuit = quantum_volume(3, depth=3, seed=5)
pipeline = PassManager([
    HighLevelSynthesis(),
    Unroll3qOrMore(),
    GulpsDecompositionPass(decomposer),
    Optimize1qGatesDecomposition(basis=["u"]),
])
compiled = pipeline.run(circuit)
compiled.draw("mpl", fold=-1, scale=0.5)
A compiled three-qubit quantum-volume circuit: two blocks, one with two √CX gates and one ∛iSWAP, the other with one √CX and two ∛iSWAP, between single-qubit u gates.

With a backend Target, select GULPS as the translation method. The plugin reads the native gates and their durations for each qubit pair from the Target.

Build a Target
from itertools import permutations
from qiskit.circuit import Parameter
from qiskit.circuit.library import UGate
from qiskit.transpiler import InstructionProperties, Target

pairs = list(permutations(range(3), 2))
target = Target(num_qubits=3)
target.add_instruction(
    CSXGate(), {p: InstructionProperties(duration=90e-9, error=3e-3) for p in pairs}
)
target.add_instruction(
    iSwapGate().power(1 / 3),
    {p: InstructionProperties(duration=140e-9, error=8e-3) for p in pairs},
)
target.add_instruction(
    UGate(Parameter("a"), Parameter("b"), Parameter("c")),
    {(q,): InstructionProperties(duration=20e-9, error=1e-4) for q in range(3)},
)
from qiskit import transpile

compiled = transpile(
    circuit, target=target, translation_method="gulps", optimization_level=1
)
print(dict(compiled.count_ops()))
{'u': 17, 'csx': 6, 'xx_plus_yy': 1}

Warning

Optimization levels 2 and 3 can undo the least-cost sentences. Their later passes resynthesize two-qubit blocks with Qiskit’s own decomposers, which minimize the number of two-qubit gates instead of your costs.

A pass built from a Target minimizes the Target property named by cost, "duration" (the default) or "error". With these error rates, minimizing error uses only \(\sqrt{\mathrm{CX}}\):

pipeline = PassManager([
    HighLevelSynthesis(),
    Unroll3qOrMore(),
    GulpsDecompositionPass(target, cost="error"),
    Optimize1qGatesDecomposition(basis=["u"]),
])
print(dict(pipeline.run(circuit).count_ops()))
{'u': 19, 'csx': 8}

Synthesis costs in other passes

select returns the cost of a block without constructing its circuit. Other transpiler passes can use it to compare choices by their compiled cost instead of by gate counts. MIRAGE introduced this idea for routing: a router can implement a block \(U\) or its mirror \(\mathrm{SWAP}\,U\), which applies \(U\) and then exchanges the two qubits. Using the mirror updates the qubit mapping instead of inserting a SWAP gate.

from qiskit.circuit.library import quantum_volume, SwapGate
from gulps.analysis.coverage import two_qubit_blocks

routing_circuit = quantum_volume(4, depth=2, seed=1)
blocks = two_qubit_blocks(routing_circuit)
swap = Operator(SwapGate()).data
direct = decomposer.select(blocks)
mirrored = decomposer.select([swap @ Operator(block).data for block in blocks])
print("Block   Original (ns)   Mirrored (ns)")
for i, ((cost, _), (mirror_cost, _)) in enumerate(zip(direct, mirrored)):
    print(f"{i:5} {cost:15g} {mirror_cost:15g}")
Block   Original (ns)   Mirrored (ns)
    0             350             400
    1             400             350
    2             400             350
    3             350             400

Because a mirror moves the logical qubits, the router weighs each saving against its effect on later gates.

Local equivalence

Two gates are locally equivalent when single-qubit gates before and after one of them give the other, up to global phase. For example, Hadamard gates on the target qubit before and after CZ give CX. GULPS searches over these equivalence classes, not over matrices, because the local layers of a sentence can absorb any difference within a class.

from qiskit.circuit.library import CXGate, CZGate
from gulps.invariants import LocalEquivalenceClass

cx, cz = LocalEquivalenceClass.from_unitaries([CXGate(), CZGate()])
print("Same class:", cx == cz)
print("Weyl coordinates:", cx.weyl.round(3) + 0.0)
Same class: True
Weyl coordinates: [0.5 0.  0. ]

Each class has one canonical gate, set by its three Weyl coordinates:

\[\operatorname{CAN}(c_1,c_2,c_3) = \exp\!\left[\frac{i\pi}{2}(c_1 XX+c_2 YY+c_3 ZZ)\right].\]

Here \(XX=X\otimes X\), and likewise for \(YY\) and \(ZZ\).

Reachable regions

The reachable region of a sentence is the set of classes that it implements as its local layers vary.

from gulps.analysis.region import ReachableRegion
from gulps.analysis.viz.polytope_viz import plot_region

sqrt_iswap = LocalEquivalenceClass.from_unitary(iSwapGate().power(1 / 2))
swap_class = LocalEquivalenceClass.from_unitary(SwapGate())
one = ReachableRegion.of([sqrt_iswap])
two = ReachableRegion.of([sqrt_iswap, sqrt_iswap])
print("One √iSWAP reaches CX:", one.reaches(cx))
print("Two √iSWAP reach CX:", two.reaches(cx))
print("Two √iSWAP reach SWAP:", two.reaches(swap_class))
One √iSWAP reaches CX: False
Two √iSWAP reach CX: True
Two √iSWAP reach SWAP: False
ax = plot_region(two, color="tab:blue")
ax.scatter(*cx.weyl, color="tab:orange", s=60, label="CX / CZ")
ax.scatter(*swap_class.weyl, color="tab:red", s=60, marker="x", label="SWAP")
ax.set_title("Reachable with two √iSWAP gates")
ax.legend();
Reachable classes for two √iSWAP gates in the Weyl chamber. CX and CZ share a point on the edge of the region, marked with a circle. SWAP is outside, marked with a cross.

The class of CX and CZ lies in the shaded region, so two \(\sqrt{\mathrm{iSWAP}}\) gates implement both. SWAP lies outside, so no choice of local layers makes two \(\sqrt{\mathrm{iSWAP}}\) gates implement SWAP. The outer tetrahedron is the Weyl chamber, the set of all Weyl coordinates, and the inner wireframe encloses the perfect entanglers. The shaded region includes both global-phase representatives of each class.

How synthesis works describes how GULPS searches these regions in order of cost.