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

איך החבילה הזו קשורה ל-ffsim

חשוב

המושגים במדריך הזה זמינים רק ב-Python API.

ffsim והחבילה הזו מתחלקות בעבודה ולא חופפות, לאורך קו אחד:

ffsim אחראית עלqiskit-fermions אחראית על
אופרטורי האנזץ ברמה גבוהה (UCJOpSpinBalanced, UCCSDOpRestrictedReal ואחיהם), הבנויים מאמפליטודות coupled-cluster או מווקטור פרמטריםהמעגל הפרמיוני (FermionicCircuit) שהאופרטורים האלה הופכים אליו, והטרנספילציה שלו למעגל קיוביטים תחת כל קידוד מפרמיון לקיוביט
סימולציה מהירה במרחב קומפקטי של מספר חלקיקים קבועמסגרת ה-mapper ומבני הנתונים של האופרטורים שמהם המעגל בנוי

ההשלכה המעשית פועלת לשני הכיוונים. המתמטיקה של האנזץ נמצאת ב-ffsim, ולכן UCJ ו-UCC מקבלים אופרטור ffsim ישירות כקלט. מנגד, שערי Qiskit של ffsim עצמה ספציפיים לטרנספורמציית Jordan-Wigner, ולכן הורדת אחד מאופרטורי האנזץ שלה דרך קידוד אחר היא מה שהבאתו לכאן קונה לכם.

סימולציה היא המקום שבו השתיים נפגשות, והמדריך הזה מסביר איך. ffsim היא ה-Backend לסימולציה של החבילה הזו, ומגיעים אליה דרך הפרוטוקולים שלה עצמה ולא דרך עטיפה: מדריך SKQD קורא ל-ffsim.apply_unitary(), ל-ffsim.linear_operator() ול-ffsim.sample_state_vector() ישירות על אובייקטי FermionicCircuitועל אובייקטי FermionOperatorללא שלב המרה.

התחברות לממשק הסימולציה של ffsim​

ffsim היא סימולטור בעל ביצועים גבוהים למעגלים קוונטיים פרמיוניים, שמנצל שימור של מספר חלקיקים ושל ספין-Z כדי לייצג וקטורי מצב בצורה קומפקטית יותר מוקטור מצב גנרי של קיוביטים במימד 2n2^n. היא מגדירה פרוטוקולים שכל אובייקט יכול לממש כדי להשתתף במנגנון הסימולציה שלה (ראו qiskit_fermions.protocols עבור הפרוטוקולים של החבילה הזו עצמה, שעוקבים אחר אותו עיצוב):

  • ffsim.SupportsApplyUnitary, באמצעות המתודה _apply_unitary_(vec, norb, nelec, copy), שמפעילה את האובייקט כאוניטרי על וקטור מצב של מספר חלקיקים קבוע

  • ffsim.SupportsLinearOperator, באמצעות המתודה _linear_operator_(norb, nelec), שמחזירה מבט של האובייקט כ-scipy.sparse.linalg.LinearOperator על אותו סקטור

  • ffsim.SupportsTrace, באמצעות המתודה _trace_(norb, nelec), שמחזירה את העקבה של האובייקט על אותו סקטור.

החבילה הזו מממשת את הממשק הזה: כל שער פרמיוני ב-qiskit_fermions.circuit.library מספק _apply_unitary_, ו-FermionOperator מספק _linear_operator_ ו-_trace_. זה מה שהופך את האופרטורים והמעגלים של החבילה הזו לתואמים ל-ffsim, כך שהכלים שלה עובדים עליהם באופן טבעי, בלי שלב המרה. ffsim.apply_unitary() יכול לסמלץ FermionicCircuit מקצה לקצה, ffsim.linear_operator() יכול לאלכסן FermionOperator, וכלי דגימה כמו ffsim.sample_state_vector() (בשימוש במדריך SKQD כדי להפוך וקטור מצב מסומלץ לספירות מדידה) עובדים ללא שינוי.

מה ש-_linear_operator_ מחזיר הוא LinearOperator רגיל של SciPy, ולכן scipy.sparse.linalg.eigsh() וחבריה עובדים עליו גם כן.

>>> import ffsim
>>> import numpy as np
>>>
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.operators import FermionOperator, ann, cre
>>>
>>> norb, nelec = 2, (1, 1)
>>> hamiltonian = FermionOperator.from_terms([
... ([cre(0), ann(1)], 0.5),
... ([cre(1), ann(0)], 0.5),
... ])
>>>
>>> circuit = FermionicCircuit(2 * norb)
>>> circuit.append(Evolution(2 * norb, hamiltonian, time=1.0), circuit.modes)
>>>
>>> reference = ffsim.hartree_fock_state(norb, nelec)
>>> state = ffsim.apply_unitary(reference, circuit, norb=norb, nelec=nelec) # native ffsim call

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

ffsim היא תלות אופציונלית​

ffsim מוצהרת כתוספת אופציונלית (pip install "qiskit-fermions[ffsim]", או בעקיפין על ידי [all]), ומוגנת בזמן ריצה על ידי HAS_FFSIM (כפי שהוצג למעלה). התקנתה פותחת את הסימולציה: ffsim.SupportsLinearOperator ו-ffsim.SupportsTrace ממומשים על ידי המרת FermionOperator של החבילה הזו ל-ffsim.FermionOperator, ולכן בלי ffsim הם זורקים MissingOptionalLibraryError. כל מה שלא מסמלץ (בניית אופרטורים, מיפוים, וטרנספילציה של המעגלים שמתקבלים) לא צריך אותה בכלל.

סימולציה פרמיונית חיה בסקטור של מספר חלקיקים קבוע​

גם _apply_unitary_ וגם _linear_operator_ מקבלים ארגומנט nelec ומייצגים את וקטור המצב מעל בסיס הדטרמיננטות של מספר חלקיקים קבוע עבור הסקטור (norb, nelec) הזה, בהתאם להגדרת מרחב ה-FCI של ffsim (ובעקיפין של PySCF), ולא מעל המרחב המלא במימד 2num_modes2^{\text{num\_modes}} שסימולטור קיוביטים כללי היה משתמש בו. זה מרחב קטן בהרבה (גודלו הוא מכפלה של מקדמים בינומיים ולא חזקת שתיים), אבל הוא מגיע עם הגבלה נוקשה. רק אופרטורים ושערים ששומרים על מספר החלקיקים (ובמקרה בעל הספין, על מספר החלקיקים של כל מין ספין בנפרד) אפשר לייצג בו. לאופרטור שפעולתו הייתה מעבירה אמפליטודה למספר חלקיקים אחר אין לאן לשים אותה.

ffsim פותרת את זה בדחיית אופרטור כזה על הסף: המרה שלו לאופרטור לינארי זורקת ValueError שמציין איזה חוק שימור נכשל. זו ברירת המחדל הנכונה, כי הפעלת exp⁡(−itH)\exp(-i t H) עבור המילטוניאן HH עם איבר שלא שומר על מספר החלקיקים הייתה הופכת מה שהיה אמור להיות אוניטרי למפה לא אוניטרית וחסרת משמעות פיזיקלית.

  • Evolution מעביר את הדחייה הזו מהאופרטור שהוא מפתח.

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

>>> from qiskit_fermions.circuit.library import Evolution
>>>
>>> non_conserving = FermionOperator.from_terms([([cre(0), cre(1)], 1.0)]) # creates 2 particles
>>> gate = Evolution(2 * norb, non_conserving, time=1.0)
>>>
>>> try:
... gate._apply_unitary_(reference, norb, nelec, copy=True)
... except ValueError as exc:
... print("rejected:", exc)
rejected: The given FermionOperator could not be converted to a LinearOperator because it does not conserve particle number and the z component of spin. Conserves particle number: False Conserves spin z: False

לכן סימולציה שאינה שומרת על מספר החלקיקים אינה אפשרית במרחב הפרמיוני, מעצם הגדרתה של ייצוג המגזר הקבוע, וזה המחיר של מרחב FCI הקומפקטי שהופך את הסימולציה הפרמיונית לאפשרית מלכתחילה. אם האלגוריתם שלך זקוק לאופרטורים שמפרים את מספר החלקיקים (למשל ערוץ שגיאה ילידי-קיוביט, או אופרטור שנבנה עבור המילטוניאן ממופה שאינו שומר על מספרי החלקיקים איבר אחר איבר), עשה קודם transpile לקיוביטים וסמלץ את QuantumCircuit שמתקבל באמצעות סימולטור ברמת קיוביטים. כל מסלול transpilation מתאים לכך; המדריכים על מעגל פרמיוני ו-transpilation נכנסים ליותר פרטים, אבל generate_preset_jw_pass_manager() הוא ברירת מחדל סבירה.

מוסכמת בלוקי הספין למערכות בעלות ספין​

nelec מוגדר כ-int | tuple[int, int], וסוג הערך שלו, ולא דגל נפרד, הוא שבוחר באחת מפריסות המודים הנתמכות:

  • int בוחר בפרשנות חסרת ספין. norb המודים מטופלים ישירות כאורביטלים חסרי ספין, ו-nelec הוא המספר הכולל של החלקיקים.

  • זוג (n_alpha, n_beta) בוחר בפרשנות בעלת ספין של 2 * norb מודים לפי מוסכמת בלוקי ספין קבועה. המודים 0 .. norb הם אורביטלי אלפא (ספין למעלה) והמודים norb .. 2 * norb הם אורביטלי בטא (ספין למטה). כל סוג ספין נשמר באופן בלתי תלוי; איבר של אלפא בלבד יכול להזיז אלקטרון בין מודי אלפא אבל לעולם לא אל מוד בטא, ולהפך.

ההכרעה הזו מתבצעת בכל נקודת כניסה של פרוטוקול ffsim בחבילה הזו (בנאי שערים, מימושי _apply_unitary_placed_, והידור המגזרים של גרעין ה-Rust הילידי), והיא המוסכמה שבה משתמשים לאורך המדריכים LUCJ ו-SKQD (למשל, InitializeModes.from_hartree_fock() ממלא את התפוסות של אלפא במודים 0 .. n_alpha ואת התפוסות של בטא במודים norb .. norb + n_beta). כדאי להשוות זאת למודול qiskit_fermions.operators ול-FermionicCircuit בכלל, שמשתמשים באינדקסי מודים כלליים ללא משמעות ספין מובנית (ראה את המדריך למעגל פרמיוני). משמעות בלוקי הספין נכפית רק כאשר nelec בעל ספין מועבר לקריאת סימולציה, ולא נאפית בתוך הייצוג של האופרטור או המעגל עצמו.

>>> from qiskit_fermions.circuit.library import InitializeModes
>>>
>>> norb, nelec = 3, (2, 1)
>>> init = InitializeModes.from_hartree_fock(norb, nelec)
>>> print([bool(occ) for occ in init.occupation]) # alpha modes 0,1; beta mode 3 (= norb + 0)
[True, True, False, True, False, False]

השלבים הבאים​