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

כתוב transpiler pass מותאם אישית

גרסאות חבילות

הקוד בדף זה פותח עם הדרישות הבאות. אנו ממליצים להשתמש בגרסאות אלה או חדשות יותר.

qiskit[all]~=2.3.0

ה-Qiskit SDK מאפשר לך ליצור passes של transpilation מותאמות אישית ולהריץ אותן באובייקט PassManager או להוסיף אותן ל-StagedPassManager. כאן נדגים איך לכתוב transpiler pass, תוך התמקדות בבניית pass שמבצע Pauli twirling על שערים קוונטיים רועשים במעגל קוונטי. דוגמה זו משתמשת ב-DAG, שהוא האובייקט שמניפולציה עליו מבצע הסוג TransformationPass של pass.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit

רקע: ייצוג DAG

לפני בניית pass, חשוב להציג את הייצוג הפנימי של מעגלים קוונטיים ב-Qiskit, ה-directed acyclic graph (DAG) (ראה מדריך זה לסקירה כללית). כדי לעקוב אחר השלבים הללו, התקן את ספריית graphviz עבור פונקציות ציור ה-DAG.

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

דוגמה זו ממחישה את ה-DAG על ידי יצירת מעגל פשוט שמכין מצב Bell ומיישם סיבוב RZR_Z, בהתאם לתוצאת המדידה.

  from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
import numpy as np

qr = QuantumRegister(3, 'qr')
cr = ClassicalRegister(3, 'cr')
qc = QuantumCircuit(qr, cr)

qc.h(qr[0])
qc.cx(qr[0], qr[1])
qc.measure(qr[0], cr[0])
qc.rz(np.pi/2, qr[1]).c_if(cr, 2)
qc.draw(output='mpl')

מעגל שמכין מצב Bell ומיישם סיבוב R_Z בהתאם לתוצאת המדידה.

השתמש בפונקציה qiskit.tools.visualization.dag_drawer() כדי לצפות ב-DAG של המעגל הזה. יש שלושה סוגים של צמתים בגרף: צמתי qubit/clbit (ירוק), צמתי פעולה (כחול), וצמתי פלט (אדום). כל קשת מציינת זרימת נתונים (או תלות) בין שני צמתים.

from qiskit.converters import circuit_to_dag
from qiskit.tools.visualization import dag_drawer

dag = circuit_to_dag(qc)
dag_drawer(dag)

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

Transpiler passes

Transpiler passes מסווגים כ-AnalysisPass או כ-TransformationPass. Passes בכלל עובדים עם ה-DAG ועם ה-property_set, אובייקט דמוי מילון לאחסון מאפיינים שנקבעים על ידי analysis passes. Analysis passes עובדים עם ה-DAG וה-property_set שלו. הם לא יכולים לשנות את ה-DAG, אך יכולים לשנות את ה-property_set. זה שונה מ-transformation passes, שכן אלה כן משנים את ה-DAG, ויכולים לקרוא (אך לא לכתוב) את ה-property_set. לדוגמה, transformation passes מתרגמים מעגל ל-ISA שלו או מבצעים routing passes כדי להכניס שערי SWAP היכן שצריך.

צור transpiler pass מסוג PauliTwirl

הדוגמה הבאה בונה transpiler pass שמוסיף Pauli twirls. Pauli twirling היא אסטרטגיה לדיכוי שגיאות שמאקראית איך qubits חווים ערוצים רועשים, שאנו מניחים שהם שערים דו-קיוביטיים בדוגמה זו (כי הם הרבה יותר נוטים לשגיאות משערים חד-קיוביטיים). ה-Pauli twirls לא משפיעים על פעולת השערים הדו-קיוביטיים. הם נבחרים כך שאלה המוחלים לפני השער הדו-קיוביטי (משמאל) מנוטרלים על ידי אלה המוחלים אחרי השער הדו-קיוביטי (מימין). במובן זה, הפעולות הדו-קיוביטיות זהות, אך האופן שבו הן מבוצעות שונה. יתרון אחד של Pauli twirling הוא שהוא הופך שגיאות קוהרנטיות לשגיאות סטוכסטיות, שניתן לשפר על ידי ממוצע נוסף.

Transpiler passes פועלים על ה-DAG, כך שהמתודה החשובה לדרוס היא .run(), שמקבלת את ה-DAG כקלט. אתחול זוגות של Paulis כפי שמוצג שומר על פעולת כל שער דו-קיוביטי. זה נעשה עם מתודת העזר build_twirl_set, שעוברת על כל Pauli דו-קיוביטי (כפי שהתקבל מ-pauli_basis(2)) ומוצאת את ה-Pauli האחר ששומר על הפעולה.

מה-DAG, השתמש במתודה op_nodes() כדי להחזיר את כל הצמתים שלו. ניתן להשתמש ב-DAG גם לאיסוף runs, שהן רצפים של צמתים שרצים ברציפות על qubit. אלה ניתנים לאיסוף כ-runs חד-קיוביטיים עם collect_1q_runs, runs דו-קיוביטיים עם collect_2q_runs, ו-runs של צמתים שבהם שמות ההוראות נמצאים ב-namelist עם collect_runs. ל-DAGCircuit יש מתודות רבות לחיפוש ומעבר על גרף. מתודה אחת שנמצאת בשימוש נפוץ היא topological_op_nodes, שמספקת את הצמתים בסדר תלות. מתודות אחרות כמו bfs_successors משמשות בעיקר לקביעת אופן האינטראקציה של צמתים עם פעולות עוקבות ב-DAG.

בדוגמה, אנחנו רוצים להחליף כל צומת, המייצג הוראה, בתת-מעגל הבנוי כ-mini DAG. ל-mini DAG מתווסף רגיסטר קוונטי דו-קיוביטי. פעולות מתווספות ל-mini DAG על ידי שימוש ב-apply_operation_back, שמציב את ה-Instruction בפלט של ה-mini DAG (בעוד ש-apply_operation_front היה מציב אותה בקלט של ה-mini DAG). לאחר מכן הצומת מוחלף ב-mini DAG על ידי שימוש ב-substitute_node_with_dag, והתהליך נמשך על כל מופע של CXGate ו-ECRGate ב-DAG (המתאימים לשערי הבסיס הדו-קיוביטיים ב-backends של IBM®).

from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit import QuantumCircuit, QuantumRegister, Gate
from qiskit.circuit.library import CXGate, ECRGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.quantum_info import Operator, pauli_basis

import numpy as np

from typing import Iterable, Optional
class PauliTwirl(TransformationPass):
"""Add Pauli twirls to two-qubit gates."""

def __init__(
self,
gates_to_twirl: Optional[Iterable[Gate]] = None,
):
"""
Args:
gates_to_twirl: Names of gates to twirl. The default behavior is to twirl all
two-qubit basis gates, `cx` and `ecr` for IBM backends.
"""
if gates_to_twirl is None:
gates_to_twirl = [CXGate(), ECRGate()]
self.gates_to_twirl = gates_to_twirl
self.build_twirl_set()
super().__init__()

def build_twirl_set(self):
"""
Build a set of Paulis to twirl for each gate and store internally as .twirl_set.
"""
self.twirl_set = {}

# iterate through gates to be twirled
for twirl_gate in self.gates_to_twirl:
twirl_list = []

# iterate through Paulis on left of gate to twirl
for pauli_left in pauli_basis(2):
# iterate through Paulis on right of gate to twirl
for pauli_right in pauli_basis(2):
# save pairs that produce identical operation as gate to twirl
if (Operator(pauli_left) @ Operator(twirl_gate)).equiv(
Operator(twirl_gate) @ pauli_right
):
twirl_list.append((pauli_left, pauli_right))

self.twirl_set[twirl_gate.name] = twirl_list

def run(
self,
dag: DAGCircuit,
) -> DAGCircuit:
# collect all nodes in DAG and proceed if it is to be twirled
twirling_gate_classes = tuple(
gate.base_class for gate in self.gates_to_twirl
)
for node in dag.op_nodes():
if not isinstance(node.op, twirling_gate_classes):
continue

# random integer to select Pauli twirl pair
pauli_index = np.random.randint(
0, len(self.twirl_set[node.op.name])
)
twirl_pair = self.twirl_set[node.op.name][pauli_index]

# instantiate mini_dag and attach quantum register
mini_dag = DAGCircuit()
register = QuantumRegister(2)
mini_dag.add_qreg(register)

# apply left Pauli, gate to twirl, and right Pauli to empty mini-DAG
mini_dag.apply_operation_back(
twirl_pair[0].to_instruction(), [register[0], register[1]]
)
mini_dag.apply_operation_back(node.op, [register[0], register[1]])
mini_dag.apply_operation_back(
twirl_pair[1].to_instruction(), [register[0], register[1]]
)

# substitute gate to twirl node with twirling mini-DAG
dag.substitute_node_with_dag(node, mini_dag)

return dag

שימוש ב-transpiler pass מסוג PauliTwirl

הקוד הבא משתמש ב-pass שנוצר למעלה כדי לבצע transpile למעגל. נשקול מעגל פשוט עם שערי cx ו-ecr.

qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.ecr(1, 2)
qc.ecr(1, 0)
qc.cx(2, 1)
qc.draw("mpl")

פלט תא הקוד הקודם

כדי להחיל את ה-pass המותאם אישית, בנה pass manager באמצעות ה-pass מסוג PauliTwirl והרץ אותו על 50 מעגלים.

pm = PassManager([PauliTwirl()])
twirled_qcs = [pm.run(qc) for _ in range(50)]

כל שער דו-קיוביטי עכשיו כלוא בין שני Paulis.

twirled_qcs[-1].draw("mpl")

פלט תא הקוד הקודם

האופרטורים זהים אם משתמשים ב-Operator מ-qiskit.quantum_info:

np.all([Operator(twirled_qc).equiv(qc) for twirled_qc in twirled_qcs])
np.True_

הצעדים הבאים

המלצות