Seskupování členů operátoru: využij strukturu operátoru
BosonOperator může každý svůj člen označit indexem skupiny. Značky samy o sobě nenesou žádný význam: jen říkají, které členy patří k sobě. Co značky znamenají, určuje to, co je spotřebovává. V tomto balíčku jsou spotřebiteli hradlo časového vývoje, které se dělí na jeden faktor na skupinu, a náhodný průchod qDRIFT, který vzorkuje celé skupiny.
Tento návod ukazuje, jak přiřadit skupiny, jak zkontrolovat vlastnosti, na které se spotřebitel spoléhá, a jak operátor uspořádat, aby výběr několika skupin z velkého operátoru zůstal levný.
Tento balíček nedodává žádnou automatickou strategii seskupování. Indexy přiřazuješ sám podle toho, co o svém modelu víš.
Přiřazení skupin
Průběžným příkladem je třímístný Bose-Hubbardův řetězec. Každá vazba přispívá hopping párem a interakce na místě tvoří diagonální část. Nejjednodušší způsob, jak sestavit seskupený operátor, je from_terms_with_groups(), která bere každý člen spolu s jeho indexem skupiny jako trojici (actions, coefficient, group). Skupinu určíš ve chvíli, kdy člen zapisuješ, tedy když ještě víš, proč tam patří:
>>> 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)
Členy se ukládají v pořadí, v jakém je zadáš, takže indexy skupin nemohou skončit přiřazeny ke špatným členům.
Operátor, který již existuje, se musí seskupit dodatečně, přiřazením atributu groups. Tam je třeba opatrnost, protože from_dict() neurčuje pořadí, v jakém členy ukládá. Vypsání seznamu podle pozice pak může přiřadit index špatnému členu. Místo toho odvoď každý index z člena samotného:
>>> 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)
Počet indexů musí odpovídat počtu členů. Cokoli jiného je odmítnuto, protože příliš krátký seznam by tiše zahodil koncové členy všude, kde se členy procházejí společně se skupinami:
>>> try:
... hamiltonian.groups = [0, 1]
... except ValueError as error:
... print(error)
expected one group index per term, but got 2 for 7 terms
Kontrola toho, na co se spotřebitel spoléhá
Tři funkce v qiskit_bosons.operators.terms odpovídají každá na jednu úzkou otázku o existujícím seskupení. Žádná z nich neříká, že je seskupení „správné“, protože správnost je relativní vůči spotřebiteli.
Je každá skupina hermitovská? To je nejdůležitější vlastnost. Časový vývoj skupiny je unitární jen tehdy, je-li skupina hermitovská, a osamělý hopping člen hermitovský není. Hermitovský je jen pár spolu s adjungovaným:
>>> from qiskit_bosons.operators.terms import groups_are_hermitian
>>> groups_are_hermitian(hamiltonian)
[True, True, True]
Seskupení, které rozdělí hopping pár, je zachyceno okamžitě:
>>> 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]
Čtyři hoppingové členy jsou zachyceny. Tři položky True jsou členy na místě , které jsou hermitovské samy o sobě. Mód se v rámci členu může legitimně opakovat a nic se kvůli tomu neodstraňuje. Příznaky následují pořadí terms, a proto lze každý z nich číst proti členu, který popisuje.
Jsou koeficienty jednotné? Randomizovaný součinový vzorec váží skupinu podle průměrné velikosti jejích koeficientů, což je věrný popis skupiny jen tehdy, když se velikosti shodují:
>>> 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]
Žádná z kontrol neimplikuje tu druhou: jednotné koeficienty nedělají skupinu hermitovskou a hermitovská skupina může míchat různé velikosti. Ve výchozím nastavení se porovnávají velikosti, protože právě ty průměruje group_coeff_means(). Hermitovský pár s komplexními koeficienty má stejné velikosti, ale nestejné koeficienty, takže splňuje pouze výchozí podobu:
>>> 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() je postavena na is_hermitian(), která porovnává operátor s jeho adjungovaným operátorem po normálním uspořádání. To je identita neořezané algebry, takže skupina, která projde, je hermitovská jako bosonový operátor. Jak se součiny žebříkových operátorů řadí a porovnávají, najdeš v části Návrhové principy reprezentací operátorů.
Seřaď členy podle skupin
split_out_groups() vrací jeden operátor pro každou požadovanou skupinu. Když jsou členy skupiny rozptýlené po operátoru, musí projít každý člen, aby je našla. Když každá skupina tvoří jeden souvislý úsek, hranice skupin hledá binárním vyhledáváním, takže cena vyhledání odpovídá počtu požadovaných skupin, a ne počtu uložených členů.
group_order() toto uspořádání vytvoří. Je to stabilní řazení podle indexu skupiny: hodnota operátoru se nemění a členy uvnitř skupiny si zachovávají své relativní pořadí.
>>> 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
Obě uspořádání vracejí stejné operátory, liší se jen cena:
>>> 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
Další kroky
Seskupení se volí tady, na bosonové úrovni, dokud je struktura modelu ještě viditelná.
-
Syntéza gatu Evolution v bosonovém prostoru ukazuje, jak ho použít k Trotterizaci evoluce a proč tím snížíš chybu, kterou součinový vzorec na úrovni qubitů snížit nedokáže.
-
Návrhové principy reprezentací operátorů vysvětlují datovou strukturu operátoru, která nese indexy skupin, a jak se jeho členy ukládají a řadí.
-
2. Seskupení před mapováním aplikuje seskupení na úplný model.