עבודה עם DAG במעברי טרנספילציה
ב-Qiskit, בשלבי הטרנספילציה, מעגלים מיוצגים באמצעות DAG. באופן כללי, DAG מורכב מצמתים (vertices, הידועים גם בשם "nodes") וקשתות מכוונות המחברות זוגות של צמתים בכיוון מסוים. ייצוג זה מאוחסן באמצעות אובייקטים מסוג qiskit.dagcircuit.DAGCircuit המורכבים מאובייקטי DagNode בודדים. היתרון של ייצוג זה על פני רשימה פשוטה של שערים (netlist) הוא שזרימת המידע בין פעולות היא מפורשת, מה שמקל על קבלת החלטות טרנספורמציה.
מדריך זה מדגים כיצד לעבוד עם DAG ולהשתמש בהם לכתיבת מעברי טרנספילציה מותאמים אישית. נתחיל בבניית מעגל פשוט ובחינת ייצוג ה-DAG שלו, ואחר כך נחקור פעולות DAG בסיסיות ונממש מעבר BasicMapper מותאם אישית.
בניית מעגל ובחינת ה-DAG שלו
קטע הקוד הבא ממחיש את ה-DAG על ידי יצירת מעגל פשוט שמכין מצב Bell ומפעיל סיבוב , בהתאם לתוצאת המדידה.
Package versions
הקוד בדף זה פותח עם הדרישות הבאות. אנחנו ממליצים להשתמש בגרסאות אלה או חדשות יותר.
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")
ב-DAG יש שלושה סוגי צמתים בגרף: צמתי קלט של qubit/clbit (ירוק), צמתי פעולה (כחול), וצמתי פלט (אדום). כל קשת מציינת זרימת נתונים (או תלות) בין שני צמתים. יש להשתמש בפונקציה qiskit.tools.visualization.dag_drawer() כדי לראות את ה-DAG של המעגל הזה. (יש להתקין את ספריית Graphviz כדי להריץ זאת.)
# Convert to DAG
dag = circuit_to_dag(circ)
dag_drawer(dag)

פעולות DAG בסיסיות
דוגמאות הקוד הבאות מדגימות פעולות נפוצות עם DAG, כולל גישה לצמתים, הוספת פעולות, והחלפת תת-מעגלים. פעולות אלה מהוות את הבסיס לבניית מעברי טרנספילציה.
קבלת כל צמתי הפעולה ב-DAG
המתודה op_nodes() מחזירה רשימה (iterable) של אובייקטי 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(). פעולה זו מצרפת את השער המצוין לפעול על ה-qubits הנתונים לאחר כל הפעולות הקיימות במעגל.
from qiskit.circuit.library import HGate
dag.apply_operation_back(HGate(), qargs=[q[0]])
dag_drawer(dag)

הוספת פעולה לתחילת המעגל
פעולה מתווספת לתחילת ה-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)

החלפת צומת בתת-מעגל
צומת המייצג פעולה מסוימת ב-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)

לאחר השלמת כל הטרנספורמציות, ניתן להמיר את ה-DAG בחזרה לאובייקט QuantumCircuit רגיל. כך פועל צינור הטרנספילציה: הצינור מקבל מעגל, מעבד אותו בצורת DAG ומפיק כפלט מעגל מותמר.
from qiskit.converters import dag_to_circuit
new_circ = dag_to_circuit(dag)
circuit_drawer(new_circ, output="mpl")
מימוש מעבר BasicMapper
ניתן לנצל את מבנה ה-DAG לכתיבת מעברי טרנספילציה. בדוגמה הבאה, מממשים מעבר BasicMapper שממפה מעגל שרירותי למכשיר עם קישוריות qubit מוגבלת. להנחיות נוספות, ניתן לפנות למדריך על כתיבת מעברי טרנספילציה מותאמים אישית.
המעבר מוגדר כ-TransformationPass, כלומר הוא משנה את המעגל. הוא עושה זאת על ידי מעבר שכבה-אחר-שכבה על ה-DAG, בדיקה האם כל הוראה עומדת בדרישות המוטלות על ידי מפת הצימוד של המכשיר. אם מזוהה הפרה, נקבע נתיב החלפה ושערי ה-SWAP הנדרשים מתווספים בהתאם.
בעת יצירת מעבר טרנספילציה, ההחלטה הראשונה כוללת בחירה האם המעבר צריך לרשת מ-TransformationPass או מ-AnalysisPass. מעברי טרנספורמציה מיועדים לשינוי המעגל, בעוד שמעברי ניתוח מיועדים רק לחילוץ מידע לשימוש על ידי מעברים עוקבים. הפונקציונליות העיקרית מיושמת אז במתודה run(dag). לבסוף, המעבר צריך להיות רשום במודול qiskit.transpiler.passes.
במעבר הספציפי הזה, ה-DAG נסרק שכבה-אחר-שכבה (כאשר כל שכבה מכילה פעולות הפועלות על קבוצות זרות של 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")
צעדים הבאים
- כדאי לעיין במדריך על יצירת מעבר טרנספילציה מותאם אישית
- כדאי ללמוד כיצד ליצור ולטרנספל מול backends מותאמים אישית
- כדאי לנסות את מדריך השוואת הגדרות טרנספילציה.
- כדאי לעיין בתיעוד API של DAG Circuit.