Skip to main content
IBM Quantum Platform

사용자 정의 백엔드를 대상으로 생성 및 트랜스파일

  • 이 페이지의 코드는 다음 요구 사항을 사용하여 개발되었습니다. 다음 버전 이상을 사용하는 것이 좋습니다.

    qiskit[all]~=2.5.2
    

키스킷의 가장 강력한 기능 중 하나는 고유한 디바이스 구성을 지원하는 기능입니다. 키스킷은 사용하는 양자 하드웨어 공급자에 구애받지 않도록 구축되었으며, 공급자는 BackendV2 객체를 고유한 장치 속성에 맞게 구성할 수 있습니다. 이 주제에서는 자체 백엔드를 구성하고 이에 대해 양자 회로를 트랜스파일하는 방법을 설명합니다.

지오메트리 또는 기준 게이트가 다른 고유한 BackendV2 개체를 생성하고 이러한 구성을 염두에 두고 회로를 트랜스파일할 수 있습니다. 아래 예시는 벌크 내에서 가장자리를 따라 기준 게이트가 다른 분리된 큐비트 격자가 있는 백엔드에 대한 것입니다.


BackendV2, Target 인터페이스를 이해하십시오

시작하기 전에 사용법과 목적을 이해하는 것이 도움이 됩니다 Provider, BackendV2Target 객체의 용도와 목적을 이해하면 도움이 됩니다.

  • 키스킷 SDK에 통합하려는 양자 디바이스나 시뮬레이터가 있는 경우, 직접 Provider 클래스를 작성해야 합니다. 이 클래스는 사용자가 제공하는 백엔드 객체를 가져오는 단일 용도로 사용됩니다. 여기에서 필요한 자격 증명 및/또는 인증 작업이 처리됩니다. 인스턴스화되면 공급자 객체는 백엔드 목록과 백엔드를 획득/인스턴스하는 기능을 제공합니다.

  • 다음으로, 백엔드 클래스는 키스킷 SDK와 회로를 실행할 하드웨어 또는 시뮬레이터 사이의 인터페이스를 제공합니다. 여기에는 트랜스파일러가 제약 조건에 따라 모든 회로를 최적화할 수 있도록 백엔드를 설명하는 데 필요한 모든 정보가 포함되어 있습니다. BackendV2 은 크게 네 부분으로 구성되어 있습니다:

    • A Target 프로퍼티는 백엔드의 제약 조건에 대한 설명을 포함하고 트랜스파일러에 대한 백엔드 모델을 제공합니다
    • 백엔드가 단일 작업에서 실행할 수 있는 회로 수 제한을 정의하는 max_circuits 속성입니다
    • 작업 제출을 수락하는 run() 메서드
    • 사용자 구성 가능 옵션과 기본값을 정의하는 _default_options 집합입니다

BackendV2 사용자 정의 생성

BackendV2 객체는 공급자가 생성한 모든 백엔드 객체에 사용되는 추상 클래스입니다( qiskit.providers 또는 다음과 같은 다른 라이브러리 내에서) qiskit_ibm_runtime.IBMBackend). 위에서 언급했듯이 이러한 객체에는 다음과 같은 여러 속성이 포함되어 있습니다 Target. Target 에는 백엔드의 속성을 지정하는 정보(예 Coupling Map, 목록 Instructions등의 속성을 트랜스파일러에 전달합니다. Target 외에도 다음과 같은 펄스 수준 세부 정보를 정의할 수도 있습니다 DriveChannel 또는 ControlChannel.

다음 예제에서는 각 칩이 16진수 연결성을 갖는 시뮬레이션된 멀티칩 백엔드를 생성하여 이 사용자 지정을 시연합니다. 이 예제에서는 백엔드의 2큐비트 게이트가 각 칩과 CZGates 각 칩 내 및 CXGates 칩 사이를 지정합니다. 먼저, 자체 BackendV2 를 생성하고 앞서 설명한 제약 조건에 따라 단일 및 2쿼비트 게이트로 Target 을 사용자 지정합니다.

그래프 시각화 라이브러리

커플링 맵을 플로팅하려면 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개의 작은 16진수 칩이 포함된 백엔드를 생성합니다. 큐비트를 배열하는 좌표 세트와 서로 다른 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

각 큐비트에는 레이블이 지정되어 있으며, 컬러 화살표는 두 개의 큐비트 게이트를 나타냅니다. 회색 화살표는 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: 204
ECR gates: 8
SX gates: 374
RZ gates: 215

트랜스파일된 회로에는 이제 및 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 패키지에는 다양한 그래프의 대규모 라이브러리가 포함되어 있으며 사용자 정의 그래프를 만들 수 있습니다. 아래의 시각적으로 흥미로운 코드는 토릭 코드에서 영감을 받은 백엔드를 생성합니다. 그런 다음 백엔드 시각화 섹션의 함수를 사용하여 백엔드를 시각화할 수 있습니다.

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: 524
X gates: 4
SX gates: 1091
RZ gates: 1065
이 페이지가 도움이 되었습니까?
GitHub에서 버그, 오타를 보고하거나 컨텐츠를 요청하십시오.