Lewati ke konten utama

Membuat dan melakukan transpilasi terhadap backend kustom

# Added by doQumentation β€” required packages for this notebook
!pip install -q numpy qiskit rustworkx
# Don't use SVGs for this file because the images are too large,
# and the SVGs are much larger than their PNGs equivalents.
%config InlineBackend.figure_format='png'
```json

{/* cspell:ignore multichip interchip Lasciate ogne speranza voi ch'intrate */}
{/*
DO NOT EDIT THIS CELL!!!
This cell's content is generated automatically by a script. Anything you add
here will be removed next time the notebook is run. To add new content, create
a new cell before or after this one.
*/}

<details>
<summary><b>Versi paket</b></summary>

Kode di halaman ini dikembangkan menggunakan persyaratan berikut.
Kami merekomendasikan penggunaan versi ini atau yang lebih baru.

qiskit[all]~=2.3.0

</details>
{/* cspell:ignore LOCC */}

Salah satu fitur Qiskit yang paling canggih adalah kemampuannya mendukung konfigurasi perangkat yang unik. Qiskit dirancang agar tidak terikat pada penyedia perangkat keras kuantum yang kamu gunakan, dan para penyedia dapat mengonfigurasi objek `BackendV2` sesuai properti perangkat mereka yang unik. Topik ini mendemonstrasikan cara mengonfigurasi backend kamu sendiri dan melakukan transpilasi sirkuit kuantum terhadapnya.

Kamu bisa membuat objek `BackendV2` yang unik dengan geometri atau basis gate yang berbeda, lalu melakukan transpilasi sirkuit dengan mempertimbangkan konfigurasi tersebut. Contoh di bawah ini mencakup backend dengan kisi Qubit yang tidak terhubung (disjoint), yang basis gate-nya berbeda antara tepi dan bagian dalam kisi.

## Memahami antarmuka Provider, BackendV2, dan Target \{#understand-the-provider-backendv2-and-target-interfaces}

Sebelum memulai, ada baiknya memahami kegunaan dan tujuan dari objek [`Provider`](../api/qiskit/providers), [`BackendV2`](../api/qiskit/qiskit.providers.BackendV2), dan [`Target`](../api/qiskit/qiskit.transpiler.Target).

- Jika kamu memiliki perangkat kuantum atau simulator yang ingin diintegrasikan ke Qiskit SDK, kamu perlu menulis kelas `Provider` sendiri. Kelas ini memiliki satu tujuan: menyediakan objek backend yang kamu sediakan. Di sinilah semua tugas kredensial dan/atau autentikasi yang diperlukan ditangani. Setelah diinstansiasi, objek provider akan menyediakan daftar backend serta kemampuan untuk mengakuisisi/menginstansiasi backend.

- Selanjutnya, kelas backend menyediakan antarmuka antara Qiskit SDK dan perangkat keras atau simulator yang akan menjalankan Circuit. Kelas ini mencakup semua informasi yang diperlukan untuk mendeskripsikan backend ke Transpiler sehingga dapat mengoptimalkan Circuit sesuai batasannya. `BackendV2` terdiri dari empat bagian utama:
- Properti [`Target`](../api/qiskit/qiskit.transpiler.Target), yang berisi deskripsi batasan backend dan memberikan model backend untuk Transpiler
- Properti `max_circuits` yang mendefinisikan batas jumlah Circuit yang bisa dijalankan backend dalam satu job
- Metode `run()` yang menerima pengiriman job
- Sekumpulan `_default_options` untuk mendefinisikan opsi yang dapat dikonfigurasi pengguna beserta nilai defaultnya

## Membuat BackendV2 kustom \{#create-a-custom-backendv2}

Objek `BackendV2` adalah kelas abstrak yang digunakan untuk semua objek backend yang dibuat oleh provider (baik dalam `qiskit.providers` maupun pustaka lain seperti [`qiskit_ibm_runtime.IBMBackend`](../api/qiskit-ibm-runtime/ibm-backend)). Seperti yang disebutkan di atas, objek-objek ini memiliki beberapa atribut, termasuk [`Target`](https://docs.quantum.ibm.com/api/qiskit/qiskit.transpiler.Target). `Target` berisi informasi yang menentukan atribut backend β€” seperti [`Coupling Map`](https://docs.quantum.ibm.com/api/qiskit/qiskit.transpiler.CouplingMap), daftar [`Instructions`](https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.Instruction), dan lainnya β€” kepada Transpiler. Selain `Target`, kamu juga bisa mendefinisikan detail tingkat pulsa seperti [`DriveChannel`](https://docs.quantum.ibm.com/api/qiskit/1.4/qiskit.pulse.channels.DriveChannel) atau [`ControlChannel`](https://docs.quantum.ibm.com/api/qiskit/1.4/qiskit.pulse.channels.ControlChannel).

Contoh berikut mendemonstrasikan kustomisasi ini dengan membuat backend multi-chip yang disimulasikan, di mana setiap chip memiliki konektivitas heavy-hex. Contoh ini menentukan set gate dua Qubit backend menjadi [`CZGates`](../api/qiskit/qiskit.circuit.library.CZGate) di dalam setiap chip dan [`CXGates`](../api/qiskit/qiskit.circuit.library.ECRGate) antar chip. Pertama, buat `BackendV2` kamu sendiri dan sesuaikan `Target`-nya dengan gate satu dan dua Qubit sesuai batasan yang telah dijelaskan sebelumnya.

<Admonition type="tip" title="Pustaka graphviz">
Memplot coupling map memerlukan pustaka [`graphviz`](https://graphviz.org/) yang sudah terinstal.
</Admonition>

```python
import numpy as np
import rustworkx as rx

from qiskit.providers import BackendV2, Options
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import XGate, SXGate, RZGate, CZGate, ECRGate
from qiskit.circuit import Measure, Delay, Parameter, Reset
from qiskit import QuantumCircuit, transpile
from qiskit.visualization import plot_gate_map

class FakeLOCCBackend(BackendV2):
"""Fake multi chip backend."""

def __init__(self, distance=3, number_of_chips=3):
"""Instantiate a new fake multi chip backend.

Args:
distance (int): The heavy hex code distance to use for each chips'
coupling map. This number **must** be odd. The distance relates
to the number of qubits by:
:math:`n = \\frac{5d^2 - 2d - 1}{2}` where :math:`n` is the
number of qubits and :math:`d` is the ``distance``
number_of_chips (int): The number of chips to have in the multichip backend
each chip will be a heavy hex graph of ``distance`` code distance.
"""
super().__init__(name="Fake LOCC backend")
# Create a heavy-hex graph using the rustworkx library, then instantiate a new target
self._graph = rx.generators.directed_heavy_hex_graph(
distance, bidirectional=False
)
num_qubits = len(self._graph) * number_of_chips
self._target = Target(
"Fake multi-chip backend", num_qubits=num_qubits
)

# Generate instruction properties for single qubit gates and a measurement, delay,
# and reset operation to every qubit in the backend.
rng = np.random.default_rng(seed=12345678942)
rz_props = {}
x_props = {}
sx_props = {}
measure_props = {}
delay_props = {}

# Add 1q gates. Globally use virtual rz, x, sx, and measure
for i in range(num_qubits):
qarg = (i,)
rz_props[qarg] = InstructionProperties(error=0.0, duration=0.0)
x_props[qarg] = InstructionProperties(
error=rng.uniform(1e-6, 1e-4),
duration=rng.uniform(1e-8, 9e-7),
)
sx_props[qarg] = InstructionProperties(
error=rng.uniform(1e-6, 1e-4),
duration=rng.uniform(1e-8, 9e-7),
)
measure_props[qarg] = InstructionProperties(
error=rng.uniform(1e-3, 1e-1),
duration=rng.uniform(1e-8, 9e-7),
)
delay_props[qarg] = None
self._target.add_instruction(XGate(), x_props)
self._target.add_instruction(SXGate(), sx_props)
self._target.add_instruction(RZGate(Parameter("theta")), rz_props)
self._target.add_instruction(Measure(), measure_props)
self._target.add_instruction(Reset(), measure_props)

self._target.add_instruction(Delay(Parameter("t")), delay_props)
# Add chip local 2q gate which is CZ
cz_props = {}
for i in range(number_of_chips):
for root_edge in self._graph.edge_list():
offset = i * len(self._graph)
edge = (root_edge[0] + offset, root_edge[1] + offset)
cz_props[edge] = InstructionProperties(
error=rng.uniform(7e-4, 5e-3),
duration=rng.uniform(1e-8, 9e-7),
)
self._target.add_instruction(CZGate(), cz_props)

cx_props = {}
# Add interchip 2q gates which are ecr (effectively CX)
# First determine which nodes to connect
node_indices = self._graph.node_indices()
edge_list = self._graph.edge_list()
inter_chip_nodes = {}
for node in node_indices:
count = 0
for edge in edge_list:
if node == edge[0]:
count += 1
if count == 1:
inter_chip_nodes[node] = count
# Create inter-chip ecr props
cx_props = {}
inter_chip_edges = list(inter_chip_nodes.keys())
for i in range(1, number_of_chips):
offset = i * len(self._graph)
edge = (
inter_chip_edges[1] + (len(self._graph) * (i - 1)),
inter_chip_edges[0] + offset,
)
cx_props[edge] = InstructionProperties(
error=rng.uniform(7e-4, 5e-3),
duration=rng.uniform(1e-8, 9e-7),
)

self._target.add_instruction(ECRGate(), cx_props)

@property
def target(self):
return self._target

@property
def max_circuits(self):
return None

@property
def graph(self):
return self._graph

@classmethod
def _default_options(cls):
return Options(shots=1024)

def run(self, circuit, **kwargs):
raise NotImplementedError(
"This backend does not contain a run method"
)

Visualize backends​

Kamu bisa melihat grafik konektivitas dari kelas baru ini dengan metode plot_gate_map() dari modul qiskit.visualization. Metode ini, bersama dengan plot_coupling_map() dan plot_circuit_layout(), adalah alat yang berguna untuk memvisualisasikan susunan Qubit dalam sebuah Backend, serta bagaimana sebuah Circuit ditata di atas Qubit-Qubit Backend tersebut. Contoh ini membuat Backend yang terdiri dari tiga chip heavy-hex kecil. Contoh ini menentukan sekumpulan koordinat untuk mengatur posisi Qubit, serta sekumpulan warna kustom untuk membedakan Gate dua-Qubit yang berbeda.

backend = FakeLOCCBackend(3, 3)

target = backend.target
coupling_map_backend = target.build_coupling_map()

coordinates = [
(3, 1),
(3, -1),
(2, -2),
(1, 1),
(0, 0),
(-1, -1),
(-2, 2),
(-3, 1),
(-3, -1),
(2, 1),
(1, -1),
(-1, 1),
(-2, -1),
(3, 0),
(2, -1),
(0, 1),
(0, -1),
(-2, 1),
(-3, 0),
]

single_qubit_coordinates = []
total_qubit_coordinates = []

for coordinate in coordinates:
total_qubit_coordinates.append(coordinate)

for coordinate in coordinates:
total_qubit_coordinates.append(
(-1 * coordinate[0] + 1, coordinate[1] + 4)
)

for coordinate in coordinates:
total_qubit_coordinates.append((coordinate[0], coordinate[1] + 8))

line_colors = ["#adaaab" for edge in coupling_map_backend.get_edges()]
ecr_edges = []

# Get tuples for the edges which have an ecr instruction attached
for instruction in target.instructions:
if instruction[0].name == "ecr":
ecr_edges.append(instruction[1])

for i, edge in enumerate(coupling_map_backend.get_edges()):
if edge in ecr_edges:
line_colors[i] = "#000000"
print(backend.name)
plot_gate_map(
backend,
plot_directed=True,
qubit_coordinates=total_qubit_coordinates,
line_color=line_colors,
)
Fake LOCC backend

Output of the previous code cell

Setiap Qubit diberi label, dan panah berwarna merepresentasikan Gate dua-Qubit. Panah abu-abu adalah Gate CZ dan panah hitam adalah Gate CX antar-chip (yang menghubungkan Qubit 6β†’216 \rightarrow 21 dan 25β†’4025 \rightarrow 40). Arah panah menunjukkan arah default di mana Gate-Gate ini dieksekusi; panah-panah tersebut menentukan Qubit mana yang menjadi kontrol/target secara default untuk setiap kanal dua-Qubit.

Transpile terhadap Backend kustom​

Setelah Backend kustom dengan Target uniknya sendiri sudah didefinisikan, sangat mudah untuk melakukan Transpile Circuit kuantum terhadap Backend ini, karena semua batasan yang relevan (Gate basis, konektivitas Qubit, dan sebagainya) yang dibutuhkan oleh pass Transpiler sudah tersimpan dalam atribut ini. Contoh berikutnya membangun sebuah Circuit yang membuat state GHZ berukuran besar dan melakukan Transpile terhadap Backend yang sudah dibuat di atas.

from qiskit.transpiler import generate_preset_pass_manager

num_qubits = 50
ghz = QuantumCircuit(num_qubits)
ghz.h(range(num_qubits))
ghz.cx(0, range(1, num_qubits))
op_counts = ghz.count_ops()

print("Pre-Transpilation: ")
print(f"CX gates: {op_counts['cx']}")
print(f"H gates: {op_counts['h']}")
print("\n", 30 * "#", "\n")

pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
transpiled_ghz = pm.run(ghz)
op_counts = transpiled_ghz.count_ops()

print("Post-Transpilation: ")
print(f"CZ gates: {op_counts['cz']}")
print(f"ECR gates: {op_counts['ecr']}")
print(f"SX gates: {op_counts['sx']}")
print(f"RZ gates: {op_counts['rz']}")
Pre-Transpilation:
CX gates: 49
H gates: 50

##############################
Post-Transpilation:
CZ gates: 151
ECR gates: 6
SX gates: 295
RZ gates: 216

Circuit yang sudah di-Transpile sekarang mengandung campuran Gate CZ dan ECR, yang kita tentukan sebagai Gate basis dalam Target Backend. Ada juga jauh lebih banyak Gate dari sebelumnya karena perlunya menyisipkan instruksi SWAP setelah memilih layout. Di bawah ini, alat visualisasi plot_circuit_layout() digunakan untuk menentukan Qubit dan kanal dua-Qubit mana yang dipakai dalam Circuit ini.

from qiskit.visualization import plot_circuit_layout

plot_circuit_layout(
transpiled_ghz, backend, qubit_coordinates=total_qubit_coordinates
)

Output of the previous code cell

Buat Backend unik​

Paket rustworkx mengandung pustaka berbagai grafik yang besar dan memungkinkan pembuatan grafik kustom. Kode yang menarik secara visual di bawah ini membuat Backend yang terinspirasi dari toric code. Kamu kemudian bisa memvisualisasikan Backend tersebut menggunakan fungsi-fungsi dari bagian Visualize backends.

class FakeTorusBackend(BackendV2):
"""Fake multi chip backend."""

def __init__(self):
"""Instantiate a new backend that is inspired by a toric code"""
super().__init__(name="Fake LOCC backend")
graph = rx.generators.directed_grid_graph(20, 20)
for column in range(20):
graph.add_edge(column, 19 * 20 + column, None)
for row in range(20):
graph.add_edge(row * 20, row * 20 + 19, None)
num_qubits = len(graph)
rng = np.random.default_rng(seed=12345678942)
rz_props = {}
x_props = {}
sx_props = {}
measure_props = {}
delay_props = {}
self._target = Target("Fake Kookaburra", num_qubits=num_qubits)
# Add 1q gates. Globally use virtual rz, x, sx, and measure
for i in range(num_qubits):
qarg = (i,)
rz_props[qarg] = InstructionProperties(error=0.0, duration=0.0)
x_props[qarg] = InstructionProperties(
error=rng.uniform(1e-6, 1e-4),
duration=rng.uniform(1e-8, 9e-7),
)
sx_props[qarg] = InstructionProperties(
error=rng.uniform(1e-6, 1e-4),
duration=rng.uniform(1e-8, 9e-7),
)
measure_props[qarg] = InstructionProperties(
error=rng.uniform(1e-3, 1e-1),
duration=rng.uniform(1e-8, 9e-7),
)
delay_props[qarg] = None
self._target.add_instruction(XGate(), x_props)
self._target.add_instruction(SXGate(), sx_props)
self._target.add_instruction(RZGate(Parameter("theta")), rz_props)
self._target.add_instruction(Measure(), measure_props)
self._target.add_instruction(Reset(), measure_props)
self._target.add_instruction(Delay(Parameter("t")), delay_props)
cz_props = {}
for edge in graph.edge_list():
cz_props[edge] = InstructionProperties(
error=rng.uniform(7e-4, 5e-3),
duration=rng.uniform(1e-8, 9e-7),
)
self._target.add_instruction(CZGate(), cz_props)

@property
def target(self):
return self._target

@property
def max_circuits(self):
return None

@classmethod
def _default_options(cls):
return Options(shots=1024)

def run(self, circuit, **kwargs):
raise NotImplementedError("Lasciate ogne speranza, voi ch'intrate")
backend = FakeTorusBackend()
# We set `figsize` to a smaller size to make the documentation website faster
# to load. Normally, you do not need to set the argument.
plot_gate_map(backend, figsize=(4, 4))

Output of the previous code cell

num_qubits = int(backend.num_qubits / 2)
full_device_bv = QuantumCircuit(num_qubits, num_qubits - 1)
full_device_bv.x(num_qubits - 1)
full_device_bv.h(range(num_qubits))
full_device_bv.cx(range(num_qubits - 1), num_qubits - 1)
full_device_bv.h(range(num_qubits))
full_device_bv.measure(range(num_qubits - 1), range(num_qubits - 1))
tqc = transpile(full_device_bv, backend, optimization_level=3)
op_counts = tqc.count_ops()
print(f"CZ gates: {op_counts['cz']}")
print(f"X gates: {op_counts['x']}")
print(f"SX gates: {op_counts['sx']}")
print(f"RZ gates: {op_counts['rz']}")
CZ gates: 867
X gates: 18
SX gates: 1630
RZ gates: 1174
Source: IBM Quantum docs β€” updated 1 Apr 2026
English version on doQumentation β€” updated 7 Mei 2026
This translation based on the English version of 11 Mar 2026