קיבוץ איברי אופרטור: שימוש במבנה האופרטור
BosonOperator יכול לתייג כל אחד מהאיברים שלו עם אינדקס קבוצה. לתגיות אין משמעות משלהן: הן רק אומרות אילו איברים שייכים יחד. מה שמשמעות התגיות נקבע על ידי מה שצורך אותן. בחבילה הזו הצרכנים הם שער ההתפתחות בזמן, שמפוצל לגורם אחד לכל קבוצה, ומעבר qDRIFT האקראי, שדוגם קבוצות שלמות.
המדריך הזה מראה איך להקצות קבוצות, איך לבדוק את התכונות שהצרכן מסתמך עליהן, ואיך לסדר את האופרטור כך שבחירת כמה קבוצות מתוך אופרטור גדול תישאר זולה.
שום אסטרטגיית קיבוץ אוטומטית אינה מגיעה עם החבילה הזו. אתם מקצים את האינדקסים בעצמכם, לפי מה שאתם יודעים על המודל שלכם.
הקצאת קבוצות
הדוגמה המלווה היא שרשרת בוז-האברד בת שלושה אתרים. כל קשר תורם זוג דילוג, והאינטראקציות באתר יוצרות חלק אלכסוני. הדרך הפשוטה ביותר לבנות אופרטור מקובץ היא from_terms_with_groups(), שמקבלת כל איבר יחד עם אינדקס הקבוצה שלו כשלשה (actions, coefficient, group). אתם מחליטים על הקבוצה ברגע שאתם כותבים את האיבר, כשאתם עדיין יודעים למה הוא שייך לשם:
>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> bonds = [(0, 1), (1, 2)]
>>> terms = []
>>> for group, (left, right) in enumerate(bonds): # one group per bond
... terms.append(([cre(left), ann(right)], -1.0, group))
... terms.append(([cre(right), ann(left)], -1.0, group))
>>> for site in range(3): # the on-site interactions share one group
... terms.append(([cre(site), cre(site), ann(site), ann(site)], 1.0, len(bonds)))
>>> hamiltonian = BosonOperator.from_terms_with_groups(terms)
>>> len(hamiltonian), hamiltonian.num_groups()
(7, 3)
האיברים נשמרים בסדר שנתתם, ולכן אינדקסי הקבוצות לא יכולים להיצמד לאיברים הלא נכונים.
אופרטור שכבר קיים צריך להיות מקובץ בדיעבד, על ידי השמה לתכונה groups. כאן צריך זהירות, כי from_dict() אינה מגדירה את הסדר שבו היא שומרת את האיברים שלה. כתיבת הרשימה לפי מיקום יכולה אז לצרף אינדקס לאיבר הלא נכון. גזרו כל אינדקס מהאיבר עצמו במקום זאת:
>>> existing = BosonOperator.from_dict({tuple(actions): coeff for actions, coeff, _ in terms})
>>> existing.groups = [
... len(bonds)
... if len({mode for _, mode in term}) == 1
... else bonds.index(tuple(sorted({mode for _, mode in term})))
... for term, _ in existing.iter_terms()
... ]
>>> existing.num_groups(), existing.equiv(hamiltonian)
(3, True)
מספר האינדקסים חייב להתאים למספר האיברים. כל דבר אחר נדחה, כי רשימה קצרה מדי הייתה משמיטה בשקט איברים בסוף בכל מקום שבו עוברים על איברים יחד עם הקבוצות שלהם:
>>> try:
... hamiltonian.groups = [0, 1]
... except ValueError as error:
... print(error)
expected one group index per term, but got 2 for 7 terms
בדיקה של מה שהצרכן מסתמך עליו
שלוש פונקציות ב-qiskit_bosons.operators.terms עונות כל אחת על שאלה צרה אחת לגבי קיבוץ קיים. אף אחת מהן לא אומרת שקיבוץ הוא "נכון", כי נכונות היא יחסית לצרכן.
האם כל קבוצה הרמיטית? זו התכונה החשובה ביותר. ההתפתחות בזמן של קבוצה היא אוניטרית רק אם הקבוצה הרמיטית, ואיבר דילוג בודד אינו כזה. רק הזוג עם הצמוד שלו הרמיטי:
>>> from qiskit_bosons.operators.terms import groups_are_hermitian
>>> groups_are_hermitian(hamiltonian)
[True, True, True]
קיבוץ שמפצל זוג דילוג נתפס מיד:
>>> split = BosonOperator.from_terms_with_groups(
... [(actions, coeff, index) for index, (actions, coeff, _) in enumerate(terms)]
... ) # one term per group
>>> [bool(flag) for flag in groups_are_hermitian(split)]
[False, False, False, False, True, True, True]
ארבעת איברי הקפיצה נתפסים. שלוש הערכים True הם האיברים באתר , שהם הרמיטיים בפני עצמם. מצב (mode) יכול לחזור על עצמו באיבר אחד באופן לגיטימי, ושום דבר לא נזרק בגלל זה. הדגלים עוקבים אחרי הסדר של terms, ולכן אפשר לקרוא כל אחד מהם מול האיבר שהוא מתאר.
האם המקדמים אחידים? נוסחת מכפלה אקראית שוקלת קבוצה לפי הגודל הממוצע של המקדמים שלה, וזה תיאור נאמן של הקבוצה רק אם הגדלים תואמים:
>>> from qiskit_bosons.operators.terms import group_coeff_means, groups_have_uniform_coeffs
>>> groups_have_uniform_coeffs(hamiltonian)
[True, True, True]
>>> group_coeff_means(hamiltonian)
[1.0, 1.0, 1.0]
אף אחת משתי הבדיקות לא גוררת את השנייה: מקדמים אחידים לא הופכים קבוצה להרמיטית, וקבוצה הרמיטית יכולה לערבב גדלים שונים. כברירת מחדל משווים את הגדלים, כי זה מה ש-group_coeff_means() מחשבת בממוצע. זוג הרמיטי עם מקדמים מרוכבים הוא בעל גדלים שווים אבל מקדמים לא שווים, ולכן הוא מקיים רק את הצורה ברירת המחדל:
>>> conjugate = BosonOperator.from_terms_with_groups(
... [([cre(0), ann(1)], 1.0j, 0), ([cre(1), ann(0)], -1.0j, 0)]
... )
>>> groups_have_uniform_coeffs(conjugate)
[True]
>>> groups_have_uniform_coeffs(conjugate, abs=False)
[False]
groups_are_hermitian() בנויה על is_hermitian(), שמשווה אופרטור לצמוד שלו אחרי סידור נורמלי. זו זהות של האלגברה הבלתי קטומה, ולכן קבוצה שעוברת את הבדיקה היא הרמיטית כאופרטור בוזוני. ראו עקרונות התכנון של ייצוגי אופרטורים כדי להבין איך מכפלות של אופרטורי סולם מסודרות ומושוות.
סידור האיברים לפי קבוצה
split_out_groups() מחזירה אופרטור אחד לכל קבוצה שביקשתם. כשאיברי הקבוצה פזורים לאורך האופרטור, היא צריכה לסרוק כל איבר כדי למצוא אותם. כשכל קבוצה יוצרת רצף רציף אחד, היא מבצעת חיפוש בינארי של גבולות הקבוצות במקום, ולכן עלות החיפוש תלויה במספר הקבוצות שהתבקשו ולא במספר האיברים שנשמרים.
group_order() מייצרת את הסידור הזה. זה מיון יציב לפי אינדקס הקבוצה: ערך האופרטור לא משתנה, ואיברים בתוך קבוצה שומרים על הסדר היחסי ביניהם.
>>> from qiskit_bosons.operators.terms import group_order
>>> scattered = BosonOperator.from_terms_with_groups(
... [(actions, coeff, group) for (actions, coeff, _), group in zip(terms, [1, 0, 1, 0, 2, 2, 2])]
... )
>>> ordered = group_order(scattered)
>>> ordered.groups
[0, 0, 1, 1, 2, 2, 2]
>>> ordered.equiv(scattered)
True
שני הסידורים מחזירים את אותם אופרטורים; רק העלות שונה:
>>> request = [2, 0]
>>> all(
... a.equiv(b)
... for a, b in zip(
... scattered.split_out_groups(request), ordered.split_out_groups(request), strict=True
... )
... )
True
השלבים הבאים
הקיבוץ נבחר כאן, ברמה הבוזונית, בזמן שמבנה המודל עדיין גלוי.
-
סינתזה של שער Evolution במרחב בוזוני מראה איך להשתמש בו כדי לבצע Trotterization של התפתחות, ולמה זה מפחית שגיאה שנוסחת מכפלה ברמת הקיוביטים לא יכולה להפחית.
-
עקרונות התכנון של ייצוגי אופרטורים מסביר את מבנה הנתונים של האופרטור שנושא את אינדקסי הקבוצות, ואיך האיברים שלו נשמרים ומסודרים.
-
2. קיבוץ לפני מיפוי מפעיל קיבוץ על מודל שלם.