Skip to content

Circuit Transpilation

Learn how to convert abstract quantum circuits into hardware-executable form using pyqpanda3's transpilation module. This tutorial covers chip topology modeling, circuit routing, gate decomposition, and the full transpilation pipeline.

Prerequisites: Circuit Construction -- you should be comfortable building QProg and QCircuit objects before working through this tutorial.


Table of Contents


Why Transpilation Matters

When you write a quantum circuit in pyqpanda3, you express your computation using an abstract gate set -- any of the 37+ gates provided by the core module. For example, you can use CNOT(0, 3) to apply a controlled-NOT between qubits 0 and 3, or SWAP(1, 4) to exchange the states of qubits 1 and 4.

However, real quantum hardware imposes three fundamental constraints:

1. Native gate set. A quantum processor does not implement every gate directly. Most superconducting chips support only a small set of basis gates, such as {U3, CZ} or {RZ, X1, CNOT}. A gate like SWAP or TOFFOLI must be decomposed into a sequence of native gates before execution.

2. Qubit connectivity. Physical qubits are arranged in a specific topology. A two-qubit gate can only be applied between qubits that are physically adjacent. If your circuit contains CNOT(0, 3) but the hardware only connects qubits 0-1, 1-2, and 2-3, the transpiler must insert SWAP operations to route the logical interaction through the physical connections.

3. Gate fidelity and coherence. Different qubits and gate types have different error rates. A good transpiler places logical qubits on the physical qubits with the highest fidelity and minimizes the total number of two-qubit gates.

Without transpilation, a circuit written for an idealized quantum computer cannot run on real hardware. The transpilation module bridges this gap by transforming your abstract circuit into one that satisfies the hardware's constraints while preserving its logical behavior.


The Transpilation Pipeline

Transpilation in pyqpanda3 consists of several stages, each addressing one or more hardware constraints:

Gate decomposition rewrites gates that are not in the target hardware's native gate set. For example, a TOFFOLI gate might be decomposed into six CNOT gates plus several single-qubit rotations.

Topology routing maps logical qubits to physical qubits and inserts SWAP gates where needed. pyqpanda3 uses the SABRE (SWAP-based BidiREctional heuristic search) algorithm, which is efficient and produces high-quality mappings.

Optimization applies local rewriting rules to cancel redundant gate pairs (e.g., H followed by H becomes identity) and merge adjacent single-qubit rotations.

The three functions exported by the transpilation module correspond to different parts of this pipeline:

FunctionPurpose
generate_topology(n, type)Generate a synthetic chip topology for testing
Transpiler().transpile(...)Full pipeline: decomposition, routing, and optimization
decompose(...)Standalone gate decomposition without routing

1. Chip Topology

1.1 What Is Chip Topology?

A chip topology describes which pairs of physical qubits can directly execute two-qubit gates. It is represented as an edge list: a list of pairs [qubit_a, qubit_b] indicating that qubits a and b are physically connected.

For example, a 4-qubit linear chain has the topology:

[0, 1], [1, 2], [2, 3]

Visually:

q0 --- q1 --- q2 --- q3

A 4-qubit square (grid) topology adds diagonal connections:

q0 --- q1
 |      |
q2 --- q3

The topology determines the routing problem. In a linear chain, any two-qubit gate between non-adjacent qubits requires SWAP gates to bring the logical states together. In a fully connected topology, no routing is needed because every qubit pair is directly connected.

1.2 generate_topology()

pyqpanda3 provides generate_topology() to create synthetic topology edge lists for testing and experimentation. You do not need to manually construct edge lists for common topologies.

python
from pyqpanda3.transpilation import generate_topology

# Generate a linear chain topology for 4 qubits
topo = generate_topology(4, "linear")
print(topo)
# [[0, 1], [1, 2], [2, 3]]

Signature:

python
generate_topology(num_qubits: int, topology_type: str) -> list

Parameters:

ParameterTypeDescription
num_qubitsintNumber of physical qubits in the topology
topology_typestrThe topology layout type (see below)

Returns: A list of [qubit_a, qubit_b] pairs representing the physical connections.

1.3 Topology Types

The following topology types are supported:

TypeDescriptionBest For
"linear"Chain: q0-q1-q2-...-q(n-1)Simple test circuits
"square"2D grid arrangementGrid-based QPUs
"all-to-all"Every qubit connected to every otherIdealized testing

Linear Topology

A linear chain connects each qubit to its immediate neighbors:

python
from pyqpanda3.transpilation import generate_topology

topo = generate_topology(5, "linear")
print(topo)
# [[0, 1], [1, 2], [2, 3], [3, 4]]

Square (Grid) Topology

A square topology arranges qubits in a 2D grid. Each qubit is connected to its neighbors horizontally and vertically:

python
from pyqpanda3.transpilation import generate_topology

topo = generate_topology(4, "square")
print(topo)
# [[0,1], [0,3], [1,2], [1,4], [2,5],
#  [3,4], [3,6], [4,5], [4,7], [5,8],
#  [6,7], [7,8]]

Square topologies model many real superconducting chips, where qubits are fabricated on a planar substrate with nearest-neighbor couplings. For 4 qubits, generate_topology creates a 3x3 grid (9 qubits, 12 edges).

All-to-All Topology

An all-to-all topology links every qubit to every other qubit:

python
from pyqpanda3.transpilation import generate_topology

topo = generate_topology(4, "all-to-all")
print(topo)
# [[0,1], [0,2], [0,3], [1,2], [1,3], [2,3]]

With all-to-all connectivity, no SWAP gates are needed for routing. This topology is useful for testing decomposition in isolation or simulating idealized hardware. Real ion-trap quantum computers often have all-to-all connectivity.

1.4 Custom Topologies

You can also pass custom edge lists directly. For example, a star graph with qubit 0 at the center:

python
custom_topo = [[0, 1], [0, 2], [0, 3], [0, 4]]

Or a ring (circular) topology:

python
ring_topo = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]]

2. The Transpiler Class

The Transpiler class is the main entry point for circuit transpilation. It combines gate decomposition, qubit routing, and circuit optimization into a single operation.

2.1 Creating a Transpiler

Create a Transpiler instance with no arguments:

python
from pyqpanda3.transpilation import Transpiler

transpiler = Transpiler()

The transpiler is stateless -- it holds no persistent configuration between calls. All parameters are provided at transpile time.

2.2 transpile() with Edge List

The most common way to use the transpiler is to provide an edge list describing the chip topology:

python
from pyqpanda3 import core
from pyqpanda3.transpilation import Transpiler, generate_topology

# Build a circuit that requires routing
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 3)       # qubits 0 and 3 are not adjacent in a 4-qubit chain
prog << core.measure([0, 3], [0, 3])

# Define the chip topology as an edge list
chip_edges = [[0, 1], [1, 2], [2, 3]]

# Transpile: route through the linear chain and decompose to basic gates
transpiler = Transpiler()
transpiled = transpiler.transpile(
    prog,
    chip_edges,            # topology edge list
    {},                    # initial mapping (empty = automatic)
    2                      # optimization level
)

print(transpiled)

In this example, the original circuit applies CNOT(0, 3), but qubits 0 and 3 are three hops apart in the linear chain. The SABRE algorithm automatically finds an optimal initial mapping and routing strategy. For example, it might map logical qubit 0 to physical qubit 3, allowing the CNOT to be executed directly without inserting SWAP gates. The final transpiled result depends on the SABRE algorithm's heuristic decisions.

Signature:

python
transpiler.transpile(
    prog,                 # QProg: the circuit to transpile
    chip_topology_edges,  # list of [int, int]: physical connections
    init_mapping,         # dict: initial qubit mapping (empty dict for automatic)
    optimization_level,   # int: 0, 1, or 2
    basic_gates=None      # list of str: target gate set (optional)
) -> QProg

Parameters:

ParameterTypeDescription
progQProgThe quantum program to transpile
chip_topology_edgeslist[list[int]]Edge list of the chip topology
init_mappingdictInitial logical-to-physical qubit mapping. Pass {} for automatic.
optimization_levelintOptimization level: 0 (none), 1 (basic), 2 (aggressive)
basic_gateslist[str] or NoneTarget gate set names. If None, uses a default set.

2.3 transpile() with Backend Object

When targeting real hardware via the cloud module, you can pass a backend object instead of an edge list. The transpiler automatically extracts the topology and native gate set from the backend:

python
from pyqpanda3 import core
from pyqpanda3.transpilation import Transpiler
from pyqpanda3.qcloud import QCloudService

# Authenticate and get a backend
service = QCloudService("your_api_key")
backend = service.backend("origin_wukong")

# Build a circuit
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1) << core.SWAP(0, 2)
prog << core.measure([0, 1, 2], [0, 1, 2])

# Transpile using the backend's topology and gate set
chip_backend = backend.chip_backend()
transpiler = Transpiler()
transpiled = transpiler.transpile(
    prog,
    chip_backend,          # ChipBackend object (extracts topology and gates)
    {},                    # initial mapping
    2                      # optimization level
)

By calling backend.chip_backend() to obtain a ChipBackend object, the transpiler internally extracts both the topology edge list and the native gate set. This is more convenient and less error-prone than extracting and passing these manually.

2.4 Optimization Levels

The optimization_level parameter controls how aggressively the transpiler optimizes the output circuit. Higher levels produce shorter circuits at the cost of longer transpilation time.

LevelBehaviorUse Case
0No optimization. Only routing and decomposition.Debugging; seeing raw routing output
1Basic optimization. Cancels adjacent inverse gates and merges rotations.Fast transpilation with cleanup
2Aggressive optimization. All rewriting rules including commutativity-based reordering.Production circuits for hardware
python
# Level 0: raw output, no simplification
transpiled = transpiler.transpile(prog, chip_edges, {}, 0)

# Level 1: basic gate cancellation
transpiled = transpiler.transpile(prog, chip_edges, {}, 1)

# Level 2: aggressive optimization (recommended for hardware)
transpiled = transpiler.transpile(prog, chip_edges, {}, 2)

2.5 Initial Qubit Mapping

The init_mapping parameter lets you specify which logical qubit should start on which physical qubit. This is a dictionary mapping logical qubit indices to physical qubit indices:

python
# Map logical qubit 0 -> physical qubit 2
# Map logical qubit 1 -> physical qubit 0
# Let the transpiler decide the rest
init_mapping = {0: 2, 1: 0}

transpiled = transpiler.transpile(prog, chip_edges, init_mapping, 2)

When you pass an empty dictionary {}, the transpiler uses the SABRE algorithm to find an initial mapping automatically. In most cases, the automatic mapping is sufficient and you do not need to provide one.

You might want to specify a custom mapping when:

  • You know that certain qubits on the hardware have higher fidelity and want to place important logical qubits there.
  • You are running multiple circuits and want them to use the same mapping for result comparison.

2.6 Specifying Basic Gates

The basic_gates parameter restricts the output circuit to use only gates from the specified set. Any gate not in this set will be decomposed:

python
# Target the IBM-like gate set
transpiled = transpiler.transpile(
    prog, chip_edges, {}, 2,
    basic_gates=["U3", "CNOT"]
)

# Target a simpler gate set (useful for analysis)
transpiled = transpiler.transpile(
    prog, chip_edges, {}, 2,
    basic_gates=["H", "S", "T", "CNOT"]
)

Common gate sets:

Gate SetGatesHardware
IBM-like["U3", "CNOT"] or ["RZ", "X1", "CNOT"]Superconducting
OriginQ-like["U3", "CZ"] or ["RZ", "X1", "CZ"]Superconducting
Clifford+T["H", "S", "T", "CNOT"]Theoretical analysis
Ion trap["RXX", "RZ", "RX"] or ["MS", "RZ"]Trapped ion

If basic_gates is not specified (or None), the transpiler uses a default gate set.


3. Gate Decomposition

The decompose function performs gate decomposition without routing. It takes a circuit (or matrix) and rewrites all gates in terms of a specified basis gate set. This is useful when you want to see the gate decomposition in isolation, without modifying qubit assignments.

3.1 Why Decompose Gates?

Gate decomposition serves several purposes:

  1. Hardware compatibility: Real QPUs implement a fixed set of physical operations. Any gate outside this set must be expressed as a sequence of supported gates.

  2. Circuit analysis: Decomposing into a standard gate set makes it easier to count resources (e.g., how many two-qubit gates are actually needed).

  3. Gate count optimization: A high-level gate like TOFFOLI requires 6 CNOT gates when decomposed. Knowing this helps you estimate circuit cost.

  4. Verification: Decomposing a unitary matrix into gates and checking that the resulting circuit's matrix matches the original verifies correctness.

The decomposition rules follow well-known identities from quantum circuit synthesis. For example:

  • SWAP(a, b) = CNOT(a,b) + CNOT(b,a) + CNOT(a,b) (3 CNOT gates)
  • CZ(a, b) = H(b) + CNOT(a,b) + H(b) (1 CNOT + 2 H gates)
  • TOFFOLI(a, b, c) = 6 CNOT + several single-qubit gates

3.2 decompose(prog, basic_gates)

Decompose all gates in a QProg into the specified basic gate set:

python
from pyqpanda3 import core
from pyqpanda3.transpilation import decompose

# Build a circuit with non-native gates
prog = core.QProg()
prog << core.H(0)
prog << core.SWAP(0, 1)        # SWAP is not in most native gate sets
prog << core.TOFFOLI(0, 1, 2)  # TOFFOLI is always decomposed
prog << core.measure([0, 1, 2], [0, 1, 2])

# Decompose into H + CNOT + rotation gates
decomposed = decompose(prog, ["H", "CNOT", "RZ", "X1"])

print(f"Original gate count: {prog.count_ops()}")
print(f"Decomposed gate count: {decomposed.count_ops()}")

Signature:

python
decompose(prog: QProg, basic_gates: list) -> QProg

Parameters:

ParameterTypeDescription
progQProgInput quantum program
basic_gateslist[str]Target gate set names

Returns: A new QProg with all gates expressed in terms of basic_gates.

The original program is not modified. The decompose function always returns a new program object.

3.3 decompose(circuit, basic_gates)

Decompose gates in a QCircuit object:

python
from pyqpanda3 import core
from pyqpanda3.transpilation import decompose

# Build a circuit module
circuit = core.QCircuit()
circuit << core.SWAP(0, 1)
circuit << core.CZ(1, 2)

# Decompose into CNOT + single-qubit gates
decomposed_circuit = decompose(circuit, ["H", "CNOT", "RZ", "X1"])

print(f"Original depth: {circuit.depth()}")
print(f"Decomposed depth: {decomposed_circuit.depth()}")

Signature:

python
decompose(circuit: QCircuit, basic_gates: list) -> QCircuit

Parameters:

ParameterTypeDescription
circuitQCircuitInput quantum circuit
basic_gateslist[str]Target gate set names

Returns: A new QCircuit with all gates expressed in terms of basic_gates.

This overload is useful when you are working with reusable circuit modules rather than full programs. The decomposed circuit can then be embedded into any QProg.

3.4 decompose(matrix, qubits)

Decompose a unitary matrix into a quantum circuit. This is the most powerful form of decomposition -- it takes an arbitrary 2n×2n unitary matrix and synthesizes a circuit that implements it.

python
import numpy as np
from pyqpanda3.transpilation import decompose

# Define a 2x2 unitary matrix (single-qubit gate)
# This is a phase gate: diag(1, exp(i*pi/4))
mat = np.array([
    [1, 0],
    [0, np.exp(1j * np.pi / 4)]
], dtype=complex)

# Decompose into a circuit acting on qubit 0
circuit = decompose(mat, [0])
print(f"Circuit depth: {circuit.depth()}")
print(f"Circuit size: {circuit.size()}")

Signature:

python
decompose(matrix: np.ndarray, qubits: list) -> QCircuit

Parameters:

ParameterTypeDescription
matrixnp.ndarrayUnitary matrix to decompose. Must be 2n×2n for n qubits.
qubitslist[int]Qubit indices the circuit acts on. Length must be n.

Returns: A QCircuit that implements the given unitary on the specified qubits.

Two-qubit example:

python
import numpy as np
from pyqpanda3.transpilation import decompose

# A 4x4 unitary (two-qubit gate)
# Create a controlled-phase gate matrix
cp_mat = np.array([
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, np.exp(1j * np.pi / 3)]
], dtype=complex)

# Decompose into a circuit on qubits 0 and 1
circuit = decompose(cp_mat, [0, 1])
print(f"2Q circuit depth: {circuit.depth()}")
print(f"2Q gate count: {circuit.count_ops(only_q2=True)}")

Verification:

You can verify that the decomposed circuit correctly implements the original matrix:

python
import numpy as np
from pyqpanda3 import core
from pyqpanda3.transpilation import decompose

# Original matrix
mat = np.array([
    [1, 0],
    [0, np.exp(1j * np.pi / 4)]
], dtype=complex)

# Decompose
circuit = decompose(mat, [0])

# Compute the circuit's unitary
circuit_matrix = circuit.matrix()

# Check that they match
print("Matrices match:", np.allclose(mat, circuit_matrix))
# Matrices match: True

3.5 Decomposition Rules

The decomposition engine applies well-known quantum circuit identities. Here are the most important ones:

SWAP decomposition:

SWAP(a,b)=CNOT(a,b)CNOT(b,a)CNOT(a,b)

This requires 3 CNOT gates.

python
from pyqpanda3 import core
from pyqpanda3.transpilation import decompose

swap_prog = core.QProg()
swap_prog << core.SWAP(0, 1)
result = decompose(swap_prog, ["H", "CNOT", "RZ", "X1"])
# result now contains: CNOT(0,1), CNOT(1,0), CNOT(0,1)

CZ decomposition:

CZ(a,b)=(IH)CNOT(a,b)(IH)

TOFFOLI decomposition:

A Toffoli (CCX) gate decomposes into 6 CNOT gates and 7 single-qubit gates:

TOFFOLI(a,b,c)=sequence of 6 CNOTs + T/Tdg + H gates

Single-qubit gate decomposition:

Any single-qubit unitary U can be decomposed into at most 3 rotation gates using the ZYZ decomposition:

U=RZ(α)RY(β)RZ(γ)

Or into a U3 gate:

U=U3(θ,ϕ,λ)

4. Putting It All Together

4.1 Full Transpilation Workflow

Here is the complete workflow for transpiling a circuit from an abstract representation to a hardware-ready form:

Complete example:

python
from pyqpanda3 import core
from pyqpanda3.transpilation import Transpiler, generate_topology, decompose

# Step 1: Build the abstract circuit
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.SWAP(0, 2)
prog << core.T(1)
prog << core.measure([0, 1, 2], [0, 1, 2])

print("=== Original Circuit ===")
print(f"Depth: {prog.depth(only_q2=True)}")
print(f"Gates: {prog.count_ops()}")
print(f"2Q Gates: {prog.count_ops(only_q2=True)}")

# Step 2: Define chip topology (4-qubit linear chain)
chip_edges = generate_topology(4, "linear")
print(f"\nChip topology: {chip_edges}")

# Step 3: Transpile with optimization level 2
transpiler = Transpiler()
transpiled = transpiler.transpile(
    prog,
    chip_edges,
    {},                           # automatic qubit mapping
    2,                            # aggressive optimization
    basic_gates=["H", "RZ", "X1", "CNOT"]
)

print("\n=== Transpiled Circuit ===")
print(f"Depth: {transpiled.depth(only_q2=True)}")
print(f"Gates: {transpiled.count_ops()}")
print(f"2Q Gates: {transpiled.count_ops(only_q2=True)}")

# Step 4: Verify correctness on a simulator
machine = core.CPUQVM()
machine.run(prog, 10000)
orig_probs = machine.result().get_prob_dict()

machine.run(transpiled, 10000)
transpiled_probs = machine.result().get_prob_dict()

print("\n=== Verification ===")
print(f"Original probabilities:    {orig_probs}")
print(f"Transpiled probabilities:  {transpiled_probs}")

4.2 Comparing Before and After

After transpilation, always compare the original and transpiled circuits on a simulator. The measurement probabilities should match within statistical noise. The workflow shown in section 4.1 demonstrates this pattern: run both circuits on CPUQVM and compare get_prob_dict() output. Each bitstring's probability should agree within a tolerance of approximately 0.05 for 10,000 shots.

4.3 Transpiling for Real Hardware

When using the cloud module to target real quantum processors, the transpilation workflow integrates with the backend information:

python
from pyqpanda3 import core
from pyqpanda3.transpilation import Transpiler
from pyqpanda3.qcloud import QCloudService, QCloudOptions

# Connect to the cloud
service = QCloudService("your_api_key")
backend = service.backend("origin_wukong")

# Get chip configuration
chip_info = backend.chip_info()
basic_gates = chip_info.get_basic_gates()  # e.g., ["U3", "CZ"]
chip_backend = chip_info.get_chip_backend()

# Build circuit
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1) << core.CNOT(1, 2)
prog << core.SWAP(0, 2)
prog << core.measure([0, 1, 2], [0, 1, 2])

# Transpile for the target hardware
transpiler = Transpiler()
transpiled = transpiler.transpile(prog, chip_backend, {}, 2)

# Submit to the hardware
options = QCloudOptions()
options.set_amend(False)  # Already transpiled

instruction_json = transpiled.to_instruction(chip_backend)
job = backend.run_instruction(instruction_json, 3000, options)

The key advantage of transpiling locally (rather than relying on server-side compilation) is fine-grained control over the optimization level, initial mapping, and basic gate set.


5. Practical Examples

5.1 Transpiling a GHZ Circuit

The Greenberger-Horne-Zeilinger (GHZ) state is a multi-qubit maximally entangled state. Preparing a GHZ state on real hardware requires the transpiler to route CNOT gates through the chip topology due to limited physical qubit connectivity.

python
from pyqpanda3 import core
from pyqpanda3.transpilation import Transpiler, generate_topology

# Build a 4-qubit GHZ state circuit
# Logical circuit: H(0) -> CNOT(0,1) -> CNOT(0,2) -> CNOT(0,3)
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.CNOT(0, 2)
prog << core.CNOT(0, 3)
prog << core.measure([0, 1, 2, 3], [0, 1, 2, 3])

print("=== GHZ State Circuit ===")
print(f"Logical depth: {prog.depth(only_q2=True)}")
print(f"Logical gates: {prog.count_ops()}")

# Transpile for a linear chain topology
# Qubit 0 must interact with qubits 1, 2, 3
# In a linear chain: 0-1-2-3
# CNOT(0,2) and CNOT(0,3) require SWAP routing
chip_edges = generate_topology(4, "linear")
transpiler = Transpiler()
transpiled = transpiler.transpile(prog, chip_edges, {}, 2)

print("\n=== Transpiled GHZ Circuit ===")
print(f"Physical depth: {transpiled.depth(only_q2=True)}")
print(f"Physical gates: {transpiled.count_ops()}")
print(f"Physical 2Q gates: {transpiled.count_ops(only_q2=True)}")

# Verify
machine = core.CPUQVM()
machine.run(prog, 10000)
orig = machine.result().get_prob_dict()
machine.run(transpiled, 10000)
trans = machine.result().get_prob_dict()

print(f"\nOriginal GHZ:   {orig}")
print(f"Transpiled GHZ: {trans}")

On a linear chain, the transpiler must route the CNOT(0,3) interaction through qubits 1 and 2. This introduces additional SWAP gates, increasing the circuit depth. On a fully connected topology, no routing is needed:

python
# No routing needed with all-to-all connectivity
full_topo = generate_topology(4, "all-to-all")
transpiled_full = transpiler.transpile(prog, full_topo, {}, 2)
print(f"\nAll-to-all depth: {transpiled_full.depth(only_q2=True)}")

5.2 Transpiling a Random Circuit

Random circuits are useful for benchmarking transpilation performance. This example generates a random 3-qubit circuit and transpiles it on a 100-qubit square topology:

python
import time
from pyqpanda3 import core
from pyqpanda3.transpilation import Transpiler, generate_topology

qubits = list(range(3))
circuit = core.random_qcircuit(qubits, 20, ["RX", "RY", "RZ", "H", "CNOT", "SWAP"])

prog = core.QProg()
prog.append(circuit)
for q in qubits:
    prog.append(core.measure(q, q))

topo = generate_topology(100, "square")
transpiler = Transpiler()

start = time.time()
transpiled = transpiler.transpile(prog, topo, {}, 2)
elapsed = (time.time() - start) * 1000
print(f"Transpilation time: {elapsed:.2f} ms")
print(f"Before depth: {prog.depth(only_q2=True)}, After depth: {transpiled.depth(only_q2=True)}")

5.3 Decomposing a Unitary Matrix

Decompose a unitary matrix into a circuit and then verify it the resulting circuit matches the original matrix:

python
import numpy as np
from pyqpanda3.transpilation import decompose

# Custom U3 matrix
theta, phi, lam = np.pi / 5, np.pi / 7, np.pi / 11
u3_mat = np.array([
    [np.cos(theta / 2), -np.exp(1j * lam) * np.sin(theta / 2)],
    [np.exp(1j * phi) * np.sin(theta / 2),
     np.exp(1j * (phi + lam)) * np.cos(theta / 2)]
], dtype=complex)

circuit = decompose(u3_mat, [0])
print("Correct:", np.allclose(u3_mat, circuit.matrix()))

5.4 Custom Topology and Gate Set

The following example shows how to combine a custom T-shaped topology with a specific gate set:

python
from pyqpanda3 import core
from pyqpanda3.transpilation import Transpiler, decompose

# T-shaped topology: q0--q2--q3, q2--q1, q2--q4
t_topo = [[0, 2], [1, 2], [2, 3], [2, 4]]

prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)  # requires routing: 0->2->1
prog << core.CNOT(3, 4)  # requires routing: 3->2->4
prog << core.measure([0, 1, 2, 3, 4], [0, 1, 2, 3, 4])

transpiler = Transpiler()
transpiled = transpiler.transpile(prog, t_topo, {}, 2, basic_gates=["H", "RZ", "X1", "CNOT"])

print(f"Original: {prog.count_ops()} gates")
print(f"Transpiled: {transpiled.count_ops()} gates, {transpiled.count_ops(only_q2=True)} 2Q")

# Compare with decomposition only (no routing)
decomposed_only = decompose(prog, ["H", "RZ", "X1", "CNOT"])
print(f"Decomposed only: {decomposed_only.count_ops()} gates")

6. Performance Considerations

Transpilation time depends on circuit size, topology sparsity, and optimization level. For circuits up to 100 qubits with depth 1000, transpilation typically completes in under a second.

Routing through sparse topologies increases circuit depth. If two qubits have a shortest-path distance of d in the topology graph (i.e., it takes d edges to traverse from one qubit to the other, where adjacent qubits have a distance of 1), a CNOT between them requires 2(d-1) SWAP gates to be inserted, and each SWAP consumes 3 CNOT gates, so the two-qubit gate count increases by 6(d-1).

DistanceSWAPs added2Q gate increase
1 (adjacent)00
2212
3424
d2(d-1)6(d-1)

Choosing a topology: Use "all-to-all" for decomposition testing, "linear" for worst-case routing, "square" for realistic chip simulation, use backend.chip_info() for real hardware.

Choosing an optimization level: Use 0 for debugging, 1 for quick testing, and 2 for production. For very large circuits (>50 qubits), level 1 may be faster than level 2.


7. API Quick Reference

generate_topology

SignatureReturnsDescription
generate_topology(num_qubits, topology_type)list[list[int]]Generate a chip topology edge list

Topology types: "linear", "square", "all-to-all"

Transpiler

MethodReturnsDescription
Transpiler()TranspilerCreate a new transpiler instance
.transpile(prog, edges, init_map, opt_level)QProgTranspile with edge list topology
.transpile(prog, backend, init_map, opt_level)QProgTranspile with backend object

decompose (3 overloads)

SignatureReturnsDescription
decompose(prog, basic_gates)QProgDecompose gates in a QProg
decompose(circuit, basic_gates)QCircuitDecompose gates in a QCircuit
decompose(matrix, qubits)QCircuitSynthesize circuit from unitary matrix

Summary

In this tutorial you learned:

  1. Why transpilation matters -- Real quantum hardware has a limited native gate set and restricted qubit connectivity. Transpilation bridges the gap between abstract circuits and hardware constraints.

  2. Chip topology -- generate_topology(n, type) creates synthetic topologies for testing. Three types are available: "linear" (chain), "square" (grid), and "all-to-all".

  3. The Transpiler class -- Transpiler().transpile() performs the full transpilation pipeline: gate decomposition, qubit routing via the SABRE algorithm, and circuit optimization. You can pass either an edge list or a cloud backend object.

  4. Optimization levels -- Level 0 does no optimization, level 1 applies basic gate cancellation, and level 2 applies aggressive optimization rules. Use level 2 for hardware execution.

  5. Gate decomposition -- The decompose function rewrites gates in terms of a specified basis set. Three overloads handle QProg, QCircuit, and raw unitary matrices.

  6. Verification -- Always compare the original and transpiled circuits on a simulator to confirm correctness before submitting to real hardware.

The transpilation module is the bridge between your quantum algorithms and real quantum processors. Understanding its capabilities helps you write circuits that are both correct and efficient on hardware.

Next steps:

Released under the MIT License.