Перейти до основного вмісту

Робота з DAG у проходах транспілятора

У Qiskit на етапах транспіляції схеми представляються у вигляді DAG (направленого ациклічного графа). DAG складається з вершин (також відомих як «вузли») та направлених ребер, що з'єднують пари вершин у певному напрямку. Таке представлення зберігається у вигляді об'єктів qiskit.dagcircuit.DAGCircuit, які складаються з окремих об'єктів DagNode. Перевага цього представлення перед простим списком вентилів (тобто нетлістом) полягає в тому, що потік інформації між операціями є явним, що спрощує прийняття рішень про перетворення.

Цей посібник демонструє, як працювати з DAG та використовувати їх для написання власних проходів транспілятора. Спочатку ми побудуємо просту схему й дослідимо її DAG-представлення, а потім розглянемо базові операції з DAG та реалізуємо власний прохід BasicMapper.

Побудова схеми та дослідження її DAG

Наведений нижче фрагмент коду ілюструє DAG, створюючи просту схему, що підготовляє стан Белла та застосовує поворот RZR_Z залежно від результату вимірювання.

Версії пакетів

Код на цій сторінці розроблено з використанням наведених нижче вимог. Рекомендуємо використовувати ці версії або новіші.

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 є три типи вузлів графа: вхідні вузли кубітів/класичних бітів (зелені), вузли операцій (сині) та вихідні вузли (червоні). Кожне ребро вказує на потік даних (або залежність) між двома вузлами. Скористайся функцією 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)

Додати операцію в кінець

Операцію додають у кінець DAGCircuit за допомогою методу apply_operation_back(). Він додає вказаний вентиль для дії на задані кубіти після всіх наявних операцій у схемі.

from qiskit.circuit.library import HGate

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

Output of the previous code cell

Додати операцію на початок

Операцію додають на початок DAGCircuit за допомогою методу apply_operation_front(). Він вставляє вказаний вентиль перед усіма наявними операціями у схемі, фактично роблячи його першою виконуваною операцією.

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 із потрібною послідовністю вентилів, а потім цільовий вузол замінюється цим під-DAG за допомогою substitute_node_with_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 можна використати для написання проходів транспілятора. У наведеному нижче прикладі реалізовано прохід BasicMapper для відображення довільної схеми на пристрій з обмеженою зв'язністю кубітів. Додаткові відомості можна знайти в посібнику зі написання власних проходів транспілятора.

Прохід визначається як TransformationPass, тобто він модифікує схему. Для цього він обходить DAG шар за шаром, перевіряючи, чи задовольняє кожна інструкція обмеженням, які накладає карта зв'язків пристрою. Якщо виявлено порушення, визначається шлях для виконання операцій SWAP, і необхідні вентилі SWAP вставляються відповідно.

Під час створення проходу транспілятора першим кроком є вибір між успадкуванням від TransformationPass або AnalysisPass. Проходи перетворення призначені для модифікації схеми, тоді як проходи аналізу — лише для вилучення інформації для використання наступними проходами. Основна функціональність реалізується в методі run(dag). Нарешті, прохід слід зареєструвати в модулі qiskit.transpiler.passes.

У цьому конкретному проході DAG обходиться шар за шаром (кожен шар містить операції, що діють на непересічних наборах кубітів і, отже, можуть виконуватися незалежно). Для кожної операції, якщо обмеження карти зв'язків не виконуються, знаходиться відповідний шлях обміну і вставляються необхідні операції 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

Наступні кроки