Перейти до основного вмісту
Цей проєкт у бета-версії. Знайшов(-ла) помилку? Відкрити issue на GitHub. Є запитання чи ідея? Запитати в Discussions — будемо раді твоєму відгуку!
Яка якість перекладу на цій сторінці?

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

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

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

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

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

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

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

qiskit[all]~=2.5.2
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"qiskit": "qiskit"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
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 0x12559c410>, 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 0x12559f490>, 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-підграф) з бажаною послідовністю гейтів, а потім цільовий вузол замінюється цим під-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.

Під час створення проходу транспілятора першим кроком є вибір між успадкуванням від 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

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