יצירת מעגלי SqDRIFT
באלכסון קוונטי מבוסס דגימה (SQD) צריך לבחור אנזאץ שממנו דוגמים מחרוזות ביטים. הווריאנט SqDRIFT משתמש במקום זאת באנסמבל של מעגלי התפתחות בזמן שנבנים ישירות מההמילטוניאן של היעד. זה מושג על ידי דגימת משנה של אופרטורי התפתחות בזמן קטנים יותר מההמילטוניאן, לפי המקדמים שלו, והשיטה הזו ידועה כשיטת הטרוטריזציה qDRIFT.
מדריך ההתחלה הזה מראה איך ליצור אנסמבל של מעגלים אקראיים כאלה.
1. הכנת ההמילטוניאן
לצורך המדריך הזה, טענו את ההמילטוניאן של המבנה האלקטרוני של N2 מקובץ FCIDUMP. יש דרכים נוספות לבנות את FermionOperator. הקפידו לעיין בתיעוד שלו, וגם ב-qiskit_fermions.operators.library.
- PYTHON
- C
>>> from qiskit_fermions.operators.library import FCIDump
>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> fcidump = FCIDump.from_file("docs/guides/n2.fcidump")
>>> num_modes = 2 * fcidump.norb
>>> hamil = FermionOperator.from_fcidump(fcidump)
#include <qiskit_fermions.h>
QfFCIDump* fcidump = NULL;
qf_fcidump_from_file("docs/guides/n2.fcidump", &fcidump);
QfFermionOperator* hamil = qf_ferm_op_from_fcidump(fcidump);
uint32_t num_modes = 2 * qf_fcidump_norb(fcidump);
2. קיבוץ איברי ההמילטוניאן
נצלו את הסימטריות הרבות שקיימות בהמילטוניאן של המבנה האלקטרוני על ידי קיבוץ איברים קשורים שיש להם מקדמים זהים. פעולה זו משנה את התפלגות מקדמי האופרטור שממנה פרוטוקול qDRIFT דוגם, אך אינה פוגעת בערבויות ההתכנסות שלו. חשוב לציין שקיבוץ איברים הקשורים בסימטריה גורם לביטול רצוי של איברי פאולי, וכך מתקבל עומק מעגל קצר יותר בסך הכול כשמפתחים מצב בזמן תחת פעולתם.
המודול qiskit_fermions.operators.terms.grouping מספק פונקציות נוחות לקיבוץ האיברים של אופרטור. הנושא מוסבר ביתר פירוט במדריך הזה.
המימוש של group_terms_by_electronic_structure() מניח שאיברי ההמילטוניאן מסודרים בסדר נורמלי!
- PYTHON
- C
>>> from qiskit_fermions.operators.terms.grouping import group_terms_by_electronic_structure
>>> from qiskit_fermions.operators.terms.ordering import canonical_order
>>>
>>> canon = canonical_order(hamil.normal_ordered().simplify(atol=1e-16))
>>> exit_code = group_terms_by_electronic_structure(canon, num_modes, two_body_physicist_order=False)
>>> assert exit_code is None
>>> print(canon.groups) # the groups attribute now contains some list of group indices
[0, ...]
QfFermionOperator* normal = qf_ferm_op_normal_ordered(hamil, NULL);
QfFermionOperator* simplified = qf_ferm_op_simplify(normal, 1e-16);
QfFermionOperator* canon = qf_ferm_op_canonical_order(simplified);
QfExitCode exit = qf_ferm_op_group_terms_by_electronic_structure(canon, num_modes, false);
assert(exit == QfExitCode_Success);
qf_ferm_op_free(normal);
qf_ferm_op_free(simplified);
ההמילטוניאן המלא של המבנה האלקטרוני מכיל איברים מסוימים שהכללתם במעגל התפתחות בזמן אינה משפיעה על מחרוזות הביטים הנצפות, ולכן רק מגדילה את תקורת הדגימה. לכן מומלץ לסנן איברים כאלה מההמילטוניאן בשלב הזה, לפני בניית השער Evolution בשלב הבא.
האיברים שמתאימים לתיאור הזה הם אלה שאלכסוניים בבסיס מספרי התפוסה, כלומר מכפלות של אופרטורי מספר (). זה כולל את היסט האנרגיה הקבוע, שהתפתחותו בזמן מכניסה למעגל רק פאזה גלובלית, את אופרטורי המספר הבודדים שהתפתחותם בזמן שקולה לסיבובי Z של קיוביט יחיד, וכן מכפלות מסדר גבוה יותר כמו . אף אחד מהם לא יכול לשנות את התפוסה של מוד, ולכן השמטתם לא עולה בתוכן של עירורים. בניגוד לדחייה לכל הגרלה בשלב 5, הסינון הזה מתבצע פעם אחת, על ההמילטוניאן עצמו, ולכן התפלגות המקדמים שממנה הפרוטוקול דוגם נבנית מחדש באופן עקבי מהאופרטור המסונן, ולא מנורמלת מחדש באמצע ההגרלה.
הפונקציה filter_diagonal_terms() מסירה איברים כאלה מאופרטור במקום:
- PYTHON
- C
>>> from qiskit_fermions.operators.terms.filtering import filter_diagonal_terms
>>>
>>> filter_diagonal_terms(canon)
qf_ferm_op_filter_diagonal_terms(canon);
סינון כאן, פעם אחת, זול משמעותית מסינון חוזר ונשנה. FermionicQDrift רץ פעם אחת לכל מעגל שעבר טרנספילציה, ולכן סינון ההמילטוניאן מראש, ולא בכל קריאה, חוסך עבודה חוזרת לכל מעגל שנוצר ממנו.
מאותה סיבה כדאי לסדר כאן את איברי ההמילטוניאן לפי אינדקס הקבוצה. אינדקסי קבוצה הם תגית לכל איבר שלא אומרת דבר על המיקום של האיברים האלה, ולכן האיברים של קבוצה מפוזרים בדרך כלל לאורך האופרטור: הקיבוץ שהוחל למעלה מקצה אינדקסים לפי סדר המפגש הראשון, וכך האיבר השני של קבוצה רחוק מהראשון.
FermionicQDrift דוגם קבוצות שלמות ומחפש כל קבוצה שהוגרלה באמצעות split_out_groups(). מציאת קבוצה בין איברים מפוזרים פירושה סריקה של כולם, ולכן החיפוש עולה אותו דבר בין אם מוגרלת קבוצה אחת ובין אם ארבעים. group_order() אוסף כל קבוצה לרצף רציף אחד, ולאחר מכן החיפוש הוא חיפוש בינארי על גבולות הקבוצות ועולה כמו הקבוצות שהוגרלו ולא כמו האיברים שמוחזקים. החיפוש עצמו נעשה זול בהרבה; כמה זמן זה חוסך בטרנספילציה נתונה תלוי במה שהצינור עושה חוץ מזה, כי המיפוי והסינתזה שולטים ברגע שהסריקה נעלמת:
- PYTHON
- C
>>> from qiskit_fermions.operators.terms.ordering import group_order
>>>
>>> canon = group_order(canon)
>>> print(canon.groups[:4])
[0, 0, ...]
QfFermionOperator* grouped = qf_ferm_op_group_order(canon);
זה מסדר מחדש את האיברים בלי לשנות את ערך האופרטור, וצריך לקרות רק פעם אחת, לא משנה כמה אקראיזציות של מעגל מוגרלות ממנו.
3. הכנת מעגל ההתפתחות בזמן
הכינו את מעגל ההתפתחות בזמן של ההמילטוניאן ואת מעגל הבסיס שממנו יוגרלו דגימות. qiskit_fermions.circuit.library מכיל את כל הרכיבים הנדרשים לכך, בהתאם לקונבנציות של Qiskit.
פרוטוקול qDRIFT הוא שיטת הסינתזה FermionicQDrift, והשער Evolution נושא אותה בתור ה-synthesis שלו. השיטה מציגה את הפרמטרים המעטים שבהם אפשר להשתמש כדי להתאים אישית את המעגלים שייווצרו:
-
אורך כל מעגל במונחים של קבוצות עירור:
num_groups -
באופן אופציונלי, זרע למחולל המספרים האקראיים:
rng -
באופן אופציונלי, משקלי דגימה שחושבו מראש, אם הטרנספילציה עצמה הופכת לצוואר בקבוק:
weights(ראו (אופציונלי) חישוב מוקדם של משקלי הדגימה בהמשך)
מכיוון שהשיטה מוחזקת על ידי השער, כל מה שמתאר את האופרטור של השער, כמו המשקלים האלה, נמצא ממש לצד האופרטור הזה.
- PYTHON
- C
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.circuit.library.synthesis import FermionicQDrift
>>>
>>> time = 1.0 # you can choose a desired scaling factor here
>>> num_groups = 10
>>> qdrift = FermionicQDrift(num_groups, rng=19)
>>> evo_gate = Evolution(num_modes, canon, time, synthesis=qdrift)
>>>
>>> circ = FermionicCircuit(num_modes)
>>> circ.append(evo_gate, circ.modes)
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions.
הדוגמה הזו לא מאתחלת את המודים הפרמיוניים עם חלקיקים, וגם לא מודדת את מצבם הסופי.
4. טרנספילציה של המעגל
המודול qiskit_fermions.transpiler משתלב ישירות עם צינור הטרנספילציה של Qiskit, ומאפשר לבצע טרנספילציה ישירה של ה-FermionicCircuit שנבנה למעלה ל-QuantumCircuit.
השתמשו במיפוי פרמיון-לקיוביט jordan_wigner() כדי להמיר את ההמילטוניאן המבוטא במונחי פרמיונים למחרוזות פאולי. אפשר לעשות זאת ישירות כחלק מתהליך הטרנספילציה באמצעות מעבר הטרנספילציה F2QSynthesis. השתמשו ב-generate_preset_jw_pass_manager() כדי לבנות FermionicStagedPassManager, שמבטיח שקידוד ג'ורדן-וינגר ישמש באופן עקבי לכל הוראות המעגל.
חשוב: הוסיפו את המעבר Decompose של Qiskit לשלב ה-optimization של צינור הטרנספילציה. הוא מפרק את השער Evolution בעזרת שיטת הסינתזה שהשער נושא, שהיא שיטת qDRIFT שהוכנה למעלה. כך מובטח שהמעגל לא משתמש בהתפתחות בזמן של ההמילטוניאן כולו, שעומקה היה חורג מיכולות חומרת המחשוב הקוונטי הזמינה כיום.
במקום זאת, הוא דוגם תת-קבוצה של מספר קבוע של groups מאיברי ההמילטוניאן לכל מעגל, בכל פעם שהמעגל עובר טרנספילציה. כך אפשר ליצור כמה אקראיזציות של מעגל, כנדרש באלגוריתם SqDRIFT, על ידי הרצה חוזרת של צינור הטרנספילציה. מספר המעגלים ליצירה הוא num_sqdrift_randomizations.
- PYTHON
- C
>>> from qiskit.transpiler.passes import Decompose
>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>>
>>> pm = generate_preset_jw_pass_manager()
>>> pm.optimization = FermionicPassManager([Decompose(["Evolution"])])
>>>
>>> num_sqdrift_randomizations = 10
>>> sqdrift_circuits = [
... pm.run(circ) for _ in range(num_sqdrift_randomizations)
... ]
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
הדוגמאות הקודמות קובעות את זרע ה-rng של שיטת הסינתזה FermionicQDrift, כדי שאפשר יהיה לשחזר את רצף האקראיזציות.
אותו צינור מקבל את שיטת qDRIFT בשני מקומות נוספים. המעבר F2FSynthesis מקבל שיטה אחת עבור כל שער Evolution לפי שם, F2FSynthesis({"Evolution": FermionicQDrift(num_groups)}), וזה כל מה שנדרש כשאף שער לא מחזיק משהו ספציפי לאופרטור שלו. השלב הבא משתמש במקום השלישי, המעבר QDriftTrotterization.
5. סינון עירורים שהוגרלו לפי תפוסה
מעבר לאיברים האלכסוניים שסוננו בשלב הקודם, עירור שהוגרל יכול לפעול כולו בתוך קבוצת מודים שהתפוסה שלהם כבר קבועה (כולם תפוסים או כולם לא תפוסים), ושם הוא לא יכול להעביר חלקיק מצד אחד לצד השני. עירור כזה לא מלמד דבר על מחרוזות הביטים שנדגמו, ולכן משבצת שהוקדשה לו היא משבצת מבוזבזת. המעבר QDriftTrotterization, שעוטף את אותה שיטת FermionicQDrift, דוחה עירורים כאלה ברגע שהם מוגרלים ומגריל תחליף, כך שכל אחת מ-num_groups המשבצות של המעגל שנוצר נושאת עירור שמצמד בין שני הצדדים.
הסינון הזה אינו בחינם, ולכן הוא מעבר נפרד. מה שהוא שומר הוא התקציב של num_groups משבצות שנדגמו, ולא את עומק המעגל: מכיוון שהתקציב הזה קבוע, עירור שנדחה מוחלף ולא מושמט, ועירור זול מפנה מקום לעירור מצמד שסינתזה שלו יקרה יותר. צפו שמעגל מסונן יהיה עמוק יותר ממעגל לא מסונן שהוגרל מאותו זרע.
נרמול מחדש של ההגרלה על פני העירורים שהתקבלו משנה את ההתפלגות שממנה פרוטוקול qDRIFT דוגם, ובניגוד לקיבוץ בשלב 2 הוא גם שובר את ערבות ההתכנסות של הפרוטוקול: המכפלה שנדגמה כבר לא מתכנסת בממוצע להתפתחות תחת ההמילטוניאן שהעברתם. כל עירור שנשמר מקבל בסופו של דבר משקל השווה להופכי של הסתברות הקבלה, העירורים שנדחו לא תורמים דבר, ולא סך גודל המקדמים ולא זמן ההתפתחות לכל שער מותאמים כדי לפצות.
כלל הקבלה הוא גם קירוב מלמעלה. הוא משווה תומכי מודים בלבד, ולכן אינו נושא מידע על אמפליטודות, והקבוצות שנמצאות במעקב רק גדלות: עירור הכלוא בקבוצה התפוסה הוא אכן אינרטי על מצב מכפלה, אבל אותה הנמקה נחלשת בהגרלות מאוחרות יותר, אחרי שעירורים קודמים שהתקבלו הותירו את המצב שזור וסימנו את רוב המודים כ"לא ודאיים".
השתמשו בסינון לתהליך העבודה של דגימת מחרוזות ביטים שהמדריך הזה מתאר, שבו המעגלים מזינים עיבוד SQD ורק מחרוזות הביטים שנדגמו חשובות. אל תשתמשו בו אם בכוונתכם להעריך ערך תצפית מהמעגלים האלה, או להסתמך בכל דרך אחרת על חסם השגיאה של qDRIFT: לתוצאות כאלה יש הטיה שהמעבר אינו מתקן.
הסינון הזה צריך לדעת אילו מודים מתחילים תפוסים. לכן הוא דורש שער InitializeModes שקודם לשערי Evolution במעגל; InitializeModes.from_hartree_fock() היא דרך נוחה לבנות אחד. הוסיפו כאן אחד עבור ייחוס הארטרי-פוק של N2 (שבעה אלקטרונים אלפא ושבעה בטא ב-14 אורביטלים מרחביים) והשוו בין העירורים שנדגמו עם הסינון ובלעדיו:
- PYTHON
- C
>>> from qiskit_fermions.circuit.library import InitializeModes
>>> from qiskit_fermions.transpiler.passes import F2FSynthesis, QDriftTrotterization
>>>
>>> init = InitializeModes.from_hartree_fock(fcidump.norb, (7, 7))
>>>
>>> hf_circ = FermionicCircuit(num_modes)
>>> hf_circ.append(init, hf_circ.modes)
>>> hf_circ.append(Evolution(num_modes, canon, time), hf_circ.modes)
>>>
>>> num_groups = 5
>>> qdrift_unfiltered = F2FSynthesis(
... {"Evolution": FermionicQDrift(num_groups, rng=3480)}
... )
>>> qdrift_trivial = QDriftTrotterization(
... FermionicQDrift(num_groups, rng=3480)
... )
>>>
>>> for instruction in FermionicPassManager(qdrift_unfiltered).run(hf_circ)._inner.data:
... if instruction.operation.name == "Evolution":
... print(sorted(instruction.operation.operator.get_support()))
[2, 4]
[41, 45, 52]
[15, 45, 55]
[41, 52, 53]
[10, 16, 37, 38]
>>>
>>> for instruction in FermionicPassManager(qdrift_trivial).run(hf_circ)._inner.data:
... if instruction.operation.name == "Evolution":
... print(sorted(instruction.operation.operator.get_support()))
[0, 1, 6, 7]
[0, 1, 28, 29]
[4, 13, 55]
[13, 20, 40, 41]
[0, 1, 28, 29]
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
אף אחד מהעירורים שהוגרלו בלי סינון לא נוגע בקבוצה התפוסה (0-6 ו-28-34) בכלל, ולכן אף אחד מהם לא יכול להעביר חלקיק בין מוד תפוס למוד לא תפוס. עם הסינון, כל החמישה נדחים ומוחלפים בעירורים שכן מצמדים מוד תפוס עם מוד לא תפוס. לדוגמה, העירור הראשון שהתקבל [0, 1, 6, 7] מעביר חלקיק בין המודים התפוסים 0, 1 ו-6 לבין המוד הלא תפוס 7.
ברגע שעירור מתקבל, כל מוד בתומך שלו הופך ל"לא ודאי" וזכאי לשחק כל אחד משני התפקידים בדגימות מאוחרות יותר, ולכן קבוצות המודים התפוסים והלא תפוסים ממשיכות לגדול ככל שמתקבלים עוד עירורים. זה מה שהופך את העירור השני, [0, 1, 28, 29], לקביל. כל ארבעת המודים שלו הם מבין אלה שהיו תפוסים במקור, ולכן הוא לא מצמד לאף מוד שהיה לא תפוס במקור. הוא מתקבל רק כי המודים 0 ו-1 הפכו ללא ודאיים (ולכן זכאים להיות הצד ה"לא תפוס" של הצימוד) ברגע שהעירור הראשון נגע בהם.
הגידול הזה הוא מה שהופך את הכלל לקירוב מלמעלה ולא למבחן: ככל שיותר מודים הופכים ללא ודאיים, פחות הגרלות נדחות, והסינון משפיע הכי הרבה על התפלגות הדגימה בהגרלות המוקדמות ביותר.
כדי לשפוט עד כמה הסינון הזיז את ההתפלגות, קראו את ספירות הדחייה שהמעבר רושם במטא-דאטה של המעגל. נשמר רשום אחד לכל שער Evolution שסונן, והיחס בין התקבלו לסך הכול מעריך את הסתברות הקבלה, שהיא הגורם שבו עירורים שנשמרו קיבלו משקל יתר:
- PYTHON
- C
>>> # a freshly seeded pass, so the counts do not depend on earlier draws
>>> qdrift_counted = QDriftTrotterization(
... FermionicQDrift(num_groups, rng=3480)
... )
>>> filtered = FermionicPassManager(qdrift_counted).run(hf_circ)
>>> discarded = filtered.metadata.get("filter_trivial.discarded")
>>> emitted = filtered.metadata.get("filter_trivial.emitted")
>>> print(f"{emitted[0]} kept, {discarded[0]} discarded")
5 kept, 7 discarded
>>> print(f"acceptance probability: {emitted[0] / (emitted[0] + discarded[0]):.2f}")
acceptance probability: 0.42
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
לסינון דרוש מידע על תפוסה שמולו מסננים. שער PrepareSlaterDeterminant מזין אותו בדיוק כמו InitializeModes, וכל OrbitalRotation מסמן כל מוד שהוא פועל עליו כ"לא ודאי". בלי אף אחד מהם, או כשהמודים שהוזנו נוחתים כולם בצד אחד (כל המודים תפוסים, או כולם לא תפוסים), המעבר פולט UserWarning ומשאיר את הדגימה לא מסוננת עבור שער Evolution הזה. בהמילטוניאן שהאיברים הנותרים בו כבר לא יכולים לצמד בין שתי הקבוצות שבמעקב, המעבר במקום זאת מעלה RuntimeError אחרי QDriftTrotterization.MAX_SAMPLE_RETRIES דחיות רצופות.
(אופציונלי) חישוב מוקדם של משקלי הדגימה
פנו לזה רק אם הטרנספילציה עצמה היא צוואר בקבוק. זה לא משנה דבר במעגלים שתקבלו.
FermionicQDrift מגריל כל קבוצה בהסתברות שפרופורציונלית לגודל המקדם שלה, ומפיק את הגדלים האלה מההמילטוניאן בכל קריאה: ערך אחד לכל איבר של ההמילטוניאן, שמצטמצם לערך אחד לכל קבוצה. מכיוון שהשיטה חסרת מצב, היא חוזרת על הצמצום הזה בכל אקראיזציה, אף שהתוצאה זהה בכל פעם. הארגומנט weights מאפשר לחשב אותו פעם אחת באמצעות group_coeff_means() ולהעביר את התוצאה, ובכך להוציא את העלות מהלולאה תוך השארת השיטה נקייה מכל מצב מטמון שתלוי בהמילטוניאן.
זה המקביל לסידור האיברים בשלב 2. שם הוצאנו את חיפוש הקבוצה מהלולאה, וכאן את צמצום המשקלים, וכל אחד מהם בולט רק במידה שהאחר חסר, ולכן כדאי להחיל את שניהם יחד. מכיוון ש-canon כבר סודר מחדש למעלה, השניים מתחברים ישירות.
המשקלים מתארים את הקבוצות של אופרטור מסוים אחד, ולכן הם שייכים לשער Evolution של אותו אופרטור: בנו את השיטה עבור השער הזה והעבירו אותה בתור ה-synthesis של השער, בדיוק כמו בשלב 3.
- PYTHON
- C
>>> from qiskit_fermions.operators.terms.grouping import group_coeff_means
>>>
>>> weights = group_coeff_means(canon) # `canon` is group-ordered, from step 2
>>> qdrift_weighted = FermionicQDrift(10, rng=19, weights=weights)
>>> weighted_gate = Evolution(num_modes, canon, time, synthesis=qdrift_weighted)
>>>
>>> weighted_circ = FermionicCircuit(num_modes)
>>> weighted_circ.append(weighted_gate, weighted_circ.modes)
>>>
>>> weighted_circuits = [
... pm.run(weighted_circ) for _ in range(num_sqdrift_randomizations)
... ]
>>> len(weighted_circuits)
10
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
מכיוון שאלה בדיוק המשקלים שהמעבר היה מחשב, המעגלים זהים לקודמים; רק הצמצום החוזר נעלם.
נדרש רשום אחד לכל קבוצה, לא לכל איבר, בכל פעם שהאופרטור נושא קבוצות, כי אופרטור מקובץ נדגם לפי קבוצות. הרשומים חייבים להיות אי-שליליים: משקל הוא גודל, כי פרוטוקול qDRIFT מפרק עם חיוביים, והסימן של מקדם שייך ל-, שממנו המעבר קורא אותו ישירות מהאופרטור.
המשקלים הם גדלים מוחלטים, לא העדפות יחסיות. הסכום שלהם קובע גם את זמן ההתפתחות, ולכן הכפלת כל רשומה ב- דוגמת באותו אופן אך מתפתחת למשך במקום . לכן העברת משהו שאינו גדלי המקדמים של ההמילטוניאן עצמו משנה איזו התפתחות האנסמבל מקרב, ולכן זה כלי ביצועים ולא דרך לכוונן את הדגימה.
מכיוון שהמשקלים שייכים לשער שהאופרטור שלו הם מתארים, מעגל יכול להכיל כמה שערי Evolution, כל אחד עם המשקלים של האופרטור שלו. המעבר QDriftTrotterization משלב 5 מכבד אותם באותו אופן: הוא מגריל שער בעזרת ה-FermionicQDrift שהשער נושא, וחוזר לזה שאיתו נבנה עבור שערים שלא נושאים כזה. סוג אחר של שיטת סינתזה על שער אינו הצהרה על qDRIFT, ולכן המעבר מתעלם ממנו. מסירת השיטה למעבר לפי שם השער במקום זאת, כמו ב-F2FSynthesis({"Evolution": ...}), מחילה שיטה אחת על כל שער Evolution, ולכן משקלים שמועברים שם משמעותיים רק עבור מעגל עם שער אחד כזה. אורך המשקלים נבדק מול כל שער שעליו הם מוחלים.
(אופציונלי) אופטימיזציה של אינדקס המודים הפרמיוניים
אפשר להוסיף לצינור הטרנספילציה שלב אופטימיזציה נוסף שממזער את המרחק של טווחי העירורים הפרמיוניים על ידי תיוג מחדש של אינדקסי המודים הפרמיוניים. האופטימיזציה הזו הוצגה במאמר SqDRIFT ומומשה ב-build_excitation_span_minimization_model(). אפשר להכניס אותה בקלות לצינור הטרנספילר באמצעות המעבר RelabelModes:
- PYTHON
- C
>>> from pyomo.environ import SolverFactory
>>> from qiskit_fermions.transpiler.passes import RelabelModes
>>>
>>> solver = SolverFactory("appsi_highs")
>>> solver.options["time_limit"] = 10
>>>
>>> qdrift = F2FSynthesis({"Evolution": FermionicQDrift(5, rng=19)})
>>> relabel = RelabelModes(solver=solver)
>>>
>>> pm.optimization = FermionicPassManager([qdrift, relabel])
>>>
>>> relabeled_circ = pm.run(circ)
>>> # if the automatic mode relabeling was successful, the circuit's
>>> # metadata will contain the mode `permutation` information
// WARNING: This feature is not available via the C API.
שימוש באופטימיזציה האוטומטית בתוך RelabelModes (שמשתמשת ב-build_excitation_span_minimization_model()) דורשת את התלות האופציונלית שמנוהלת על ידי HAS_PYOMO.
התיוג מחדש משנה איזה מוד נושא קיוביט נתון, ולכן אלכסון מרחב המשנה מקבל את מחרוזות הביטים הנכונות רק אם לוקחים זאת בחשבון. המקום שבו מתבצע החשבון תלוי במקום שבו מודדים.
הוסיפו את המדידות ל-FermionicCircuit באמצעות FermionicCircuit.measure_all() לפני הטרנספילציה, ואין מה לעשות: כל מדידה נוקבת בשם המוד שהיא קוראת, ולכן התיוג מחדש נושא אותה איתו ומחרוזות הביטים שנדגמו מאונדקסות לפי המוד המקורי.
מדדו במקום זאת את ה-QuantumCircuit שעבר טרנספילציה, ואז צריך לעבד את מחרוזות הביטים בעזרת ה-permutation שנרשם במטא-דאטה של המעגל. עיינו בתיעוד של RelabelModes למתכון.
השלבים הבאים
עכשיו, כשיצרתם בהצלחה אנסמבל של מעגלים, אפשר לדגום מהם מחרוזות ביטים. לשם כך יש להריץ את המעגלים בחומרה. עיינו בתיעוד של Qiskit להוראות מפורטות.
לאחר שמתקבלות דגימות של מחרוזות ביטים, אפשר להשתמש בהן יחד עם מקדמי ההמילטוניאן כדי לבצע עיבוד SQD, כפי שמוסבר במדריכי ה-SQD addon.