Skip to main content
IBM Quantum Platform

カスタムバックエンドに対して作成およびトランスパイルする

  • このページのコードは、以下の要件に基づいて開発された。 これらのバージョンまたは新しいバージョンの使用をお勧めします。

    qiskit[all]~=2.5.1
    

Qiskitのより強力な特徴の一つは、ユニークなデバイス構成をサポートする能力である。 Qiskitは、使用する量子ハードウェアのプロバイダに依存しないように構築されており、プロバイダは、 BackendV2 オブジェクトを独自のデバイスプロパティに設定することができます。 このトピックでは、独自のバックエンドを設定し、それに対して量子回路をトランスパイルする方法を示す。

異なるジオメトリや基底ゲートを持つユニークな BackendV2 オブジェクトを作成し、それらの構成を念頭に置いて回路をトランスパイルすることができます。 以下の例は、基底ゲートがバルク内とエッジに沿って異なる、不連続な量子ビット格子を持つバックエンドをカバーしている。


プロバイダー、 BackendV2、およびターゲットインターフェースを理解する

の使い方と目的を理解しておくとよい。 Provider, BackendV2および Target オブジェクトを理解するのに役立つ。

  • Qiskit SDKに統合したい量子デバイスやシミュレータがある場合は、独自の Provider クラスを書く必要があります。 このクラスの目的はただ一つ、あなたが提供するバックエンド・オブジェクトを取得することです。 ここでは、必要なクレデンシャルや認証タスクが処理される。 インスタンス化されると、プロバイダオブジェクトはバックエンドのリストを提供し、バックエンドを獲得/インスタンス化する機能も提供する。

  • 次に、バックエンドクラスは、Qiskit SDKと回路を実行するハードウェアやシミュレータとのインターフェースを提供します。 トランスパイラにバックエンドを記述するために必要な情報がすべて含まれているので、トランスパイラは制約に従って回路を最適化することができる。 BackendV2 は4つの主要部分から構成されている:

    • A Target プロパティ。バックエンドの制約の説明を含み、トランスパイラにバックエンドのモデルを提供する
    • max_circuits 、バックエンドが1つのジョブで実行できる回路数の上限を定義するプロパティ
    • ジョブ投入を受け付ける run() メソッド
    • _default_options ユーザーが設定可能なオプションとそのデフォルト値を定義するためのセット

カスタム BackendV2 を作成する

BackendV2 オブジェクトは、プロバイダ( qiskit.providers 内または qiskit_ibm_runtime.IBMBackend). 上述したように、これらのオブジェクトにはいくつかの属性があります。 Target. Target には、バックエンドの属性を指定する情報が含まれている。 Coupling Mapのリスト Instructionsなどのバックエンドの属性を指定する情報が含まれています。 Targetのようなパルスレベルの詳細も定義できる。 DriveChannel または ControlChannel.

以下の例では、各チップがヘビーヘックス接続を持つマルチチップ・バックエンドをシミュレートすることで、このカスタマイズを実証している。 この例では、バックエンドの2量子ビット・ゲート・セットを各チップ内で CZGates 各チップ内と CXGates チップ間である。 まず、独自の BackendV2 を作成し、その Target を、先に説明した制約に従ってシングルおよび2量子ビットゲートでカスタマイズする。

グラフビズ・ライブラリ

カップリング・マップの作図には graphviz ライブラリがインストールされている必要があります。

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"
        )

バックエンドを可視化する

この新しいクラスの接続グラフは、 モジュールの plot_gate_map()qiskit.visualization メソッドで見ることができます。 この方法は plot_coupling_map()plot_circuit_layout()とともに、バックエンドの量子ビットの配置や、バックエンドの量子ビットにまたがって回路がどのように配置されているかを視覚化するのに役立つツールです。 この例では、3つの小さなヘビーヘックス・チップを含むバックエンドを作成する。 これは、量子ビットを配置する座標のセットと、異なる2量子ビットゲートのカスタムカラーのセットを指定する。

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,
)

Output:

Fake LOCC backend
Output of the previous code cell

それぞれの量子ビットにはラベルが付けられ、色の付いた矢印は2量子ビットのゲートを表している。 グレーの矢印はCZゲート、黒の矢印はチップ間CXゲート(これらは量子ビット 6216 \rightarrow 21254025 \rightarrow 40 を接続している)。 矢印の方向は、これらのゲートが実行されるデフォルトの方向を示し、各2量子ビット・チャンネルのデフォルトでどの量子ビットが制御/ターゲットになるかを指定します。


カスタムバックエンドに対してトランスパイルする

を持つカスタムバックエンドが定義されました。 Target が定義されたので、このバックエンドに対して量子回路をトランスパイルするのは簡単です。なぜなら、トランスパイラ・パスに必要なすべての関連制約(基底ゲート、量子ビットの接続性など)がこの属性に含まれているからです。 次の例では、大きなGHZステートを作成する回路を構築し、上で構築したバックエンドに対してそれをトランスパイルする。

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']}")

Output:

Pre-Transpilation: 
CX gates: 49
H gates: 50

 ############################## 

Post-Transpilation: 
CZ gates: 207
ECR gates: 8
SX gates: 379
RZ gates: 211

トランスパイルされた回路には現在、 ECR``CZ とゲートが混在しています。これらはバックエンドので基本ゲート Targetとして指定したものです。 レイアウト選択後にSWAP命令を挿入する必要があるため、開始時よりもゲートがかなり多くなっています。 以下では、可視 plot_circuit_layout() 化ツールを用いて、この回路で使用された量子ビットと2量子ビットチャネルを特定します。

from qiskit.visualization import plot_circuit_layout

plot_circuit_layout(
    transpiled_ghz, backend, qubit_coordinates=total_qubit_coordinates
)

Output:

Output of the previous code cell

独自のバックエンドを作成する

rustworkx パッケージには、さまざまなグラフの大規模なライブラリーが含まれており、カスタムグラフの作成が可能です。 以下のビジュアル的に興味深いコードは、トーリックコードにインスパイアされたバックエンドを作成する。 そして、 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:

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']}")

Output:

CZ gates: 580
X gates: 129
SX gates: 936
RZ gates: 840
このページは役に立ちましたか?
バグや誤字の報告、またはコンテンツの要求はGitHubで行ってください。