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

עבודה עם מעגלים בוזוניים

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

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

למה להשתמש במעגלים בוזוניים​

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

  1. הגדירו את ההמילטוניאן הבוזוני, למשל H=−∑ijbi†bjH = -\sum_{ij} b^\dagger_i b_j.

  2. בחרו קטימה וקידוד, ומפו איתם את ההמילטוניאן לאופרטור קיוביטים.

  3. ממשו את ההתפתחות בזמן במרחב הקיוביטים, בעזרת טרוטריזציה או שיטה אחרת.

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

BosonicCircuit מעביר את ההחלטות האלה לסוף:

  1. הגדירו את ההמילטוניאן הבוזוני.

  2. בטאו את ההתפתחות בזמן ישירות על אופנים בוזוניים, עם שערים בוזוניים.

  3. בחרו את הקטימה ואת הקידוד כחלק מה-Transpile.

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

אינדוקס אופנים גנרי​

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

הוספת שערים​

שערים מתווספים על אופנים. רק מופעים של BosonicGate מתקבלים:

>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import Evolution, InitializeModes
>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> hopping = BosonOperator.from_dict({(cre(0), ann(1)): -1.0, (cre(1), ann(0)): -1.0})
>>> hopping.groups = [0] * len(hopping) # the pair is Hermitian, its terms alone are not
>>>
>>> prepare = BosonicCircuit(2)
>>> prepare.append(InitializeModes([2, 0]), prepare.modes)
>>> step = BosonicCircuit(2)
>>> step.append(Evolution(2, hopping, time=0.1), step.modes)

כל דבר אחר נדחה, והשגיאה נותנת שם לסוג הפוגע:

>>> from qiskit.circuit.library import HGate
>>> try:
... prepare.append(HGate(), [0])
... except ValueError as error:
... print(error)
Unsupported instruction type: <class '...HGate'>

הרכבת מעגלים​

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

>>> program = prepare.compose(step)
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A composed BosonicCircuit.

כברירת מחדל מוחזר מעגל חדש ושני האופרנדים נשארים ללא שינוי. העבירו inplace=True כדי לשנות את הראשון במקום זאת, ו-front=True כדי לשים את האופרנד לפני ההוראות הקיימות:

>>> step.compose(prepare, front=True)._inner.data == program._inner.data
True

אפשר להניח מעגל צר יותר על תת-קבוצה נבחרת של האופנים:

>>> single = BosonicCircuit(1)
>>> single.append(Evolution(1, BosonOperator.from_dict({(cre(0), ann(0)): 1.0}), 0.1), single.modes)
>>> wide = BosonicCircuit(3).compose(single, [2])
>>> wide.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A narrow-composed BosonicCircuit.

רק BosonicCircuit אחר מתקבל כאופרנד, כי compose של Qiskit עצמו היה משבץ שערי קיוביט ועוקף את הבדיקה ש-append אוכף.

חזרה על מעגל​

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

>>> len(step.repeat(0)._inner.data) # zero repetitions is the identity
0
>>> step.repeat(5).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A repeated BosonicCircuit.

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

הפרדת צעדים עם מחסומים​

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

>>> program = BosonicCircuit(2)
>>> program.append(InitializeModes([2, 0]), program.modes)
>>> program.barrier()
>>> program.barrier(program.modes[0], label="zero")
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A BosonicCircuit with barriers.

repeat(..., insert_barriers=True) שם אחד בין חזרות עוקבות, ואף אחד אחרי האחרונה:

>>> step.repeat(3, insert_barriers=True).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A repeated BosonicCircuit with barriers.

מחסומים והקידוד​

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

>>> from qiskit_bosons.mappers import LevelSpec
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>> lowered = generate_preset_occupation_pass_manager(LevelSpec(2, "unary_projected")).run(program)
>>> lowered.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A QuantumCircuit showing the expansion of barrier to cover the entire qubit register.

קריאת מעגל בוזוני​

ל-BosonicCircuit אין measure או measure_all. מה שביט נמדד אומר תלוי בקידוד. תחת קידוד אונארי התפוסה של אופן מפוזרת על כמה קיוביטים וצריך לקרוא אותה מתבנית של ביטים, ותחת קומפקטי היא מספר בינארי, ולכן מדידה שמציינת אופן הייתה צריכה להכיר את הקידוד, מה שמעגל בוזוני בכוונה לא מכיר. מדדו במקום זאת את QuantumCircuit שעבר Transpile, ופענחו את מחרוזות הביטים עם LevelEncoding.level_to_bits() וההיסטים של LevelSpec שהורדתם איתו. סימולציה של דינמיקת בוז-האברד עם בחירת קידוד עושה את זה למודל שלם.

>>> hasattr(BosonicCircuit, "measure_all")
False

הצעדים הבאים​