メインコンテンツへスキップ

トランスパイラーパスでDAGを使う

Qiskitでは、トランスパイルの各ステージにおいて回路はDAGを使って表現されます。一般にDAGは、頂点(ノードとも呼ばれます)と、頂点のペアを特定の向きで結ぶ有向エッジで構成されます。この表現は、個々のDagNodeオブジェクトから成るqiskit.dagcircuit.DAGCircuitオブジェクトとして格納されます。純粋なゲートのリスト(ネットリスト)と比べたこの表現の利点は、演算間の情報の流れが明示されているため、変換の判断がしやすい点です。

このガイドでは、DAGを扱いカスタムトランスパイラーパスを記述する方法を説明します。まずシンプルな回路を構築してそのDAG表現を確認し、続いて基本的なDAG操作を行い、最後にカスタムのBasicMapperパスを実装します。

回路を構築してDAGを確認する

以下のコードスニペットでは、ベル状態を準備して測定結果に応じてRZR_Z回転を適用するシンプルな回路を作成し、DAGを示します。

パッケージのバージョン

このページのコードは以下の要件を使って開発されました。 これらのバージョン以降を使用することをお勧めします。

qiskit[all]~=2.3.0
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit
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 of the previous code cell

DAGにはグラフノードが3種類あります:量子ビット/古典ビットの入力ノード(緑)、演算ノード(青)、出力ノード(赤)です。各エッジはノード間のデータの流れ(依存関係)を示します。qiskit.tools.visualization.dag_drawer()関数を使って、この回路のDAGを確認できます。(実行するにはGraphvizライブラリをインストールしてください。)

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

Output of the previous code cell

基本的なDAG操作

以下のコード例では、ノードへのアクセス、演算の追加、サブ回路の置換など、DAGに対する一般的な操作を示します。これらの操作は、トランスパイラーパスを構築するための基盤となります。

DAG内のすべての演算ノードを取得する

op_nodes()メソッドは、回路内のDAGOpNodeオブジェクトのイテラブルリストを返します:

dag.op_nodes()
[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 0x7f912f47db10>, 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)
node name: if_else
op: Instruction(name='if_else', num_qubits=1, num_clbits=3, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7f912f4ceed0>, 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 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 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 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 of the previous code cell

BasicMapperパスを実装する

DAG構造をトランスパイラーパスの記述に活用できます。以下の例では、任意の回路をQubitの接続が制限されたデバイスにマッピングするBasicMapperパスを実装します。詳細については、カスタムトランスパイラーパスの記述に関するガイドを参照してください。

このパスはTransformationPassとして定義されており、回路を変更します。DAGをレイヤーごとに走査し、各命令がデバイスのカップリングマップによる制約を満たしているかどうかを確認します。違反が検出された場合は、SWAPパスを決定して必要なSWAPゲートを挿入します。

トランスパイラーパスを作成する際の最初の判断は、TransformationPassAnalysisPassのどちらを継承するかです。変換パスは回路を変更するために設計されており、解析パスは後続のパスが使用するための情報を抽出することのみを目的としています。主な機能はrun(dag)メソッドに実装します。最後に、パスをqiskit.transpiler.passesモジュールに登録する必要があります。

このパスでは、DAGをレイヤーごとに走査します(各レイヤーには互いに素なQubitセットに作用する演算が含まれており、独立して実行できます)。各演算について、カップリングマップの制約が満たされていない場合は、適切なSWAPパスを特定し、関連するQubitを隣接させるために必要なSWAPを挿入します。

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 of the previous code cell

次のステップ