דלג לתוכן הראשי

עבודה עם DAG במעברי טרנספילציה

ב-Qiskit, בשלבי הטרנספילציה, מעגלים מיוצגים באמצעות DAG. באופן כללי, DAG מורכב מצמתים (vertices, הידועים גם בשם "nodes") וקשתות מכוונות המחברות זוגות של צמתים בכיוון מסוים. ייצוג זה מאוחסן באמצעות אובייקטים מסוג qiskit.dagcircuit.DAGCircuit המורכבים מאובייקטי DagNode בודדים. היתרון של ייצוג זה על פני רשימה פשוטה של שערים (netlist) הוא שזרימת המידע בין פעולות היא מפורשת, מה שמקל על קבלת החלטות טרנספורמציה.

מדריך זה מדגים כיצד לעבוד עם DAG ולהשתמש בהם לכתיבת מעברי טרנספילציה מותאמים אישית. נתחיל בבניית מעגל פשוט ובחינת ייצוג ה-DAG שלו, ואחר כך נחקור פעולות DAG בסיסיות ונממש מעבר BasicMapper מותאם אישית.

בניית מעגל ובחינת ה-DAG שלו

קטע הקוד הבא ממחיש את ה-DAG על ידי יצירת מעגל פשוט שמכין מצב Bell ומפעיל סיבוב 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 ישנם שלושה סוגים של צמתי גרף: צמתי קלט של qubit/clbit (ירוק), צמתי פעולה (כחול), וצמתי פלט (אדום). כל קשת מציינת זרימת מידע (או תלות) בין שני צמתים. השתמש בפונקציה 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(). פעולה זו מצרפת את השער המצוין לפעול על ה-qubits הנתונים לאחר כל הפעולות הקיימות במעגל.

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 שממפה מעגל произвольный למכשיר עם קישוריות qubit מוגבלת. להנחיות נוספות, ניתן לפנות למדריך על כתיבת מעברי טרנספילציה מותאמים אישית.

המעבר מוגדר כ-TransformationPass, כלומר הוא משנה את המעגל. הוא עושה זאת על ידי מעבר שכבה-אחר-שכבה על ה-DAG, בדיקה האם כל הוראה עומדת בדרישות המוטלות על ידי מפת הצימוד של המכשיר. אם מזוהה הפרה, נקבע נתיב החלפה והשערי SWAP הנדרשים מוסיפים בהתאם.

בעת יצירת מעבר טרנספילציה, ההחלטה הראשונה כוללת בחירה האם המעבר צריך לרשת מ-TransformationPass או מ-AnalysisPass. מעברי טרנספורמציה מיועדים לשינוי המעגל, בעוד שמעברי ניתוח מיועדים רק לחילוץ מידע לשימוש על ידי מעברים עוקבים. הפונקציונליות העיקרית מיושמת אז במתודה run(dag). לבסוף, המעבר צריך להיות רשום במודול qiskit.transpiler.passes.

במעבר הספציפי הזה, ה-DAG נסרק שכבה-אחר-שכבה (כאשר כל שכבה מכילה פעולות הפועלות על קבוצות disjoint של qubits ולכן ניתנות לביצוע עצמאי). לכל פעולה, אם דרישות מפת הצימוד אינן מתקיימות, מזוהה נתיב החלפה מתאים, והחלפות הנדרשות מוכנסות כדי להביא את ה-qubits המעורבים לסמיכות.

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

צעדים הבאים