Introduction to Rivet Transpiler

Quantum Transpilation is the transformation of a given virtual quantum circuit:

  • to match the topology of a specific device

  • to optimize the circuit for execution

Rivet gives the User a high level of insight into, and control over the transpilation process, including the – typically invisible to the user – use of quantum resources, such as auxiliary qubits used in various transpilation passes, which can be constrained via the Qubit-Constrained Transpilation function. Combined with a debugging interface it allows to optimize the classical and quantum compute involved in the execution and shorten the development loop, especially in research and prototyping.

1. Basic Example

Transpilation includes placement of virtual qubits of a circuit to physical qubits of the quantum device or simulator. Additionally, SWAP gates can be included to route qubits around the backend topology.

Here we present a simple quantum circuit with 3 qubits before and after transpilation (using the function transpile_chain which transpiles a chain of virtual circuits keeping qubits consistent).

BEFORE transpilation

from qiskit import QuantumCircuit

from qiskit_ibm_runtime.fake_provider import FakeLimaV2

from rivet_transpiler import transpile_chain

backend = FakeLimaV2()

circuit = QuantumCircuit(3)

circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(0, 2)

circuit.barrier()

circuit.draw()
q_0: ──■─────────■──
     ┌─┴─┐       │
q_1:  X ├──■────┼──
     └───┘┌─┴─┐┌─┴─┐
q_2: ─────┤ X ├┤ X           └───┘└───┘

AFTER transpilation

CHAIN = [circuit] * 3

transpiled_circuit = transpile_chain(
    CHAIN,
    backend,
    seed_transpiler=1234
)

transpiled_circuit.draw(fold=-1)
                              ┌───┐            ┌───┐                                ┌───┐          ┌───┐               ┌───┐       q_1 -> 0 ──■─────────■──┤ X ├──■────────░─┤ X ├─────────────────■────────░───■──┤ X ├──■───────┤ X ├───────────────┤ X ├─░─
               ┌─┴─┐     ┌─┴─┐└─┬─┘┌─┴─┐       └─┬─┘┌───┐     ┌───┐┌─┴─┐┌───┐  ┌─┴─┐└─┬─┘┌─┴─┐┌───┐└─┬─┘┌───┐     ┌───┐└─┬─┘       q_2 -> 1  X ├──■──┤ X ├──■──┤ X ├──■───░───■──┤ X ├──■──┤ X ├┤ X ├┤ X ├─░─┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├──■──┤ X ├──■───░─
               └───┘┌─┴─┐└───┘     └───┘┌─┴─┐       └─┬─┘┌─┴─┐└─┬─┘└───┘└─┬─┘  └───┘     └───┘└─┬─┘     └─┬─┘┌─┴─┐└─┬─┘            q_0 -> 2 ─────┤ X ├───────────────┤ X ├─░────────■──┤ X ├──■─────────■───░──────────────────■─────────■──┤ X ├──■────────░─
                    └───┘               └───┘            └───┘                                               └───┘           ░
ancilla_0 -> 3 ────────────────────────────────────────────────────────────────░───────────────────────────────────────────────░─
                                                                                                                              ░
ancilla_1 -> 4 ────────────────────────────────────────────────────────────────░───────────────────────────────────────────────░─
                                                                                                                              

2. Transpilation Stages

Pass Manager is an internal Qiskit object constructed by the transpile function “under the hood” during transpilation. Pass Manager consists of Passes which analyse or change the transpiled circuit. Calling pass_manager.run is equivalent to calling transpile and passing a circuit, backend and corresponding transpilation parameters.

[1]:
import qiskit

pass_manager = qiskit.transpiler.preset_passmanagers.generate_preset_pass_manager(
    # backend=backend,
    # initial_layout=[1, 0, 2],
    optimization_level=3,
    seed_transpiler=1234
)

Calling pass_manager.stages the main transpilation stages can be displayed: (‘init’, ‘layout’, ‘routing’, ‘translation’, ‘optimization’, ‘scheduling’)

  1. Init - Unrolling custom instructions and converting the circuit to all 1 and 2 qubit gates.

  2. Layout - Mapping circuit virtual qubits to backend physical qubits.

  3. Routing - Inject SWAP gates to comply with the backend’s coupling map.

  4. Translation - Translate to the target backend’s basis gate set.

  5. Optimization - Main optimization loop to increase circuit quality.

  6. Scheduling - Conversion to hardware-specific pulses.

The Rivet Transpiler package provides a family of functions (transpile and service functions) for the efficient transpilation of quantum circuits. Check API Reference section for more details.

3. Supported Stacks

Flexible Stack Selection: Users can transpile their entire circuit, or parts of a circuit, via one or a combination of transpilation passes from different stacks of their preference. This allows one to choose the optimal transpiling strategy for the given use case and circuit architecture. Supported stacks include:

  1. Qiskit transpilation

  2. Pytket transpilation

  3. BQSKit QSearch synthesis

  4. BQSKit QFactor instantiation

Make sure you have followed and installed Rivet Transpiler. Check the installation steps from Readme.md. Then import Qiskit, other modules required for the example execution and Rivet Transpiler functions.

Stacks Usage

[2]:
from rivet_transpiler import transpile
from rivet_transpiler import get_litmus_circuit

from qiskit_ibm_runtime.fake_provider import FakeLimaV2

backend = FakeLimaV2()

QUBITS_COUNT = 3

litmus_circuit = get_litmus_circuit(QUBITS_COUNT, "Litmus")

litmus_circuit.draw()
[2]:
            ┌──────────────┐          ┌───┐ Litmus
Litmus_0_0: ┤ Rz(Litmus_0) ├──■───────┤ X ├───░────
            ├──────────────┤┌─┴─┐     └─┬─┘   ░
Litmus_0_1: ┤ Rz(Litmus_1) ├┤ X ├──■────┼─────░────
            ├──────────────┤└───┘┌─┴─┐  │     ░
Litmus_0_2: ┤ Rz(Litmus_2) ├─────┤ X ├──■─────░────
            └──────────────┘     └───┘        ░    
[3]:
STACKS = ["qiskit",
          "qiskit_qsearch",
          "qiskit_qfactor_qsearch",
          "qiskit_pytket"]

Qiskit

[4]:
transpiled_circuit = transpile(
    litmus_circuit,
    backend,
    stack="qiskit",
    seed_transpiler=1234)

transpiled_circuit.draw()
[4]:
                ┌──────────────┐          ┌───┐          ┌───┐ Litmus
Litmus_0_2 -> 0 ┤ Rz(Litmus_2) ├───────■──┤ X ├──■───────┤ X ├───░────
                ├──────────────┤     ┌─┴─┐└─┬─┘┌─┴─┐┌───┐└─┬─┘   ░
Litmus_0_0 -> 1 ┤ Rz(Litmus_0) ├──■──┤ X ├──■──┤ X ├┤ X ├──■─────░────
                ├──────────────┤┌─┴─┐└───┘     └───┘└─┬─┘        ░
Litmus_0_1 -> 2 ┤ Rz(Litmus_1) ├┤ X ├─────────────────■──────────░────
                └──────────────┘└───┘                            ░
 ancilla_0 -> 3 ──────────────────────────────────────────────────────

 ancilla_1 -> 4 ──────────────────────────────────────────────────────
                                                                      

Pytket

[5]:
transpiled_circuit = transpile(
    litmus_circuit,
    backend,
    stack="qiskit_pytket",
    seed_transpiler=1234)

transpiled_circuit.draw()
[5]:
                ┌──────────────┐          ┌───┐          ┌───┐ ░
Litmus_0_2 -> 0 ┤ Rz(Litmus_2) ├───────■──┤ X ├──■───────┤ X ├─░─
                ├──────────────┤     ┌─┴─┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ ░
Litmus_0_0 -> 1 ┤ Rz(Litmus_0) ├──■──┤ X ├──■──┤ X ├┤ X ├──■───░─
                ├──────────────┤┌─┴─┐└───┘     └───┘└─┬─┘      ░
Litmus_0_1 -> 2 ┤ Rz(Litmus_1) ├┤ X ├─────────────────■────────░─
                └──────────────┘└───┘                          ░
 ancilla_0 -> 3 ─────────────────────────────────────────────────

 ancilla_1 -> 4 ─────────────────────────────────────────────────