Skip to main content
IBM Quantum Platform

트랜스파일러 패스에서 DAG 작업

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

이 가이드에서는 DAG로 작업하고 이를 사용하여 커스텀 트랜스파일러 패스를 작성하는 방법을 설명합니다. 간단한 회로를 구축하고 DAG 표현을 살펴보는 것으로 시작한 다음, 기본 DAG 연산을 살펴보고 사용자 지정 BasicMapper 패스를 구현합니다.


회로를 구축하고 그 DAG를 조사하라

아래 코드 스니펫은 벨 상태를 준비하고 측정 결과에 따라 RZR_Z 회전을 적용하는 간단한 회로를 생성하여 DAG를 설명합니다.

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

    qiskit[all]~=2.5.2
    
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.converters import circuit_to_dag
from qiskit.visualization import circuit_drawer
from qiskit.visualization.dag_visualization import dag_drawer

# Create circuit
q = QuantumRegister(3, "q")
c = ClassicalRegister(3, "c")
circ = QuantumCircuit(q, c)
circ.h(q[0])
circ.cx(q[0], q[1])
circ.measure(q[0], c[0])

# Qiskit 2.0 uses if_test instead of c_if
with circ.if_test((c, 2)):
    circ.rz(0.5, q[1])

circuit_drawer(circ, output="mpl")

Output:

Output of the previous code cell

DAG에는 쿼비트/클릭비트 입력 노드(녹색), 연산 노드(파란색), 출력 노드(빨간색) 등 세 가지 종류의 그래프 노드가 있습니다. 각 에지는 두 노드 간의 데이터 흐름(또는 종속성)을 나타냅니다. qiskit.tools.visualization.dag_drawer () 함수를 사용하여 이 회로의 DAG를 확인합니다. ( 그래프 시각화 라이브러리를 설치하여 실행합니다.)

# Convert to DAG
dag = circuit_to_dag(circ)
dag_drawer(dag)

Output:

Output of the previous code cell

기본 DAG 연산

아래 코드 예시는 노드 액세스, 연산 추가, 서브회로 대체 등 DAG를 사용한 일반적인 작업을 보여줍니다. 이러한 작업은 트랜스파일러 패스를 구축하기 위한 토대가 됩니다.


DAG 내의 모든 작업 노드 가져오기

op_nodes() 메서드는 회로에 있는 DAGOpNode 객체의 이터러블 목록을 반환합니다:

dag.op_nodes()

Output:

[DAGOpNode(op=Instruction(name='h', num_qubits=1, num_clbits=0, params=[]), qargs=(<Qubit register=(3, "q"), index=0>,), cargs=()),
 DAGOpNode(op=Instruction(name='cx', num_qubits=2, num_clbits=0, params=[]), qargs=(<Qubit register=(3, "q"), index=0>, <Qubit register=(3, "q"), index=1>), cargs=()),
 DAGOpNode(op=Instruction(name='measure', num_qubits=1, num_clbits=1, params=[]), qargs=(<Qubit register=(3, "q"), index=0>,), cargs=(<Clbit register=(3, "c"), index=0>,)),
 DAGOpNode(op=Instruction(name='if_else', num_qubits=1, num_clbits=3, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7f0a4e275490>, None]), qargs=(<Qubit register=(3, "q"), index=1>,), cargs=(<Clbit register=(3, "c"), index=0>, <Clbit register=(3, "c"), index=1>, <Clbit register=(3, "c"), index=2>))]

각 노드는 DAGOpNode 클래스의 인스턴스입니다:

node = dag.op_nodes()[3]
print("node name:", node.name)
print("op:", node.op)
print("qargs:", node.qargs)
print("cargs:", node.cargs)
print("condition:", node.op.condition)

Output:

node name: if_else
op: Instruction(name='if_else', num_qubits=1, num_clbits=3, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7f0a4e1b0650>, None])
qargs: (<Qubit register=(3, "q"), index=1>,)
cargs: (<Clbit register=(3, "c"), index=0>, <Clbit register=(3, "c"), index=1>, <Clbit register=(3, "c"), index=2>)
condition: (ClassicalRegister(3, 'c'), 2)

작업 추가하기

apply_operation_back() 메서드를 사용하여 DAGCircuit의 끝에 연산이 추가됩니다. 회로의 모든 기존 연산이 끝난 후 지정된 게이트가 지정된 큐비트에 작동하도록 추가합니다.

from qiskit.circuit.library import HGate

dag.apply_operation_back(HGate(), qargs=[q[0]])
dag_drawer(dag)

Output:

Output of the previous code cell

전면에 작전 추가

apply_operation_front() 메서드를 사용하여 DAGCircuit의 시작 부분에 연산이 추가됩니다. 이렇게 하면 회로의 모든 기존 연산 앞에 지정된 게이트가 삽입되어 효과적으로 첫 번째 연산이 실행됩니다.

from qiskit.circuit.library import CCXGate

dag.apply_operation_front(CCXGate(), qargs=[q[0], q[1], q[2]])
dag_drawer(dag)

Output:

Output of the previous code cell

노드를 서브회로로 대체하다

DAGCircuit에서 특정 연산을 나타내는 노드는 하위 회로로 대체됩니다. 먼저, 원하는 게이트 시퀀스로 새로운 하위 DAG를 구성한 다음 substitute_node_with_dag() 을 사용하여 대상 노드를 이 하위 DAG로 대체하여 나머지 회로와의 연결을 유지합니다.

from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit.library import CHGate, U2Gate, CXGate

# Build sub-DAG
mini_dag = DAGCircuit()
p = QuantumRegister(2, "p")
mini_dag.add_qreg(p)
mini_dag.apply_operation_back(CHGate(), qargs=[p[1], p[0]])
mini_dag.apply_operation_back(U2Gate(0.1, 0.2), qargs=[p[1]])

# Replace CX with mini_dag
cx_node = dag.op_nodes(op=CXGate).pop()
dag.substitute_node_with_dag(cx_node, mini_dag, wires=[p[0], p[1]])
dag_drawer(dag)

Output:

Output of the previous code cell

모든 변환이 완료되면 DAG를 일반 QuantumCircuit 객체로 다시 변환할 수 있습니다. 이것이 트랜스파일러 파이프라인이 작동하는 방식입니다. 회로를 가져와서 DAG 형태로 처리하고 변환된 회로를 출력으로 생성합니다.

from qiskit.converters import dag_to_circuit

new_circ = dag_to_circuit(dag)
circuit_drawer(new_circ, output="mpl")

Output:

Output of the previous code cell

BasicMapper 패스 구현

DAG 구조는 트랜스파일러 패스를 작성하는 데 활용할 수 있습니다. 아래 예시에서는 BasicMapper 패스를 구현하여 큐비트 연결이 제한된 장치에 임의의 회로를 매핑합니다. 추가 지침은 사용자 지정 트랜스파일러 패스 작성 가이드를 참조하세요.

패스는 TransformationPass 로 정의되며, 이는 회로를 수정한다는 의미입니다. 이는 DAG를 레이어별로 순회하며 각 명령어가 디바이스의 커플링 맵에 의해 부과된 제약 조건을 충족하는지 확인하는 방식으로 이루어집니다. 위반이 감지되면 스왑 경로가 결정되고 그에 따라 필요한 스왑 게이트가 삽입됩니다.

트랜스파일러 패스를 만들 때 첫 번째 결정은 패스를 TransformationPass 에서 상속할지 AnalysisPass 에서 상속할지 선택하는 것입니다. 변환 패스는 회로를 수정하기 위해 설계된 반면, 분석 패스는 후속 패스에서 사용할 정보를 추출하기 위한 용도로만 사용됩니다. 그런 다음 주요 기능은 run(dag) 메서드에서 구현됩니다. 마지막으로 qiskit.transpiler.passes 모듈에 패스를 등록해야 합니다.

이 특정 패스에서 DAG는 레이어별로 탐색됩니다(각 레이어에는 분리된 큐비트 집합에 작용하는 연산이 포함되어 있으므로 독립적으로 실행될 수 있음). 각 연산에 대해 커플링 맵 제약 조건이 충족되지 않으면 적절한 스왑 경로를 식별하고 필요한 스왑을 삽입하여 관련된 큐비트를 인접하게 만듭니다.

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler import Layout
from qiskit.circuit.library import SwapGate


class BasicSwap(TransformationPass):
    def __init__(self, coupling_map, initial_layout=None):
        super().__init__()
        self.coupling_map = coupling_map
        self.initial_layout = initial_layout

    def run(self, dag):
        new_dag = DAGCircuit()
        for qreg in dag.qregs.values():
            new_dag.add_qreg(qreg)
        for creg in dag.cregs.values():
            new_dag.add_creg(creg)

        if self.initial_layout is None:
            self.initial_layout = Layout.generate_trivial_layout(
                *dag.qregs.values()
            )

        current_layout = self.initial_layout.copy()

        for layer in dag.serial_layers():
            subdag = layer["graph"]
            for gate in subdag.two_qubit_ops():
                q0, q1 = gate.qargs
                p0 = current_layout[q0]
                p1 = current_layout[q1]

                if self.coupling_map.distance(p0, p1) != 1:
                    path = self.coupling_map.shortest_undirected_path(p0, p1)
                    for i in range(len(path) - 2):
                        wire1, wire2 = path[i], path[i + 1]
                        qubit1 = current_layout[wire1]
                        qubit2 = current_layout[wire2]
                        new_dag.apply_operation_back(
                            SwapGate(), qargs=[qubit1, qubit2]
                        )
                        current_layout.swap(wire1, wire2)

            new_dag.compose(
                subdag, qubits=current_layout.reorder_bits(new_dag.qubits)
            )

        return new_dag

이제 작은 예제 회로에서 패스를 테스트할 수 있습니다. 새로 정의된 패스가 포함된 패스 관리자가 구성됩니다. 그런 다음 예제 회로를 이 패스 관리자에게 제공하면 변환된 새로운 회로가 출력으로 얻어집니다.

from qiskit.transpiler import CouplingMap, PassManager
from qiskit import QuantumRegister, QuantumCircuit

q = QuantumRegister(7, "q")
in_circ = QuantumCircuit(q)
in_circ.h(q[0])
in_circ.cx(q[0], q[4])
in_circ.cx(q[2], q[3])
in_circ.cx(q[6], q[1])
in_circ.cx(q[5], q[0])
in_circ.rz(0.1, q[2])
in_circ.cx(q[5], q[0])

coupling = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6]]
coupling_map = CouplingMap(couplinglist=coupling)

pm = PassManager()
pm.append(BasicSwap(coupling_map))

out_circ = pm.run(in_circ)

in_circ.draw(output="mpl")
out_circ.draw(output="mpl")

Output:

Output of the previous code cell

다음 단계

권장사항
이 페이지가 도움이 되었습니까?
GitHub에서 버그, 오타를 보고하거나 컨텐츠를 요청하십시오.