주 콘텐츠로 건너뛰기

커스텀 Transpiler 패스 작성하기

패키지 버전

이 페이지의 코드는 다음 요구 사항을 사용하여 개발되었습니다. 이 버전 이상을 사용하길 권장합니다.

qiskit[all]~=2.3.0

Qiskit SDK를 사용하면 커스텀 트랜스파일 패스를 만들고 PassManager 객체에서 실행하거나 StagedPassManager에 추가할 수 있습니다. 여기서는 Transpiler 패스를 작성하는 방법을 시연하며, 양자 Circuit의 잡음 있는 양자 Gate에 Pauli 트월링을 수행하는 패스를 구축하는 데 초점을 맞춥니다. 이 예제에서는 TransformationPass 유형의 패스가 조작하는 객체인 DAG를 사용합니다.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit

배경: DAG 표현

패스를 구축하기 전에, Qiskit에서 양자 Circuit의 내부 표현인 방향성 비순환 그래프(DAG)를 소개하는 것이 중요합니다 (개요는 이 튜토리얼을 참조하세요). 이 단계를 따르려면 DAG 플로팅 함수를 위해 graphviz 라이브러리를 설치하세요.

Qiskit에서 트랜스파일 단계 내에서 Circuit은 DAG를 사용하여 표현됩니다. 일반적으로 DAG는 정점(노드라고도 함)과 특정 방향으로 정점 쌍을 연결하는 방향성 간선으로 구성됩니다. 이 표현은 개별 DagNode 객체로 구성된 qiskit.dagcircuit.DAGCircuit 객체를 사용하여 저장됩니다. 순수한 Gate 목록(즉, 넷리스트)에 비해 이 표현의 장점은 연산 간의 정보 흐름이 명시적이어서 변환 결정을 내리기가 더 쉽다는 것입니다.

이 예제는 Bell 상태를 준비하고 측정 결과에 따라 RZR_Z 회전을 적용하는 간단한 Circuit을 만들어 DAG를 설명합니다.

  from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
import numpy as np

qr = QuantumRegister(3, 'qr')
cr = ClassicalRegister(3, 'cr')
qc = QuantumCircuit(qr, cr)

qc.h(qr[0])
qc.cx(qr[0], qr[1])
qc.measure(qr[0], cr[0])
qc.rz(np.pi/2, qr[1]).c_if(cr, 2)
qc.draw(output='mpl')

Bell 상태를 준비하고 측정 결과에 따라 R_Z 회전을 적용하는 Circuit.

qiskit.tools.visualization.dag_drawer() 함수를 사용하여 이 Circuit의 DAG를 확인하세요. 그래프 노드에는 세 가지 종류가 있습니다: Qubit/클비트 노드(초록색), 연산 노드(파란색), 출력 노드(빨간색). 각 간선은 두 노드 간의 데이터 흐름(또는 의존성)을 나타냅니다.

from qiskit.converters import circuit_to_dag
from qiskit.tools.visualization import dag_drawer

dag = circuit_to_dag(qc)
dag_drawer(dag)

Circuit의 DAG는 방향성 간선으로 연결된 노드로 구성됩니다. 이는 Qubit 또는 클래식 비트, 연산, 데이터 흐름 방식을 시각적으로 표현하는 방법입니다.

Transpiler 패스

Transpiler 패스는 AnalysisPass 또는 TransformationPass로 분류됩니다. 패스는 일반적으로 DAG와 분석 패스에 의해 결정된 속성을 저장하는 딕셔너리 유사 객체인 property_set으로 작동합니다. 분석 패스는 DAG와 그 property_set 모두에서 작동합니다. DAG를 수정할 수 없지만 property_set은 수정할 수 있습니다. 이는 DAG를 수정하고 property_set을 읽을 수 있지만 쓸 수는 없는 변환 패스와 대조됩니다. 예를 들어, 변환 패스는 Circuit을 해당 ISA로 변환하거나 필요한 경우 SWAP Gate를 삽입하는 라우팅 패스를 수행합니다.

PauliTwirl Transpiler 패스 만들기

다음 예제는 Pauli 트월을 추가하는 Transpiler 패스를 구성합니다. Pauli 트월링은 Qubit이 잡음 채널을 경험하는 방식을 무작위화하는 오류 억제 전략입니다. 이 예제에서는 두 Qubit Gate가 단일 Qubit Gate보다 훨씬 오류가 많기 때문에 두 Qubit Gate를 잡음 채널로 가정합니다. Pauli 트월은 두 Qubit Gate의 동작에 영향을 미치지 않습니다. 두 Qubit Gate 이전(왼쪽)에 적용되는 것들이 두 Qubit Gate 이후(오른쪽)에 적용되는 것들에 의해 상쇄되도록 선택됩니다. 이런 의미에서 두 Qubit 연산은 동일하지만 수행 방식은 다릅니다. Pauli 트월링의 한 가지 이점은 일관된 오류를 확률적 오류로 변환한다는 것이며, 이는 더 많은 평균화를 통해 개선될 수 있습니다.

Transpiler 패스는 DAG에서 작동하므로 재정의해야 할 중요한 메서드는 DAG를 입력으로 받는 .run()입니다. 표시된 것처럼 Pauli 쌍을 초기화하면 각 두 Qubit Gate의 동작이 보존됩니다. 이는 각 두 Qubit Pauli(pauli_basis(2)에서 얻은)를 반복하고 연산을 보존하는 다른 Pauli를 찾는 헬퍼 메서드 build_twirl_set으로 수행됩니다.

DAG에서 op_nodes() 메서드를 사용하여 모든 노드를 반환합니다. DAG는 또한 Qubit에서 중단 없이 실행되는 노드 시퀀스인 런(run)을 수집하는 데 사용할 수 있습니다. 이것들은 collect_1q_runs로 단일 Qubit 런으로, collect_2q_runs로 두 Qubit 런으로, collect_runs로 이름이 네임리스트에 있는 노드의 런으로 수집할 수 있습니다. DAGCircuit에는 그래프를 검색하고 순회하는 많은 메서드가 있습니다. 일반적으로 사용되는 메서드는 의존성 순서로 노드를 제공하는 topological_op_nodes입니다. bfs_successors와 같은 다른 메서드는 주로 DAG에서 노드가 후속 연산과 상호 작용하는 방식을 결정하는 데 사용됩니다.

이 예제에서는 명령을 나타내는 각 노드를 미니 DAG로 구축된 서브회로로 교체하고자 합니다. 미니 DAG에는 두 Qubit 양자 레지스터가 추가되어 있습니다. 연산은 apply_operation_back을 사용하여 미니 DAG에 추가되며, 이는 미니 DAG의 출력에 Instruction을 배치합니다(apply_operation_front는 미니 DAG의 입력에 배치합니다). 그런 다음 노드는 substitute_node_with_dag를 사용하여 미니 DAG로 대체되고, 프로세스는 DAG의 각 CXGateECRGate 인스턴스에 대해 계속됩니다(IBM® Backend의 두 Qubit 기저 Gate에 해당).

from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit import QuantumCircuit, QuantumRegister, Gate
from qiskit.circuit.library import CXGate, ECRGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.quantum_info import Operator, pauli_basis

import numpy as np

from typing import Iterable, Optional
class PauliTwirl(TransformationPass):
"""Add Pauli twirls to two-qubit gates."""

def __init__(
self,
gates_to_twirl: Optional[Iterable[Gate]] = None,
):
"""
Args:
gates_to_twirl: Names of gates to twirl. The default behavior is to twirl all
two-qubit basis gates, `cx` and `ecr` for IBM backends.
"""
if gates_to_twirl is None:
gates_to_twirl = [CXGate(), ECRGate()]
self.gates_to_twirl = gates_to_twirl
self.build_twirl_set()
super().__init__()

def build_twirl_set(self):
"""
Build a set of Paulis to twirl for each gate and store internally as .twirl_set.
"""
self.twirl_set = {}

# iterate through gates to be twirled
for twirl_gate in self.gates_to_twirl:
twirl_list = []

# iterate through Paulis on left of gate to twirl
for pauli_left in pauli_basis(2):
# iterate through Paulis on right of gate to twirl
for pauli_right in pauli_basis(2):
# save pairs that produce identical operation as gate to twirl
if (Operator(pauli_left) @ Operator(twirl_gate)).equiv(
Operator(twirl_gate) @ pauli_right
):
twirl_list.append((pauli_left, pauli_right))

self.twirl_set[twirl_gate.name] = twirl_list

def run(
self,
dag: DAGCircuit,
) -> DAGCircuit:
# collect all nodes in DAG and proceed if it is to be twirled
twirling_gate_classes = tuple(
gate.base_class for gate in self.gates_to_twirl
)
for node in dag.op_nodes():
if not isinstance(node.op, twirling_gate_classes):
continue

# random integer to select Pauli twirl pair
pauli_index = np.random.randint(
0, len(self.twirl_set[node.op.name])
)
twirl_pair = self.twirl_set[node.op.name][pauli_index]

# instantiate mini_dag and attach quantum register
mini_dag = DAGCircuit()
register = QuantumRegister(2)
mini_dag.add_qreg(register)

# apply left Pauli, gate to twirl, and right Pauli to empty mini-DAG
mini_dag.apply_operation_back(
twirl_pair[0].to_instruction(), [register[0], register[1]]
)
mini_dag.apply_operation_back(node.op, [register[0], register[1]])
mini_dag.apply_operation_back(
twirl_pair[1].to_instruction(), [register[0], register[1]]
)

# substitute gate to twirl node with twirling mini-DAG
dag.substitute_node_with_dag(node, mini_dag)

return dag

PauliTwirl Transpiler 패스 사용하기

다음 코드는 위에서 만든 패스를 사용하여 Circuit을 트랜스파일합니다. cxecr Gate가 있는 간단한 Circuit을 고려해 보세요.

qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.ecr(1, 2)
qc.ecr(1, 0)
qc.cx(2, 1)
qc.draw("mpl")

이전 코드 셀의 출력

커스텀 패스를 적용하려면 PauliTwirl 패스를 사용하여 패스 매니저를 구축하고 50개의 Circuit에서 실행합니다.

pm = PassManager([PauliTwirl()])
twirled_qcs = [pm.run(qc) for _ in range(50)]

이제 각 두 Qubit Gate는 두 Pauli 사이에 끼워져 있습니다.

twirled_qcs[-1].draw("mpl")

이전 코드 셀의 출력

qiskit.quantum_infoOperator를 사용하면 연산자가 동일합니다:

np.all([Operator(twirled_qc).equiv(qc) for twirled_qc in twirled_qcs])
np.True_

다음 단계

권장 사항