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í,
Dva důsledky formují rozhraní. Index módu se může v jednom členu opakovat, protože 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, .
Uložení
Operátor drží své členy ve čtyřech plochých polích, podobně jako komprimovaný řádkový formát (CSR) řídké matice:
coeffs | Jeden komplexní koeficient na člen. |
actions | Jedna booleovská hodnota na faktor: True pro kreační a False pro anihilační operátor. |
modes | Jeden index módu na faktor. |
boundaries | Pozice 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 , a konstantu , 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.
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čí a -0 značí . 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,
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 . Operátor & je kompozice, která se řídí konvencí Qiskitu čtení zleva doprava: a & b nejprve aplikuje a a pak b, takže je to . Funkce komutátorů používají @, takže commutator(a, b) je . 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.
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 a 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: 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á jako hermitovský bez další práce.
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, , 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ě: je diagonální, zatímco by nebyl.
Další kroky
-
Seskupování členů operátoru: využití struktury operátoru ukazuje, jak členy označit indexy skupin.
-
Mapování reprezentací operátorů převádí operátor na qubitový operátor, včetně volby kódování a vlivu oříznutí.
-
Práce s bosonickými obvody umisťuje operátor do obvodu jako hradlo
Evolution. -
Simulace Bose-Hubbardovy dynamiky s volbou kódování sestaví hamiltonián Bose-Hubbardova řetězce a simuluje ho od začátku do konce.
-
Moduly
qiskit_bosons.operatorsaqiskit_bosons.operators.termsdokumentují kompletní rozhraní.