דלג לתוכן הראשי
פרויקט זה בשלב בטא. מצאת באג? פתח issue ב-GitHub. יש שאלה או רעיון? שאל ב-Discussions — נשמח לשמוע ממך!
מה איכות התרגום בדף זה?

עקרונות העיצוב של ייצוגי אופרטורים

המדריך הזה מסביר את עקרונות העיצוב והמושגים המרכזיים המשותפים לכל ייצוגי האופרטורים במודול operators.

סקירה​

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

  • מבנה נתונים דליל: אופרטורים מקודדים רק פעולות שאינן זהות. פריסת הנתונים הפנימית בדרך כלל בהשראת פורמטים של מטריצות דלילות, מה שמאפשר אחסון וחישוב יעילים למערכות עם הרבה מודים אבל מעט יחסית תרומות משמעותיות.

  • איטרציה על איברים ושחזור: בלי קשר לאחסון הדליל הפנימי, האופרטורים מספקים ממשק איטרציה עקבי, כך שאפשר לבדוק, לסנן ולהמיר איברים בלי להבין את מבנה הנתונים שמתחת, ואז לשחזר אופרטורים חדשים מהאיברים ששונו.

  • אינדוקס מבוסס מודים: אופרטורים משתמשים באינדקסי מודים מופשטים כדי לסמן דרגות חופש פרמיוניות, מה שמאפשר מיפוי גמיש ממערכות פיזיקליות לייצוג האופרטור.

  • קיבוץ איברים ויחסי חילוף: אופרטורים תומכים באופן מובנה במידע קיבוץ שמשייך איברים לאינדקסי קבוצות. זה מאפשר אופטימיזציות ושימור מבנה פיזיקלי בלי צורך במבני נתונים נפרדים. ראו את מדריך הקיבוץ לשימוש מעשי.

  • פעולות אריתמטיות ומתמטיות: כל האופרטורים מממשים סט עקבי של פעולות אריתמטיות (כמו חיבור, כפל והרכבה) ופונקציות מתמטיות באמצעות הפרוטוקול OperatorTrait, מה שמאפשר קוד אחיד על פני סוגי אופרטורים שונים.

  • סדר איברים בעת אופרטור וצורות נורמליות: אופרטורים שקולים מתמטית יכולים להיות בעלי ייצוגים והתנהגות שונים מאוד באלגוריתמים קוונטיים. כל ייצוגי האופרטורים תומכים בכמה צורות נורמליות (על בסיס יחסי חילוף ספציפיים לאלגברה) כדי להגיע לייצוגי אופרטורים קנוניים, צפויים וניתנים לאופטימיזציה.

מבנה נתונים דליל​

כל האופרטורים משתמשים בייצוג דליל שבו רק פעולות שאינן זהות נשמרות. כל פעולה שאינה זהות מורכבת ממקדם (מספר מרוכב) ומרצף פעולות על מודים מסוימים.

הגישה הזו מקטינה דרמטית את השימוש בזיכרון ואת זמן החישוב, במיוחד למערכות עם הרבה מודים אבל מעט יחסית תרומות משמעותיות. בקידוד פעולות שאינן זהות בלבד, הפעולות מתמקדות רק במה שחשוב, וכך אפשר לעבוד עם מערכות גדולות שלא היו ישימות בייצוגים צפופים. בנוסף, אופרטורים מתרחבים באופן טבעי לכל מספר מודים; אופרטור שפועל על המודים {0, 1} עובד ללא שינוי במערכות עם הרבה יותר מודים, כי מודים שלא מושפעים הם זהות באופן משתמע.

איברים זהים נשמרים בנפרד במהלך פעולות אריתמטיות וצריך לאחד אותם במפורש אם יש בכך צורך.

פורמט האחסון הפנימי​

באופן פנימי, אופרטורים נשמרים במערכים בהשראת פורמטים של מטריצות דלילות:

  • מערך מקדמים: המקדם המרוכב של כל איבר

  • מערך אינדקסי מודים: המודים הפרמיוניים שכל פעולה פועלת עליהם

  • מערך גבולות: אינדקסים שמסמנים איפה המודים של כל איבר מתחילים ונגמרים במערך המודים

חשוב

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

הדוגמאות הבאות מראות איך המערכים האלה מאורגנים. קודם, בנייה ישירה באמצעות המערכים הדלילים:

>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> # Construct operators directly using sparse arrays
>>> # First operator: 1.0 * +0 -1
>>> op1 = FermionOperator(
... coeffs=[1.0],
... actions=[True, False],
... modes=[0, 1],
... boundaries=[0, 2],
... )
>>>
>>> # Second operator: 1.0 * +2 -3
>>> op2 = FermionOperator(
... coeffs=[1.0],
... actions=[True, False],
... modes=[2, 3],
... boundaries=[0, 2],
... )
>>>
>>> # Combine the sparse operators
>>> op1 += op2
>>> print(format(op1))
1.000000e0 +0.000000e0j * (+0 -1)
1.000000e0 +0.000000e0j * (+2 -3)

שיטות בנייה נוחות​

למפתחי Python יש כמה שיטות בנייה נוחות שמסתירות את פרטי האחסון הדליל. הן מקלות על בניית אופרטורים בלי לדאוג לניהול מערכי המקדמים, המודים והגבולות:

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct operators using operator algebra notation
>>> op1 = FermionOperator.from_dict({(cre(0), ann(1)): 1.0})
>>> op2 = FermionOperator.from_dict({(cre(2), ann(3)): 1.0})
>>>
>>> # The result is sparse even when combining them
>>> op1 += op2
>>> print(format(op1))
1.000000e0 +0.000000e0j * (+0 -1)
1.000000e0 +0.000000e0j * (+2 -3)
רמז

מימושים בודדים של אופרטורים עשויים לתמוך בשיטות בנייה נוספות שמתאימות למקרה השימוש הספציפי שלהם. בדקו בתיעוד ה-API של סוג האופרטור שלכם כדי לראות את כל אפשרויות הבנייה.

איטרציה על איברים ושחזור​

אופרטורים מספקים ממשק איטרציה עקבי באמצעות OperatorTrait.iter_terms(), בלי קשר לייצוג הדליל הפנימי שלהם. לכן אפשר לבדוק, לסנן או לשנות איברים בלי להבין את מבנה הנתונים שמתחת. לאחר מכן אפשר לשחזר אופרטור חדש מהאיברים שעברו שינוי באמצעות OperatorTrait.from_terms().

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct an operator with terms of different orders
>>> op = FermionOperator.from_dict({
... (): 0.5, # constant term (order 0)
... (cre(0), ann(1)): 1.0, # two-body term (order 2)
... (cre(0), cre(1), ann(1), ann(0)): 0.25 # four-body term (order 4)
... })
>>>
>>> # Filter to keep only terms of order 2
>>> order_two_terms = [
... (term, coeff) for term, coeff in op.iter_terms()
... if len(term) == 2
... ]
>>>
>>> # Reconstruct operator from filtered terms
>>> filtered_op = FermionOperator.from_terms(order_two_terms)
>>> print(f"Original operator has {len(op)} terms")
Original operator has 3 terms
>>> print(f"Filtered operator has {len(filtered_op)} term")
Filtered operator has 1 term

אינדוקס מבוסס מודים​

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

ההפשטה הזו קיימת גם במודול qiskit_fermions.circuit, שבו FermionicCircuit פועל על אוגר של מודים פרמיוניים. גם בייצוגי האופרטורים וגם בייצוגי המעגלים, המודים מספקים דרך עקבית ובלתי תלויה באלגברה לציין אילו דרגות חופש משתתפות בפעולה נתונה.

חשוב

המימושים הנוכחיים משתמשים במודים חסרי ספין: כל ייצוגי האופרטורים שהמודול הזה מספק כרגע מתייחסים למודים כאל דרגות חופש פרמיוניות חסרות ספין. כלומר, אם במערכת שלכם יש גם אלקטרונים או פרמיונים עם ספין למעלה וגם עם ספין למטה, צריך למפות אותם במפורש למודים נפרדים (למשל, מודים 0-3 לספין למעלה של ארבעה אורביטלים מרחביים, מודים 4-7 לספין למטה, או כל מוסכמה אחרת שתבחרו).

העיצוב הזה שומר על ייצוגי הליבה פשוטים וכלליים, ומונע כפיית מוסכמת סדר ספינים מסוימת. מודולי עזר כמו qiskit_fermions.operators.library מספקים פונקציות נוחות (למשל FCIDump.from_file()) שמטפלות במיפויים כאלה במקומכם בעת טעינת נתוני מבנה אלקטרוני.

רמז

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

קיבוץ איברים ויחסי חילוף​

בדומה למקדמים ולאינדקסי המודים, אופרטורים יכולים לשמור באופן אופציונלי מערך קבוצות שמשייך כל איבר לאינדקס קבוצה. כיוון שהקיבוץ משולב ישירות בייצוג האופרטור כחלק ממבנה הנתונים הדליל, מידע הקיבוץ עובר באופן טבעי עם האופרטור דרך טרנספורמציות. כך אפשר לנצל מבנה באופן שיטתי, בין אם הוא נובע מתכונות פיזיקליות, מיחסים אלגבריים או מסימטריות ספציפיות לבעיה. את המידע המובנה אפשר להשתמש בו בהמשך בפעולות כמו סינתזה ופירוק של מעגלים, באמצעות שיטות כמו OperatorTrait.split_out_groups().

להנחיה מפורטת על קיבוץ איברי אופרטור בתהליכי העבודה שלכם, ראו את המדריך קיבוץ.

>>> from qiskit_fermions.operators import MajoranaOperator, gamma
>>> op = MajoranaOperator.from_dict({
... (gamma(0, False),): 1.0,
... (gamma(1, False),): 1.0,
... (gamma(2, False), gamma(3, False)): 1.0
... })
>>> # Assign group indices to terms
>>> op.groups = [0, 0, 1]
>>> # Partition operator by groups
>>> grouped_ops = op.split_out_groups()

פעולות אריתמטיות ומתמטיות​

כל האופרטורים מממשים את הפרוטוקול OperatorTrait, שמספק קבוצה אחידה של פעולות על פני סוגי אופרטורים שונים. כך קוד שנכתב עבור ייצוג אופרטור אחד עובד באופן אחיד גם עם האחרים. ראו qiskit_fermions.protocols כדי להבין כיצד הפרוטוקול הזה קשור לפרוטוקולים האחרים שהחבילה מגדירה.

הפרוטוקול כולל פעולות אריתמטיות (כמו חיבור, כפל והרכבה), פעולות מבניות (איטרציה על איברים, ניתוח תמיכה במודים, שינוי שמות), פונקציות מתמטיות (סידור נורמלי, פישוט, בדיקת שקילות) ועוד. ראו את התיעוד של OperatorTrait להפניה מלאה של כל הפעולות הזמינות.

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct a Hermitian operator: H = +0 -1 + +1 -0
>>> op = FermionOperator.from_dict({
... (cre(0), ann(1)): 1.0,
... (cre(1), ann(0)): 1.0
... })
>>>
>>> # Check if the operator is Hermitian
>>> print(f"Operator is Hermitian: {op.is_hermitian(atol=1e-10)}")
Operator is Hermitian: True
הערה

is_hermitian() מוודא ש-H−H†=0H - H^\dagger = 0 על ידי צמצום ההפרש הזה לצורה נורמלית והשוואתו מול zero(). בניסוח של פעולות הפרוטוקול האחרות, הוא מבצע את הפעולות הבאות:

>>> difference = (op - op.adjoint()).normal_ordered().simplify(atol=1e-10)
>>> difference.equiv(FermionOperator.zero(), atol=1e-10)
True

עוצמת הבדיקה הזו תלויה בסוג האופרטור, משום שהיא תלויה בכמה שלמה הצורה הנורמלית של אותו סוג בכיווץ של יוצרים סמוכים. תוצאת True תמיד אמינה, אבל בחלק מהסוגים תוצאת False היא שמרנית: ראו is_hermitian() לפרטים.

חשוב

הדוגמה משתמשת ב-atol=1e-10. הפרמטר atol (סבילות מוחלטת) קובע סף. מקדמים שגודלם קטן מ-atol נחשבים לאפס ונזרקים. זה חיוני ליציבות נומרית בעת השוואת אופרטורים, מכיוון שחשבון נקודה צפה עלול להכניס שגיאות עיגול קטנות שאחרת ימנעו מאופרטורים שקולים להיות מזוהים ככאלה.

רמז

למרות שהפרוטוקול OperatorTrait מספק ממשק משותף, מימושים בודדים של אופרטורים עשויים להציע שיטות נוחות נוספות שאינן חלק מהפרוטוקול. למשל, FermionOperator ו-MajoranaOperator מספקים שיטה max_rank() שמדווחת על אורך האיבר הארוך ביותר שלהם. תמיד עיינו בתיעוד ה-API של סוג האופרטור שלכם כדי לגלות את כל הפונקציונליות הזמינה.

סידור איברי אופרטור וצורות נורמליות​

אתגר יסודי באלגברת אופרטורים קוונטיים הוא שאופרטורים שקולים מתמטית אפשר לייצג בדרכים רבות ושונות, ולכל אחת השלכות שונות על אלגוריתמים קוונטיים. אותו אופרטור אפשר לכתוב בצורות שקולות אלגברית (למשל, a†ba^\dagger b אפשר לבטא כ-ba†+[a†,b]ba^\dagger + [a^\dagger,b]), ובכל זאת הייצוגים האלה מובילים להתנהגות שונה בסינתזה של מעגלים, בפישוט ובאלגוריתמים נומריים.

ייצוגי האופרטורים תומכים בפעולות של סידור נורמלי שהופכות אופרטורים לצורות קנוניות ייחודיות לכל אלגברה, באמצעות יחסי חילוף. זה מאפשר השוואות אמינות (לשני אופרטורים שקולים יש צורות מסודרות נורמלית זהות), חושף פישוטים (יחסי חילוף גורמים לאיברים להתבטל או להתחבר), ותומך באלגוריתמים שדורשים צורות אופרטור מסוימות לצורך נכונות ויעילות.

חשוב

ייצוגי אופרטורים שונים עשויים לממש סידור נורמלי על בסיס יחסי חילוף שונים שמתאימים לאלגברה שלהם. למשל, סידור נורמלי פרמיוני משתמש ביחסי אנטי-חילוף ({ci,cj†}=δij\{c_i, c_j^\dagger\} = \delta_{ij}), ואילו סידור נורמלי של מיורנה משתמש במוסכמות אלגבריות אחרות ({γi,γj}=2δij\{\gamma_i, \gamma_j\} = 2\delta_{ij}). תמיד עיינו בתיעוד של סוג האופרטור שלכם כדי להבין כיצד הסידור הנורמלי ממומש.

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Two different representations of the same operator
>>> op1 = FermionOperator.from_dict({(ann(0), cre(0)): 1.0})
>>> op2 = FermionOperator.from_dict({(): 1.0, (cre(0), ann(0)): -1.0})
>>>
>>> # Direct comparison fails due to different forms
>>> op1.equiv(op2, atol=1e-10)
False
>>>
>>> # Normal-order both and compare again
>>> op1_normal = op1.normal_ordered()
>>> op2_normal = op2.normal_ordered()
>>> op1_normal.equiv(op2_normal, atol=1e-10)
True
רמז

שיטת הפרוטוקול OperatorTrait.normal_ordered() משאירה בכוונה את הארגומנטים המיקומיים והמילות מפתח שלה בלתי מוגדרים, וכך מאפשרת למימושי אופרטור קונקרטיים להגדיר פרמטרים מתכווננים ששולטים בצורה הקנונית המדויקת שמתקבלת. זה מאפשר אופטימיזציות ייחודיות לאופרטור וגרסאות של צורה נורמלית שמותאמות לאלגברה או למקרה השימוש שלהם. בדקו בתיעוד ה-API של סוג האופרטור שלכם אילו פרמטרים זמינים.