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
- The Transpilation Pipeline
- 1. Chip Topology
- 2. The Transpiler Class
- 3. Gate Decomposition
- 4. Putting It All Together
- 5. Practical Examples
- 6. Performance Considerations
- 7. API Quick Reference
- Summary
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:
| Function | Purpose |
|---|---|
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 --- q3A 4-qubit square (grid) topology adds diagonal connections:
q0 --- q1
| |
q2 --- q3The 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.
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:
generate_topology(num_qubits: int, topology_type: str) -> listParameters:
| Parameter | Type | Description |
|---|---|---|
num_qubits | int | Number of physical qubits in the topology |
topology_type | str | The 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:
| Type | Description | Best For |
|---|---|---|
"linear" | Chain: q0-q1-q2-...-q(n-1) | Simple test circuits |
"square" | 2D grid arrangement | Grid-based QPUs |
"all-to-all" | Every qubit connected to every other | Idealized testing |
Linear Topology
A linear chain connects each qubit to its immediate neighbors:
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:
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:
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:
custom_topo = [[0, 1], [0, 2], [0, 3], [0, 4]]Or a ring (circular) topology:
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:
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:
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:
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)
) -> QProgParameters:
| Parameter | Type | Description |
|---|---|---|
prog | QProg | The quantum program to transpile |
chip_topology_edges | list[list[int]] | Edge list of the chip topology |
init_mapping | dict | Initial logical-to-physical qubit mapping. Pass {} for automatic. |
optimization_level | int | Optimization level: 0 (none), 1 (basic), 2 (aggressive) |
basic_gates | list[str] or None | Target 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:
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.
| Level | Behavior | Use Case |
|---|---|---|
0 | No optimization. Only routing and decomposition. | Debugging; seeing raw routing output |
1 | Basic optimization. Cancels adjacent inverse gates and merges rotations. | Fast transpilation with cleanup |
2 | Aggressive optimization. All rewriting rules including commutativity-based reordering. | Production circuits for hardware |
# 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:
# 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:
# 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 Set | Gates | Hardware |
|---|---|---|
| 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:
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.
Circuit analysis: Decomposing into a standard gate set makes it easier to count resources (e.g., how many two-qubit gates are actually needed).
Gate count optimization: A high-level gate like
TOFFOLIrequires 6 CNOT gates when decomposed. Knowing this helps you estimate circuit cost.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:
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:
decompose(prog: QProg, basic_gates: list) -> QProgParameters:
| Parameter | Type | Description |
|---|---|---|
prog | QProg | Input quantum program |
basic_gates | list[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:
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:
decompose(circuit: QCircuit, basic_gates: list) -> QCircuitParameters:
| Parameter | Type | Description |
|---|---|---|
circuit | QCircuit | Input quantum circuit |
basic_gates | list[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
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:
decompose(matrix: np.ndarray, qubits: list) -> QCircuitParameters:
| Parameter | Type | Description |
|---|---|---|
matrix | np.ndarray | Unitary matrix to decompose. Must be n qubits. |
qubits | list[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:
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:
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: True3.5 Decomposition Rules
The decomposition engine applies well-known quantum circuit identities. Here are the most important ones:
SWAP decomposition:
This requires 3 CNOT gates.
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:
TOFFOLI decomposition:
A Toffoli (CCX) gate decomposes into 6 CNOT gates and 7 single-qubit gates:
Single-qubit gate decomposition:
Any single-qubit unitary
Or into a U3 gate:
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:
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:
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.
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:
# 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:
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:
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:
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).
| Distance | SWAPs added | 2Q gate increase |
|---|---|---|
| 1 (adjacent) | 0 | 0 |
| 2 | 2 | 12 |
| 3 | 4 | 24 |
d | 2(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
| Signature | Returns | Description |
|---|---|---|
generate_topology(num_qubits, topology_type) | list[list[int]] | Generate a chip topology edge list |
Topology types: "linear", "square", "all-to-all"
Transpiler
| Method | Returns | Description |
|---|---|---|
Transpiler() | Transpiler | Create a new transpiler instance |
.transpile(prog, edges, init_map, opt_level) | QProg | Transpile with edge list topology |
.transpile(prog, backend, init_map, opt_level) | QProg | Transpile with backend object |
decompose (3 overloads)
| Signature | Returns | Description |
|---|---|---|
decompose(prog, basic_gates) | QProg | Decompose gates in a QProg |
decompose(circuit, basic_gates) | QCircuit | Decompose gates in a QCircuit |
decompose(matrix, qubits) | QCircuit | Synthesize circuit from unitary matrix |
Summary
In this tutorial you learned:
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.
Chip topology --
generate_topology(n, type)creates synthetic topologies for testing. Three types are available:"linear"(chain),"square"(grid), and"all-to-all".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.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.
Gate decomposition -- The
decomposefunction rewrites gates in terms of a specified basis set. Three overloads handleQProg,QCircuit, and raw unitary matrices.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:
- Cloud Computing -- Submit transpiled circuits to real quantum processors
- Visualization -- Draw and compare original vs. transpiled circuits
- Noise Simulation -- Simulate noise on transpiled circuits
- API Reference: transpilation -- Complete API documentation