Групування членів оператора: використання структури оператора
BosonOperator може позначати кожен свій член груповим індексом. Позначки не мають власного змісту: вони лише кажуть, які члени належать разом. Що позначки означають, вирішує те, що їх споживає. У цьому пакеті споживачі — це гейт часової еволюції, який розбивається на один множник на групу, і рандомізований прохід qDRIFT, що вибирає цілі групи.
Цей посібник показує, як призначати групи, як перевіряти властивості, на які покладається споживач, і як розташувати оператор так, щоб вибір кількох груп із великого лишався дешевим.
Цей пакет не постачається з автоматичною стратегією групування. Індекси ти призначаєш сам, виходячи з того, що знаєш про свою модель.
Призначення груп
Наскрізний приклад — триузловий ланцюжок Бозе-Хаббарда. Кожен зв'язок дає пару стрибка, а внутрішньовузлові взаємодії утворюють діагональну частину. Найпростіший спосіб побудувати згрупований оператор — from_terms_with_groups(), що приймає кожен член разом із його груповим індексом як трійку (actions, coefficient, group). Ти визначаєш групу в момент запису члена, коли ще знаєш, чому він там належить:
>>> 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)
Члени зберігаються в порядку, в якому ти їх задаєш, тож групові індекси не можуть опинитися прив'язаними до неправильних членів.
Оператор, що вже існує, доводиться групувати постфактум, призначаючи атрибут groups. Тут потрібна обережність, бо from_dict() не визначає порядок, у якому зберігає свої члени. Запис списку за позиціями тоді може прикріпити індекс до неправильного члена. Натомість виводь кожен індекс із самого члена:
>>> 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)
Кількість індексів має збігатися з кількістю членів. Усе інше відхиляється, бо надто короткий список мовчки відкинув би кінцеві члени всюди, де члени перебираються разом зі своїми групами:
>>> try:
... hamiltonian.groups = [0, 1]
... except ValueError as error:
... print(error)
expected one group index per term, but got 2 for 7 terms
Перевірка того, на що покладається споживач
Три функції в qiskit_bosons.operators.terms відповідають кожна на одне вузьке запитання про наявне групування. Жодна не каже, що групування «правильне», бо правильність залежить від споживача.
Чи кожна група ермітова? Це найважливіша властивість. Часова еволюція групи унітарна лише тоді, коли група ермітова, а окремий член стрибка не є таким. Ермітовою є лише пара з його спряженим:
>>> from qiskit_bosons.operators.terms import groups_are_hermitian
>>> groups_are_hermitian(hamiltonian)
[True, True, True]
Групування, що розбиває пару стрибка, виявляється одразу:
>>> 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]
Чотири доданки стрибків виявлено. Три записи True — це локальні доданки , які ермітові самі по собі. Мода може цілком законно повторюватися в доданку, і через це нічого не відкидається. Прапорці йдуть у порядку terms, тому кожен можна зіставити з доданком, який він описує.
Чи однорідні коефіцієнти? Рандомізована формула добутку оцінює групу за середньою величиною її коефіцієнтів, а це вірний опис групи лише тоді, коли величини збігаються:
>>> 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]
Жодна з перевірок не випливає з іншої: однорідні коефіцієнти не роблять групу ермітовою, а ермітова група може містити різні величини. За замовчуванням порівнюються величини, бо саме їх усереднює group_coeff_means(). Ермітова пара з комплексними коефіцієнтами має рівні величини, але нерівні коефіцієнти, тому задовольняє лише типову форму:
>>> 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() побудовано на основі is_hermitian(), який порівнює оператор з його спряженим після нормального впорядкування. Це тотожність необрізаної алгебри, тому група, що проходить перевірку, є ермітовою як бозонний оператор. Про те, як упорядковуються й порівнюються добутки драбинних операторів, див. Принципи проєктування представлень операторів.
Упорядкуй доданки за групами
split_out_groups() повертає по одному оператору для кожної запитаної групи. Якщо доданки групи розкидані по оператору, доводиться переглядати кожен доданок, щоб їх знайти. Якщо кожна група утворює один неперервний фрагмент, межі груп шукаються бінарним пошуком, тож вартість пошуку залежить від кількості запитаних груп, а не від кількості збережених доданків.
group_order() створює таке розташування. Це стабільне сортування за індексом групи: значення оператора не змінюється, а доданки в межах групи зберігають свій взаємний порядок.
>>> 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
Обидва розташування повертають однакові оператори; відрізняється лише вартість:
>>> 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
Наступні кроки
Групування обирається тут, на бозонному рівні, поки структура моделі ще видима.
-
Синтез вентиля еволюції в бозонному просторі показує, як використати його для тротеризації еволюції і чому це зменшує похибку, яку не може зменшити формула добутку на рівні кубітів.
-
Принципи проєктування представлень операторів пояснюють структуру даних оператора, що містить індекси груп, і те, як зберігаються та впорядковуються його доданки.
-
2. Групування перед відображенням застосовує групування до повної моделі.