עבודה עם מעגלים פרמיוניים
המושגים במדריך הזה זמינים כרגע רק ב-Python API. פונקציונליות מקבילה תהיה זמינה ב-C API בגרסה עתידית.
המדריך הזה מסביר איך להשתמש ב-FermionicCircuit כדי לממש אלגוריתמים קוונטיים ישירות במרחב הפרמיוני, במקום למפות קודם לקיוביטים.
למה להשתמש במעגלים פרמיוניים
נבחן את תהליך העבודה המסורתי למימוש האבולוציה בזמן של המילטוניאן פרמיוני על ארכיטקטורה מבוססת קיוביטים:
-
מגדירים את ההמילטוניאן הפרמיוני (למשל, ).
-
ממפים אותו לאופרטור קיוביטים (למשל, באמצעות מיפוי Jordan-Wigner).
-
מממשים את האבולוציה בזמן במרחב הקיוביטים (באמצעות טרוטריזציה או שיטות אחרות).
לגישה הזו יש מגבלה מהותית: מיפוי מוקדם לקיוביטים משליך את הידע על מבנה הבעיה הפרמיוני. מעברי אופטימיזציה סטנדרטיים לא יכולים לנצל סימטריות פרמיוניות או סידורים מחדש מותרים של איברים, ולכן מתקבלים מעגלים לא אופטימליים עם עומק ומספר שערים מיותרים.
ה-FermionicCircuit מאפשר תהליך עבודה טוב יותר:
-
מגדירים את ההמילטוניאן הפרמיוני (למשל, ).
-
מממשים את האבולוציה בזמן ישירות במרחב הפרמיוני באמצעות שערים פרמיוניים.
-
ממפים למרחב הקיוביטים כחלק מתהליך הטרנספילציה.
הגישה הזו פותרת את המגבלה בכך שהיא שומרת את הידע על מבנה הבעיה במרחב הפרמיוני, שבו מעברי האופטימיזציה יכולים לנצל את המבנה הפרמיוני ואת יחסי החילוף כדי להקטין את עומק המעגל. המעגל הפרמיוני מתאר איזה חישוב לבצע, והטרנספילציה מטפלת באיך למפות אותו לקיוביטים, וכך מתקבלת הפרדה נקייה יותר של תחומי אחריות.
אינדוקס מודים גנרי
גם המודול operators וגם ה-FermionicCircuit משתמשים באינדוקס גנרי מבוסס מודים, שלא מניח דבר על טבעם של המודים. מוד הוא פשוט אינדקס מופשט שמסמן דרגת חופש פרמיונית, בלי סמנטיקה מובנית. בחירת העיצוב הזו משקפת את מודול ה-operators ומבטיחה גמישות מרבית. לדוגמה, אפשר לממש אבולוציה בזמן עבור כל אופרטור שמממש את הפרוטוקול OperatorTrait, ללא תלות בייצוג המתמטי שלו.
בניית מעגל פרמיוני
הדוגמה הבאה מממשת את האבולוציה בזמן שתוארה בסעיף הקודם. היא בונה FermionicCircuit על ידי קביעת מספר המודים הפרמיוניים, ואז מוסיפה שערים פרמיוניים מ-qiskit_fermions.circuit.library כדי לממש את האבולוציה בזמן.
הדוגמה גם מראה איך לשלב ידע על התחום באופרטור באמצעות קיבוץ איברי אופרטור. כשמקצים אינדקסי קבוצה לאיברי ההמילטוניאן, אתם שומרים מידע מבני שמעברי האופטימיזציה יכולים לנצל לאורך כל מחסנית הטרנספילציה.
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Create a circuit with 4 fermionic modes
>>> circuit = FermionicCircuit(4)
>>>
>>> # Define a simple fermionic Hamiltonian
>>> hamiltonian = FermionOperator.from_terms([
... ([cre(0), ann(2)], 0.5),
... ([cre(2), ann(0)], 0.5),
... ([cre(1), ann(3)], 0.5),
... ([cre(3), ann(1)], 0.5),
... ])
>>> # Add some problem structure by grouping our Hamiltonian terms
>>> hamiltonian.groups = [0, 0, 1, 1]
>>>
>>> # Add an evolution gate to implement exp(-i * t * H)
>>> evolution = Evolution(4, hamiltonian, time=1.0)
>>> circuit.append(evolution, circuit.register)
שימו לב שקיבוץ איברי האופרטור נשמר גם בפעולות פשוטות כמו פירוק. זה מדגים איך מידע מבני זורם דרך מחסנית המעגלים. כדי להבין את הטרנספילציה המלאה לקיוביטים, עיינו במדריך Transpiling fermionic circuits.
הפרדת שערים באמצעות barrier
מעברי האופטימיזציה ממזגים שערים שכנים כשאפשר. לדוגמה, MergeOrbitalRotations מאחד רצף של שערי OrbitalRotation עוקבים לשער אחד, ו-MergeSlaterDeterminantPreparation מקפל InitializeModes לתוך הסיבוב שבא אחריו.
השתמשו ב-FermionicCircuit.barrier() כשאתם רוצים למנוע את זה. ל-barrier אין השפעה אוניטרית, ולכן הוא לא משנה את המצב שהמעגל שלכם מכין; הוא רק מסמן נקודה שהמעברים לא ממזגים מעבר לה. קראו לו בלי ארגומנטים כדי שיכסה את כל המודים, או העבירו את המודים שאליהם רוצים להגביל אותו:
>>> from qiskit_fermions.circuit.library import OrbitalRotation
>>> import numpy as np
>>>
>>> rotations = FermionicCircuit(4)
>>> rotations.append(OrbitalRotation(np.eye(4, dtype=complex)), rotations.register)
>>> rotations.barrier()
>>> rotations.append(OrbitalRotation(np.eye(4, dtype=complex)), rotations.register)
ה-barriers שורדים את הטרנספילציה מפרמיונים לקיוביטים, ולכן הם מגבילים גם את האופטימיזציה ברמת הקיוביטים שבאה אחר כך. המדריך Transpile fermionic circuits מכסה את השלב הזה.
חזרה על מעגל
אלגוריתמים רבים מפעילים את אותו בלוק של שערים פעמים רבות. אבולוציה בזמן היא המקרה הנפוץ: צעד Trotter נבנה פעם אחת ואז חוזרים עליו עד שמגיעים לזמן היעד. השתמשו ב-repeat() כדי לבנות את המעגל החוזר, וב-compose() כדי לחבר אותו לקידומת של הכנת מצב.
הדוגמה הבאה בונה צעד Trotter מסדר שני עבור מודל Fermi-Hubbard. הצעד סימטרי: הוא מפצל את איבר הקפיצה לשני שערי OrbitalRotation בחצי משך, שעוטפים את האינטראקציה באתר.
>>> import scipy.linalg
>>> from qiskit_fermions.circuit.library import InitializeModes, OrbitalRotation
>>>
>>> norb, reps, time = 2, 3, 1.0
>>> num_modes = 2 * norb
>>> dt = time / reps
>>>
>>> hopping = np.zeros((norb, norb))
>>> hopping[0, 1] = hopping[1, 0] = -1.0
>>> interaction = FermionOperator.from_dict(
... {(cre(p), ann(p), cre(p + norb), ann(p + norb)): 2.0 for p in range(norb)}
... )
>>>
>>> def rotation(duration):
... propagator = scipy.linalg.expm(-1j * duration * hopping)
... # the same rotation acts on the alpha and beta halves of the register
... return scipy.linalg.block_diag(propagator, propagator)
>>>
>>> trotter_step = FermionicCircuit(num_modes)
>>> trotter_step.append(OrbitalRotation(rotation(dt / 2)), trotter_step.modes)
>>> trotter_step.append(
... Evolution(num_modes, interaction, dt, atomic=True), trotter_step.modes
... )
>>> trotter_step.append(OrbitalRotation(rotation(dt / 2)), trotter_step.modes)
חזרה על הצעד וחיבורו למצב ייחוס של Hartree-Fock נותנים את האבולוציה המלאה:
>>> evolution = FermionicCircuit(num_modes)
>>> evolution.append(InitializeModes([True, False, True, False]), evolution.modes)
>>> evolution.compose(trotter_step.repeat(reps), inplace=True)
>>> evolution.count_ops()["OrbitalRotation"]
6
החזרות משוטחות לתוך המעגל המוחזר ולא עטופות בשער יחיד, ולכן count_ops() סופר שני סיבובים לכל צעד. השיטוח הזה הוא מה שהופך את הכפילות בגבולות הצעדים לגלויה ל-Transpiler: הסיבוב האחרון בחצי משך של צעד אחד והסיבוב הראשון של הצעד הבא סמוכים זה לזה, ולכן MergeOrbitalRotations מאחד אותם לסיבוב יחיד.
>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.passes import MergeOrbitalRotations
>>>
>>> merged = FermionicPassManager([MergeOrbitalRotations()]).run(evolution)
>>> merged.count_ops()["OrbitalRotation"]
4
התוצאה עולה סיבוב אחד לכל צעד ועוד אחד, וזה מה שמקבלים כשממזגים את חצאי הצעדים האלה ידנית. כתיבת הצעד בצורתו הסימטרית הטבעית וחזרה עליו מפיקות את אותו מעגל, ולכן המיזוג נשאר לשלב האופטימיזציה.
כשצריך לשמור את החזרות נפרדות, העבירו insert_barriers=True כדי למקם barrier() ביניהן. מעברי האופטימיזציה לא ממזגים שערים מעבר ל-barrier, ולכן כל סיבוב נשמר:
>>> separated = trotter_step.repeat(reps, insert_barriers=True)
>>> FermionicPassManager([MergeOrbitalRotations()]).run(separated).count_ops()[
... "OrbitalRotation"
... ]
6
הארגומנט reps של FermionicSuzukiTrotter הוא מושג שונה. הוא מחלק שער Evolution יחיד ליותר גורמים קצרים יותר, ואילו repeat() משכפל מעגלים שלמים. חזרה על מעגל שמכיל Evolution אחד רק מכפילה את זמן האבולוציה הכולל שלו.
מדידת מודים פרמיוניים
measure_all() קורא כל מוד לביט קלאסי משלו, ומשייך את מוד i לביט i. השתמשו ב-measure() עם ClassicalRegister משלכם כדי לקרוא רק חלק מהם.
>>> measured = trotter_step.measure_all(inplace=False)
>>> measured.count_ops()["measure"]
4
העברת inplace=False מחזירה עותק מדוד ומשאירה את המקורי כמו שהוא, וזה חשוב כי מדידה היא לא אוניטרית: אי אפשר עוד להפעיל מעגל שמכיל מדידה על וקטור מצב, אז שמרו מעגל לא מדוד אם אתם רוצים גם לסמלץ אותו.
מדידה כאן, ולא על ה-QuantumCircuit אחרי הטרנספילציה, היא מה ששומר על מחרוזות הביטים שנדגמו קלות לקריאה. מדידה פרמיונית נושאת את שם המוד שהיא קוראת, ולכן כל שלב שמזיז מודים ממקום למקום (RelabelModes ברמה הפרמיונית, layout ו-routing ברמת הקיוביטים) מעביר איתו את המדידה, ומחרוזת הביטים נשארת מאונדקסת לפי המוד ששאלתם עליו.
בניגוד ל-measure_all(), לא מוכנס barrier() לפני המדידות. קראו ל-barrier() בעצמכם קודם אם אתם רוצים אחד שם.
האם ביט שנמדד אומר את מה שאתם מצפים תלוי בקידוד מפרמיונים לקיוביטים, שנבחר בזמן הטרנספילציה ולא כאן. תחת קידוד מבוסס בסיס אכלוס כמו Jordan-Wigner כל מוד נישא על ידי קיוביט אחד והביט הוא האכלוס של אותו מוד; ראו FermionicMeasure.
טרנספילציה של מעגלים פרמיוניים
כדי לממש את האלגוריתם הקוונטי שה-FermionicCircuit שלכם מייצג, צריך לבצע לו טרנספילציה ל-QuantumCircuit. אפשר ללמוד איך לעשות את זה במדריך Transpile fermionic circuits.
השלבים הבאים
-
למדו על השערים הפרמיוניים הזמינים בתיעוד של
qiskit_fermions.circuit.library. -
עיינו במדריך ההסבר על אופרטורים כדי להבין איך לבנות המילטוניאנים פרמיוניים שאפשר להשתמש בהם עם מעגלים פרמיוניים.
-
עברו על מדריך ההתחלה של SQDRIFT לדוגמה מעשית מקצה לקצה.
-
קראו את המדריך על הקשר ל-ffsim כדי להבין איך אפשר לסמלץ
FermionicCircuitישירות, ואיך אינדקסי המודים הגנריים האלה מקבלים סמנטיקה של ספין רק בזמן הסימולציה.

