Přeskočit na hlavní obsah
Tento projekt je v betě. Našel jsi chybu? Otevřít issue na GitHubu. Máš otázku nebo nápad? Zeptej se v Discussions — rádi si poslechneme tvou zpětnou vazbu!
Jaká je kvalita překladu na této stránce?

Principy návrhu reprezentací operátorů

Tento průvodce vysvětluje, jak je BosonOperator organizován a jak s ním pracovat: jak ho sestavit, jak operátory prohlížet a kombinovat a jak dva operátory převést do tvaru, ve kterém je lze porovnat. Začni tady, když zapisuješ hamiltonián.

BosonOperator je přesný součet součinů bosonických kreačních a anihilačních operátorů s komplexními koeficienty. Nenese žádný cutoff obsazení. Bosonický mód pojme libovolně mnoho bosonů, takže jeho Fockův prostor je nekonečněrozměrný, a oříznutí vstupuje do hry až při mapování operátoru na qubity (viz Oříznutí Fockova prostoru). Vše v tomto průvodci se proto odehrává v přesné algebře kanonických komutačních relací,

[bi,bj†]=δij,[bi,bj]=[bi†,bj†]=0 .[b_i, b_j^\dagger] = \delta_{ij}, \qquad [b_i, b_j] = [b_i^\dagger, b_j^\dagger] = 0 \, .

Dva důsledky formují rozhraní. Index módu se může v jednom členu opakovat, protože bi†bi†b_i^\dagger b_i^\dagger je platný operátor, který vytváří dva bosony. Také přeuspořádání dvou faktorů nikdy nezavádí znaménko, ačkoli přesunutí anihilačního operátoru přes kreační operátor na stejném módu přidává člen, bibi†=bi†bi+1b_i b_i^\dagger = b_i^\dagger b_i + 1.

Uložení​

Operátor drží své členy ve čtyřech plochých polích, podobně jako komprimovaný řádkový formát (CSR) řídké matice:

coeffsJeden komplexní koeficient na člen.
actionsJedna booleovská hodnota na faktor: True pro kreační a False pro anihilační operátor.
modesJeden index módu na faktor.
boundariesPozice v actions a modes, kde každý člen začíná a končí.

Konstruktor přijímá tato čtyři pole přímo. Operátor níže má tři členy, a to 2b0†b12 b_0^\dagger b_1, −ib1†b1†-i b_1^\dagger b_1^\dagger a konstantu 0.50.5, a pole můžeš přečíst zpět metodami get_*:

>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>> op = BosonOperator(
... coeffs=[2.0, -1.0j, 0.5],
... actions=[True, False, True, True],
... modes=[0, 1, 1, 1],
... boundaries=[0, 2, 4, 4],
... )
>>> op.get_coeffs()
[(2+0j), (-0-1j), (0.5+0j)]
>>> op.get_actions()
[True, False, True, True]
>>> op.get_modes()
[0, 1, 1, 1]
>>> op.get_boundaries()
[0, 2, 4, 4]
>>> len(op)
3

První člen zabírá pozice 0 až 2 v actions a modes, druhý 2 až 4 a třetí 4 až 4, což je způsob, jakým se ukládá konstantní člen, tedy člen bez faktorů. Opakovaný mód 1 ve druhém členu se zachovává tak, jak je zapsán; člen se nikdy neodstraní ani nesloučí kvůli tomu, že se index módu objeví dvakrát.

Poznámka

Operátor vytvořený pomocí from_dict() neukládá své členy v pořadí slovníku, takže jeho pole mohou členy vypisovat při každém spuštění v jiném pořadí. Samotný operátor je stejný a nic níže, co operátory porovnává nebo vypisuje, na uloženém pořadí nezávisí.

Toto uspořádání činí konstrukci a aritmetiku levnou, s jedním důsledkem: operátor může obsahovat tentýž člen několikrát najednou. Součty a součiny členy přidávají a neslučují je, proto zavolej simplify(), když potřebuješ jedinečné členy (viz Aritmetika).

Sestavení operátorů​

Nejpřímější způsob sestavení operátoru je from_dict(), který přijímá slovník z n-tice faktorů na koeficient. Funkce cre() a ann() vytvářejí faktory a prázdná n-tice je konstantní člen:

>>> b = BosonOperator.from_dict({(ann(0),): 1.0})
>>> b_dag = BosonOperator.from_dict({(cre(0),): 1.0})
>>> number = BosonOperator.from_dict({(cre(0), ann(0)): 1.0})
>>> print(format(number))
1.000000e0 +0.000000e0j * (+0 -0)

Ve vypsaném tvaru +0 značí b0†b_0^\dagger a -0 značí b0b_0. Konstruktory zero() a one() vracejí aditivní a multiplikativní jednotku a from_terms() znovu sestaví operátor z iterátoru iter_terms().

Knihovna operátorů poskytuje bezrozměrné kvadratury polohy a hybnosti,

q^=b†+b2,p^=i(b†−b)2 ,\hat{q} = \frac{b^\dagger + b}{\sqrt{2}}, \qquad \hat{p} = \frac{i \left(b^\dagger - b\right)}{\sqrt{2}} \, ,

jako position() a momentum(). Společně s funkcemi komutátorů ze stejného modulu můžeš algebru ověřit přímo:

>>> from qiskit_bosons.operators.library import commutator, momentum, position
>>> q, p = position(0), momentum(0)
>>> q.is_hermitian() and p.is_hermitian()
True
>>> commutator(q, p).equiv(1j * BosonOperator.one())
True
>>> commutator(b, b_dag).equiv(BosonOperator.one())
True

Modul také poskytuje anti_commutator() a double_commutator(). Všechny tři přijímají operátory stejného typu a vracejí nový operátor.

Pozor na pořadí součinu​

Dva operátory lze násobit dvěma způsoby a snadno se pletou. Operátor @ je matematický součin, takže a @ b je ABAB. Operátor & je kompozice, která se řídí konvencí Qiskitu čtení zleva doprava: a & b nejprve aplikuje a a pak b, takže je to BABA. Funkce komutátorů používají @, takže commutator(a, b) je [A,B][A, B]. Pokud závorku sestavíš sám pomocí &, tiše ji znegueš:

>>> (b @ b_dag).equiv(b_dag @ b + BosonOperator.one())
True
>>> (b & b_dag).equiv(b_dag @ b)
True
>>> ((b @ b_dag) - (b_dag @ b)).equiv(BosonOperator.one())
True
>>> ((b & b_dag) - (b_dag & b)).equiv(-BosonOperator.one())
True

Prohlížení operátoru​

Operátor není přímo iterovatelný, protože jeho členy žijí v plochých polích výše. Použij místo toho iter_terms(), který pro každý člen vrací seznam dvojic (is_creation, mode) a koeficient. Iterátor vrací kopie, takže jejich změna operátor nemění.

>>> hopping = BosonOperator.from_dict(
... {
... (cre(0), ann(1)): -1.0,
... (cre(1), ann(0)): -1.0,
... (cre(2), cre(2), ann(2), ann(2)): 0.5,
... }
... )
>>> len(hopping)
3
>>> sorted(hopping.iter_terms())
[([(True, 0), (False, 1)], (-1+0j)), ([(True, 1), (False, 0)], (-1+0j)), ([(True, 2), (True, 2), (False, 2), (False, 2)], (0.5+0j))]

Několik metod shrnuje strukturu operátoru:

>>> hopping.get_support()
{0, 1, 2}
>>> hopping.min_num_modes()
3
>>> hopping.max_rank()
4

get_support() je množina módů, na které operátor působí, min_num_modes() je nejmenší registr, který ho pojme, a max_rank() je délka jeho nejdelšího členu.

Poznámka

Mód je abstraktní index, který označuje jeden bosonický stupeň volnosti. Sám o sobě nemá žádný význam: ty určuješ, který uzel mřížky, která vibrační souřadnice nebo který optický port mód představuje. BosonicCircuit používá stejné indexy (viz Práce s bosonickými obvody), takže operátor a hradla, která ho nesou, se shodnou na tom, co je mód 2, aniž by jedno z nich vědělo, co představuje. Nic v operátoru neurčuje, kolik úrovní mód má, což je také důvod, proč je velikost registru jen dolní mez; min_num_modes() je nejmenší registr, který operátor pojme, nikoli jeho vlastnost.

Metody obsazení udávají, kolik bosonů může jediný člen umístit do jednoho módu. To je dolní mez cutoffu, který mapování potřebuje. Není to postačující cutoff, protože opakované aplikace operátoru, například při časové evoluci, mohou zaplnit vyšší úrovně (viz Oříznutí Fockova prostoru):

>>> hopping.mode_occupations()
[1, 1, 2]
>>> hopping.mode_occupations([2, 0])
[2, 1]
>>> hopping.max_occupation()
2

Dvě další kontroly se zabývají zachovávanými veličinami. Operátor zachovává počet částic, když má každý člen stejně kreačních jako anihilačních operátorů. Předání seznamu velikostí bloků testuje stejnou podmínku v rámci každého bloku po sobě jdoucích módů:

>>> hopping.conserves_particle_number()
True
>>> move = BosonOperator.from_dict({(cre(0), ann(2)): 1.0})
>>> move.conserves_sector([3])
True
>>> move.conserves_sector([2, 1])
False

Nakonec relabel_modes() přejmenuje módy: mód i se stane permutation[i]. Seznam musí být dost dlouhý, aby indexoval každý mód operátoru, a nesmí opakovat položku:

>>> print(format(move.relabel_modes([5, 6, 7])))
1.000000e0 +0.000000e0j * (+5 -7)

Aritmetika​

Operátory podporují sčítání, odčítání, negaci a násobení a dělení číslem, v obvyklém tvaru operátoru nebo na místě:

>>> total = 2 * b_dag + b_dag - 3j * b
>>> len(total)
3
>>> len(total.simplify())
2
>>> total.simplify().equiv(3 * b_dag - 3j * b)
True

Tři členy total nebyly nikdy sloučeny, takže operátor držel b_dag dvakrát, dokud je simplify() nespojil. Metoda sčítá koeficienty shodných členů a odstraňuje ty, jejichž celková velikost je pod tolerancí. Je to strukturální operace: porovnává zapsané faktory a neaplikuje komutační relace. Související metoda ichop() odstraňuje malé koeficienty na místě bez předchozího slučování, takže před ichop() zavolej simplify(), když operátor může obsahovat duplicitní členy.

Součiny používají @ nebo &, jak je popsáno výše, a ** umocní operátor na celé číslo:

>>> x = b_dag + b
>>> print(format((x ** 2).simplify()))
1.000000e0 +0.000000e0j * (-0 -0)
1.000000e0 +0.000000e0j * (-0 +0)
1.000000e0 +0.000000e0j * (+0 -0)
1.000000e0 +0.000000e0j * (+0 +0)

Výsledek uvádí čtyři pořadí tak, jak jsou zapsána. K převedení na jediný tvar jsou potřeba komutační relace, které aplikuje další sekce.

Adjungovaný operátor obrací pořadí faktorů v každém členu, zaměňuje kreační a anihilační operátory a komplexně sdružuje koeficienty. Metoda is_hermitian() testuje, zda se operátor rovná svému adjungovanému:

>>> hop = BosonOperator.from_dict({(cre(0), ann(1)): 1j})
>>> print(format(hop.adjoint()))
0.000000e0 -1.000000e0j * (+1 -0)
>>> hop.is_hermitian()
False
>>> (hop + hop.adjoint()).is_hermitian()
True

Samotný hopping člen není hermitovský, což je důležité, když členy seskupuješ pro časovou evoluci: každá skupina musí být hermitovská sama o sobě (viz Seskupování členů operátoru: využití struktury operátoru).

Normální uspořádání a porovnávání operátorů​

Dva součiny žebříkových operátorů mohou být týmž operátorem a přesto vypadat odlišně, protože b b†b\,b^\dagger a b†b+1b^\dagger b + 1 jsou v algebře rovné. Operátory, které jsou v tomto smyslu rovné, nemusí být uloženy stejně, takže jejich porovnání vyžaduje komutační relace. Metoda normal_ordered() je aplikuje: přesune každý kreační operátor nalevo od každého anihilačního operátoru a přidá člen, kdykoli se zamění dvojice na stejném módu.

>>> product = BosonOperator.from_dict({(ann(0), cre(0), ann(0), cre(0)): 1.0})
>>> ordered = product.normal_ordered()
>>> len(ordered)
5
>>> print(format(ordered.simplify()))
1.000000e0 +0.000000e0j * ()
3.000000e0 +0.000000e0j * (+0 -0)
1.000000e0 +0.000000e0j * (+0 +0 -0 -0)

Uspořádaný operátor je vrácen jako neslučený součet s pěti uloženými členy, které se po sloučení pomocí simplify() zredukují na tři. Normální uspořádání nikdy nezpůsobí zánik členu: b†b†b^\dagger b^\dagger a každý další opakovaný faktor zůstávají.

Argument sandwich určuje, jak jsou módy uvnitř dvou skupin faktorů uspořádány. Ve výchozím nastavení jsou obě skupiny seřazeny sestupně. S sandwich=True jsou kreační operátory vzestupně a anihilační sestupně, což staví větší indexy směrem ke středu:

>>> pair = BosonOperator.from_dict({(cre(0), cre(1), ann(0), ann(1)): 1.0})
>>> list(pair.normal_ordered().iter_terms())
[([(True, 1), (True, 0), (False, 1), (False, 0)], (1+0j))]
>>> list(pair.normal_ordered(sandwich=True).iter_terms())
[([(True, 0), (True, 1), (False, 1), (False, 0)], (1+0j))]

Operátor == testuje přesnou rovnost uložených členů, bez tolerance a bez zjednodušení, takže může hlásit rozdíl mezi dvěma operátory, které jsou matematicky rovné. Metoda equiv() nejprve převede rozdíl obou operátorů do normálního uspořádání a pak testuje, zda je každý koeficient pod tolerancí. Je to porovnání, které téměř vždy chceš:

>>> reordered = BosonOperator.from_dict({(cre(0), ann(0)): 1.0, (): 1.0})
>>> contracted = BosonOperator.from_dict({(ann(0), cre(0)): 1.0})
>>> contracted == reordered
False
>>> contracted.simplify() == reordered.simplify()
False
>>> contracted.equiv(reordered)
True

Samotná metoda simplify obě formy od sebe nechává oddělené, protože slučuje jen shodné zapsané členy. Podobně is_hermitian() porovnává operátor s jeho adjungovaným v normálním uspořádání, takže rozpozná b b†+b†bb\,b^\dagger + b^\dagger b jako hermitovský bez další práce.

Poznámka

Normální uspořádání je přesná identita v neoříznuté algebře a equiv na tom stojí. Po oříznutí není identitou. Zmapovaný operátor závisí na formě, kterou mapuješ, takže zmapované obrazy contracted a reordered výše se liší, a to pouze na nejvyšší zachované úrovni. Velikost rozdílu a způsob volby formy najdeš v Oříznutí Fockova prostoru.

Řazení a filtrování členů​

Přeuspořádání členů operátoru, na rozdíl od faktorů uvnitř členu, operátor nemění, ale může změnit chování algoritmů, které členy čtou v pořadí, například randomizované produktové formule. Funkce z qiskit_bosons.operators.terms vracejí nový operátor a ponechávají případné indexy skupin připojené k jejich členům.

canonical_order() seřadí členy do pevného pořadí, které závisí jen na zapsaných faktorech, takže se operátor čte stejně bez ohledu na to, jak byl sestaven. order_terms() řadí podle klíče, který dodáš, a ten dostane každý člen jako n-tici, kterou vrací iter_terms():

>>> from qiskit_bosons.operators.terms import canonical_order, order_terms
>>> mixed = BosonOperator.from_dict({(cre(1), ann(0)): 1.0, (cre(0), ann(1)): 3.0, (): 2.0})
>>> list(canonical_order(mixed).iter_terms())
[([], (2+0j)), ([(True, 0), (False, 1)], (3+0j)), ([(True, 1), (False, 0)], (1+0j))]
>>> by_weight = order_terms(mixed, key=lambda term: abs(term[1]), reverse=True)
>>> [coeff for _, coeff in by_weight.iter_terms()]
[(3+0j), (2+0j), (1+0j)]

Pokud operátor nese indexy skupin (viz Seskupování členů operátoru: využití struktury operátoru), funkce group_order() učiní z každé skupiny jeden souvislý úsek členů:

>>> from qiskit_bosons.operators.terms import group_order
>>> grouped = BosonOperator.from_dict({(cre(1), ann(0)): 1.0, (cre(0), ann(1)): 3.0, (): 2.0})
>>> grouped.groups = [1, 0, 1]
>>> group_order(grouped).groups
[0, 1, 1]

Poslední pomocná funkce členy odstraňuje, místo aby je přeuspořádávala. Člen, který je součinem operátorů počtu, bi†bib_i^\dagger b_i, jen násobí každý Fockův stav fází, takže filter_diagonal_terms() může odstranit konstantu, operátory počtu a jejich součiny, když je tato fáze pro to, co počítáš, irelevantní. Mění operátor na místě a vyžaduje operátor v normálním uspořádání, což nekontroluje:

>>> from qiskit_bosons.operators.terms import filter_diagonal_terms
>>> model = BosonOperator.from_dict(
... {
... (cre(0), ann(1)): -1.0,
... (cre(1), ann(0)): -1.0,
... (cre(0), cre(0), ann(0), ann(0)): 0.5,
... (cre(0), ann(0)): 0.2,
... (): 0.1,
... }
... )
>>> filter_diagonal_terms(model)
>>> len(model)
2
>>> model.is_hermitian()
True

Zůstanou jen dva hopping členy. Interakce na místě a operátor počtu jsou v obsazovací bázi diagonální, takže se odstraní spolu s konstantou. Opakovaný mód v interakci je zpracován správně: b0†b0†b0b0b_0^\dagger b_0^\dagger b_0 b_0 je diagonální, zatímco b0†b0†b0b_0^\dagger b_0^\dagger b_0 by nebyl.

Další kroky​