주 콘텐츠로 건너뛰기

나만의 Transpiler 패스 작성하기

패키지 버전

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

qiskit[all]~=2.5.2

Qiskit SDK를 사용하면 사용자 정의 트랜스파일 패스를 만들어 PassManager 객체에서 실행하거나 StagedPassManager에 추가할 수 있습니다. 여기서는 트랜스파일러 패스를 작성하는 방법을 보여드리며, 양자 회로의 노이즈가 있는 양자 게이트에 대해 Pauli twirling을 수행하는 패스를 만드는 데 중점을 둡니다. 이 예제는 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])
with qc.if_test((cr, 2)):
qc.rz(np.pi/2, qr[1])
qc.draw(output='mpl')

Circuit preparing a Bell state and applying an R_Z rotation depending on the measurement outcome.

이 Circuit의 DAG를 보려면 qiskit.tools.visualization.dag_drawer() 함수를 사용하세요. 세 가지 종류의 그래프 노드가 있습니다: qubit/clbit 노드(녹색), 연산 노드(파란색), 출력 노드(빨간색). 각 엣지는 두 노드 간의 데이터 흐름(또는 종속성)을 나타냅니다.

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

dag = circuit_to_dag(qc)
dag_drawer(dag)

The circuit's DAG consists of nodes that are connected by directional edges. It is a visual way to represent qubits or classical bits, the operations, and the way that data flows.

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 트월링의 한 가지 이점은 결맞음 오류(coherent error)를 확률적 오류로 변환한다는 것이며, 이는 더 많은 평균화를 통해 개선될 수 있습니다.

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의 각 CXGate 및 ECRGate 인스턴스에 대해 계속됩니다(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을 트랜스파일합니다. cx 및 ecr 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_info의 Operator를 사용하면 연산자가 동일합니다:

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

다음 단계​

권장 사항