Przejdź do głównej treści
Ten projekt jest w wersji beta. Znalazłeś błąd? Otwórz zgłoszenie na GitHub. Masz pytanie lub pomysł? Zapytaj w Discussions — chętnie poznamy Twoją opinię!
Jak oceniasz jakość tłumaczenia tej strony?

Grupowanie członów operatora: wykorzystanie struktury operatora

BosonOperator może oznaczyć każdy ze swoich członów indeksem grupy. Same znaczniki nie niosą własnego znaczenia: mówią tylko, które człony należą do siebie. To, co znaczniki oznaczają, rozstrzyga to, co z nich korzysta. W tym pakiecie konsumentami są bramka ewolucji czasowej, dzielona na jeden czynnik na grupę, oraz losowy przebieg qDRIFT, który próbkuje całe grupy.

Ten przewodnik pokazuje, jak przypisywać grupy, jak sprawdzać właściwości, na których polega konsument, oraz jak ułożyć operator, aby wybieranie kilku grup z dużego operatora pozostawało tanie.

Uwaga

Ten pakiet nie zawiera automatycznej strategii grupowania. Indeksy przypisujesz samodzielnie, na podstawie wiedzy o swoim modelu.

Przypisywanie grup​

Przykładem przewodnim jest trójwęzłowy łańcuch Bosego-Hubbarda. Każde wiązanie wnosi parę przeskoku, a oddziaływania na węzłach tworzą część diagonalną. Najprostszym sposobem zbudowania pogrupowanego operatora jest from_terms_with_groups(), która przyjmuje każdy człon wraz z jego indeksem grupy jako trójkę (actions, coefficient, group). Grupę wybierasz w chwili zapisywania członu, gdy jeszcze wiesz, dlaczego tam należy:

>>> 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)

Człony są przechowywane w podanej kolejności, więc indeksy grup nie mogą zostać przypisane do niewłaściwych członów.

Operator, który już istnieje, trzeba pogrupować post factum, przypisując atrybut groups. Tu potrzebna jest ostrożność, ponieważ from_dict() nie określa kolejności, w jakiej przechowuje człony. Wypisanie listy według pozycji może wtedy przypisać indeks do niewłaściwego członu. Zamiast tego wyprowadź każdy indeks z samego członu:

>>> 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)

Liczba indeksów musi odpowiadać liczbie członów. Wszystko inne jest odrzucane, ponieważ zbyt krótka lista po cichu pomijałaby końcowe człony wszędzie tam, gdzie człony są iterowane razem z ich grupami:

>>> try:
... hamiltonian.groups = [0, 1]
... except ValueError as error:
... print(error)
expected one group index per term, but got 2 for 7 terms

Sprawdzanie tego, na czym polega konsument​

Trzy funkcje w qiskit_bosons.operators.terms odpowiadają każda na jedno wąskie pytanie o istniejące grupowanie. Żadna z nich nie mówi, że grupowanie jest „poprawne”, ponieważ poprawność jest względna wobec konsumenta.

Czy każda grupa jest hermitowska? To właściwość, która ma największe znaczenie. Ewolucja czasowa grupy jest unitarna tylko wtedy, gdy grupa jest hermitowska, a pojedynczy człon przeskoku bi†bjb^\dagger_i b_j nie jest. Tylko para z jego sprzężeniem jest hermitowska:

>>> from qiskit_bosons.operators.terms import groups_are_hermitian
>>> groups_are_hermitian(hamiltonian)
[True, True, True]

Grupowanie, które rozdziela parę przeskoku, jest natychmiast wychwytywane:

>>> 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]

Cztery człony przeskoku zostają wychwycone. Trzy wpisy True to człony lokalne bi†bi†bibib^\dagger_i b^\dagger_i b_i b_i, które same z siebie są hermitowskie. Mod może zasadnie powtarzać się w obrębie członu i nic nie jest przez to odrzucane. Flagi następują w kolejności terms, dlatego każdą z nich można odczytać w odniesieniu do członu, który opisuje.

Czy współczynniki są jednorodne? Losowa formuła produktowa waży grupę średnią wielkością jej współczynników, co jest wiernym opisem grupy tylko wtedy, gdy te wielkości są zgodne:

>>> 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]

Żadne z tych sprawdzeń nie wynika z drugiego: jednorodne współczynniki nie czynią grupy hermitowską, a grupa hermitowska może mieszać różne wielkości. Domyślnie porównywane są wielkości, ponieważ to właśnie je uśrednia group_coeff_means(). Hermitowska para z zespolonymi współczynnikami ma równe wielkości, ale nierówne współczynniki, więc spełnia tylko formę domyślną:

>>> 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]
Uwaga

groups_are_hermitian() jest zbudowana na is_hermitian(), która porównuje operator z jego sprzężeniem po uporządkowaniu normalnym. To tożsamość nieobciętej algebry, więc grupa, która przechodzi test, jest hermitowska jako operator bozonowy. Zobacz Zasady projektowe reprezentacji operatorów, aby dowiedzieć się, jak iloczyny operatorów drabinkowych są porządkowane i porównywane.

Uporządkuj człony według grup​

split_out_groups() zwraca jeden operator na każdą żądaną grupę. Gdy człony grupy są rozrzucone po całym operatorze, funkcja musi przejrzeć każdy człon, aby je znaleźć. Gdy każda grupa tworzy jeden ciągły fragment, zamiast tego wyszukuje binarnie granice grup, więc koszt wyszukania zależy od liczby grup żądanych, a nie od liczby członów przechowywanych.

group_order() tworzy taki układ. Jest to stabilne sortowanie według indeksu grupy: wartość operatora się nie zmienia, a człony w obrębie grupy zachowują swoją względną kolejność.

>>> 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

Oba układy zwracają te same operatory; różni się tylko koszt:

>>> 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

Następne kroki​

Grupowanie wybiera się tutaj, na poziomie bozonowym, gdy struktura modelu jest jeszcze widoczna.