Gruparea termenilor operatorului: folosește structura operatorului
Un BosonOperator poate eticheta fiecare dintre termenii săi cu un index de grup. Etichetele nu au semnificație proprie: ele spun doar care termeni aparțin împreună. Ce înseamnă etichetele este decis de ceea ce le consumă. În acest pachet, consumatorii sunt poarta de evoluție în timp, care este împărțită într-un factor pe grup, și pasul aleatoriu qDRIFT, care eșantionează grupuri întregi.
Acest ghid arată cum se atribuie grupuri, cum se verifică proprietățile pe care se bazează un consumator și cum să aranjezi operatorul astfel încât alegerea câtorva grupuri dintr-unul mare să rămână ieftină.
Acest pachet nu include nicio strategie automată de grupare. Atribui tu indecșii, pornind de la ce știi despre modelul tău.
Atribuirea grupurilor
Exemplul de lucru este un lanț Bose-Hubbard cu trei situri. Fiecare legătură contribuie cu o pereche de salt, iar interacțiunile locale formează o componentă diagonală. Cea mai simplă metodă de a construi un operator grupat este from_terms_with_groups(), care preia fiecare termen împreună cu indexul său de grup ca triplet (actions, coefficient, group). Decizi grupul în momentul în care scrii termenul, când încă știi de ce aparține acolo:
>>> 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)
Termenii sunt stocați în ordinea în care îi dai, deci indecșii de grup nu pot ajunge atașați termenilor greșiți.
Un operator care există deja trebuie grupat ulterior, prin atribuirea atributului groups. Aici este nevoie de grijă, deoarece from_dict() nu specifică ordinea în care își stochează termenii. Scrierea listei pe baza poziției poate atașa atunci un index termenului greșit. Derivă în schimb fiecare index din termenul însuși:
>>> 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)
Numărul de indecși trebuie să corespundă numărului de termeni. Orice altceva este respins, deoarece o listă prea scurtă ar elimina silențios termenii de la coadă oriunde termenii sunt iterați împreună cu grupurile lor:
>>> try:
... hamiltonian.groups = [0, 1]
... except ValueError as error:
... print(error)
expected one group index per term, but got 2 for 7 terms
Verifică pe ce se bazează consumatorul
Trei funcții din qiskit_bosons.operators.terms răspund fiecare la câte o întrebare îngustă despre o grupare existentă. Niciuna nu spune că o grupare este „corectă”, deoarece corectitudinea este relativă la consumator.
Este fiecare grup hermitian? Aceasta este proprietatea care contează cel mai mult. Evoluția în timp a unui grup este unitară doar dacă grupul este hermitian, iar un singur termen de salt nu este. Doar perechea cu adjunctul său este hermitiană:
>>> from qiskit_bosons.operators.terms import groups_are_hermitian
>>> groups_are_hermitian(hamiltonian)
[True, True, True]
O grupare care desparte o pereche de salt este prinsă imediat:
>>> 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]
Cei patru termeni de salt sunt detectați. Cele trei intrări True sunt termenii locali , care sunt hermitieni prin ei înșiși. Un mod se poate repeta în mod legitim într-un termen și nimic nu este eliminat din această cauză. Indicatorii urmează ordinea din terms, motiv pentru care fiecare poate fi citit în raport cu termenul pe care îl descrie.
Sunt coeficienții uniformi? O formulă de produs randomizată cântărește un grup după magnitudinea medie a coeficienților săi, ceea ce este o descriere fidelă a grupului doar dacă magnitudinile coincid:
>>> 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]
Niciuna dintre verificări nu o implică pe cealaltă: coeficienții uniformi nu fac un grup hermitian, iar un grup hermitian poate amesteca magnitudini. În mod implicit se compară magnitudinile, deoarece aceasta este ceea ce mediază group_coeff_means(). O pereche hermitiană cu coeficienți complecși are magnitudini egale, dar coeficienți inegali, deci satisface doar forma implicită:
>>> 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() este construită pe is_hermitian(), care compară un operator cu adjunctul său după ordonarea normală. Aceasta este o identitate a algebrei netrunchiate, deci un grup care trece verificarea este hermitian ca operator bosonic. Vezi Principii de proiectare ale reprezentărilor operatorilor pentru modul în care produsele de operatori de scară sunt ordonate și comparate.
Ordonează termenii după grup
split_out_groups() returnează câte un operator pentru fiecare grup cerut. Când termenii unui grup sunt împrăștiați prin operator, funcția trebuie să parcurgă fiecare termen pentru a-i găsi. Când fiecare grup formează o secvență contiguă, ea caută binar limitele grupurilor, așa că „costul” unei căutări depinde de numărul de grupuri cerute, nu de numărul de termeni păstrați.
group_order() produce acea dispunere. Este o sortare stabilă după indicele grupului: valoarea operatorului nu se schimbă, iar termenii dintr-un grup își păstrează ordinea relativă.
>>> 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
Ambele dispuneri returnează aceiași operatori; diferă doar costul:
>>> 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
Pașii următori
Gruparea este aleasă aici, la nivel bosonic, cât timp structura modelului este încă vizibilă.
-
Sintetizează o poartă de evoluție în spațiul bosonic arată cum o poți folosi pentru a aplica Trotter unei evoluții și de ce aceasta reduce o eroare pe care o formulă de produs la nivel de qubit nu o poate reduce.
-
Principii de proiectare ale reprezentărilor operatorilor explică structura de date a operatorului care poartă indicii grupurilor și cum sunt stocați și ordonați termenii săi.
-
2. Grupează înainte de mapare aplică o grupare unui model complet.