トランスパイラパスにおけるDAGの操作
Qiskitでは、トランスパイルステージにおいて、回路はDAGを用いて表現される。 一般にDAGは、頂点(「ノード」とも呼ばれる)と、頂点同士を特定の向きで結ぶ有向辺で構成される。 この表現は、個々の DagNode オブジェクトで構成される qiskit.dagcircuit.DAGCircuit オブジェクトを使って保存される。 ゲートの純粋なリスト(つまりネットリスト)よりもこの表現の利点は、演算間の情報の流れが明示的であるため、変換の決定が容易になることである。
このガイドでは、DAGを使用してカスタム・トランスパイラ・パスを作成する方法を説明します。 簡単な回路を構築し、そのDAG表現を調べることから始め、基本的なDAG操作について調べ、カスタム BasicMapper パスを実装する。
回路を構築し、その有向非巡回グラフ(DAG)を調べる
以下のコード・スニペットは、測定結果に応じてベル状態を準備し、 回転を適用する単純な回路を作成することで、DAGを説明している。
このページのコードは、以下の要件に基づいて開発された。 これらのバージョンまたは新しいバージョンの使用をお勧めします。
qiskit[all]~=2.5.1
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:
DAGでは、3種類のグラフノードがある:qubit/clbit入力ノード(緑)、演算ノード(青)、出力ノード(赤)。 各エッジは、2つのノード間のデータの流れ(または依存関係)を示す。 この回路のDAGを表示するには、 qiskit.tools.visualization.dag_drawer ()関数を使用します。 (これを実行するには Graphvizライブラリをインストールする)
# Convert to DAG
dag = circuit_to_dag(circ)
dag_drawer(dag)Output:
基本的な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 0x7fcbf0f00510>, 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 0x7fcbf0fefbd0>, 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:
操作を前面に追加する
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:
ノードをサブ回路で置換する
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:
すべての変換が完了すると、DAGは通常の QuantumCircuit オブジェクトに戻すことができる。 これがトランスパイラ・パイプラインの動作である。 回路が取り込まれ、DAG形式で処理され、変換された回路が出力される。
from qiskit.converters import dag_to_circuit
new_circ = dag_to_circuit(dag)
circuit_drawer(new_circ, output="mpl")Output:
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:
次のステップ
- カスタムトランスパイラ・パスの作成に関するガイドを確認してください
- カスタムバックエンドの作成方法と、それに対するトランスパイルの方法について学ぶ
- トランスパイラ設定の比較ガイドをお試しください。
- DAG Circuit API のドキュメントを確認してください。