Перейти до основного вмісту
Цей проєкт у бета-версії. Знайшов(-ла) помилку? Відкрити issue на GitHub. Є запитання чи ідея? Запитати в Discussions — будемо раді твоєму відгуку!
Яка якість перекладу на цій сторінці?

Робота з бозонними колами

BosonicCircuit виражає обчислення безпосередньо на бозонних модах. Він не несе ні обрізання, ні кодування, ні кількості кубітів: вони з'являються пізніше, під час транспіляції, і саме тому одне коло можна знизити під кількома кодуваннями та обрізаннями без перебудови.

Він навмисно надає скорочений API порівняно з QuantumCircuit з Qiskit, щоб кубітні гейти не можна було застосувати до бозонної моди, адже це не було б коректно визначеною операцією. Цей посібник охоплює операції, які доступні для збирання більших кіл із менших.

Навіщо використовувати бозонні кола​

Розглянь традиційний процес перенесення часової еволюції бозонного гамільтоніана на кубітну архітектуру:

  1. Визнач бозонний гамільтоніан, наприклад H=−∑ijbi†bjH = -\sum_{ij} b^\dagger_i b_j.

  2. Обери обрізання та кодування і відобрази з ними гамільтоніан на кубітний оператор.

  3. Реалізуй часову еволюцію в кубітному просторі за допомогою тротеризації або іншого методу.

Другий крок іде рано, і він ухвалює два рішення, які дорого відкликати. Обрізання фіксує, скільки рівнів зберігає кожна мода, а кодування фіксує, у скільки кубітів це обійдеться, тож обидва зафіксовані в кубітному операторі ще до того, як із колом щось зроблено. Щоб розглянути ту саму фізику за іншого обрізання чи кодування, потрібно знову відобразити гамільтоніан і знову побудувати коло. Крім того, відображення відкидає бозонну структуру: які члени належать разом і що члени пари стрибка є спряженими один до одного.

BosonicCircuit переносить ці рішення в кінець:

  1. Визнач бозонний гамільтоніан.

  2. Вирази часову еволюцію безпосередньо на бозонних модах за допомогою бозонних гейтів.

  3. Обери обрізання та кодування як частину транспіляції.

Коло описує, яке обчислення виконати, а транспіляція вирішує, як розмістити його на кубітах. Те саме коло потім знижується за кожного обрізання та під кожним кодуванням, а групування оператора залишається для проходів, які можуть його використати. Наступні розділи показують, як збирається таке коло; Транспіляція бозонних кіл показує його зниження.

Загальне індексування мод​

Модуль operators і коло мають одну спільну конвенцію для мод. Мода — це лише індекс, що позначає бозонний ступінь свободи, і вона не несе власного змісту. Тож можна еволюціонувати будь-який оператор, що реалізує протокол OperatorTrait, що б він не представляв, і коло ніколи не мусить знати, що означає мода 2. Те саме стосується розміру моди: коло не має обрізання заповнення, тож тут ніщо не каже, скільки рівнів має мода.

Додавання гейтів​

Гейти додаються на моди. Приймаються лише екземпляри BosonicGate:

>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import Evolution, InitializeModes
>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> hopping = BosonOperator.from_dict({(cre(0), ann(1)): -1.0, (cre(1), ann(0)): -1.0})
>>> hopping.groups = [0] * len(hopping) # the pair is Hermitian, its terms alone are not
>>>
>>> prepare = BosonicCircuit(2)
>>> prepare.append(InitializeModes([2, 0]), prepare.modes)
>>> step = BosonicCircuit(2)
>>> step.append(Evolution(2, hopping, time=0.1), step.modes)

Усе інше відхиляється, і помилка називає тип-порушник:

>>> from qiskit.circuit.library import HGate
>>> try:
... prepare.append(HGate(), [0])
... except ValueError as error:
... print(error)
Unsupported instruction type: <class '...HGate'>

Композиція кіл​

compose() розміщує інструкції одного кола на іншому. Вони вбудовуються, а не загортаються в один гейт, тож результат — звичайне коло, яке можна й далі декомпонувати та транспілювати.

>>> program = prepare.compose(step)
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A composed BosonicCircuit.

За замовчуванням повертається нове коло, а обидва операнди залишаються недоторканими. Передай inplace=True, щоб змінити перше, і front=True, щоб розмістити операнд перед наявними інструкціями:

>>> step.compose(prepare, front=True)._inner.data == program._inner.data
True

Вужче коло можна розмістити на вибраній підмножині мод:

>>> single = BosonicCircuit(1)
>>> single.append(Evolution(1, BosonOperator.from_dict({(cre(0), ann(0)): 1.0}), 0.1), single.modes)
>>> wide = BosonicCircuit(3).compose(single, [2])
>>> wide.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A narrow-composed BosonicCircuit.

Як операнд приймається лише інше BosonicCircuit, бо власний compose Qiskit вбудував би кубітні гейти й обійшов перевірку, яку забезпечує append.

Повторення кола​

repeat() повторює коло від початку до кінця. Повторення розгортаються: результат містить інструкції reps разів поспіль, а не один гейт, що обгортає коло. Тому кількості операцій масштабуються з reps, а decompose діє на повторюване тіло, а не знімає одне повторення.

>>> len(step.repeat(0)._inner.data) # zero repetitions is the identity
0
>>> step.repeat(5).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A repeated BosonicCircuit.

Це спосіб тротеризації на рівні кола: еволюціонуй короткий час і повторюй. Про альтернативу вибору формули добутку на самому гейті та про те, чому це дві різні речі, що випадково поділяють слово «повторення», див. Синтез гейта Evolution у бозонному просторі.

Розділення кроків бар'єрами​

Бар'єр позначає точку синхронізації, крізь яку проходи оптимізації не зливають гейти. barrier() ставить його на всі моди або на названі тобою:

>>> program = BosonicCircuit(2)
>>> program.append(InitializeModes([2, 0]), program.modes)
>>> program.barrier()
>>> program.barrier(program.modes[0], label="zero")
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A BosonicCircuit with barriers.

repeat(..., insert_barriers=True) ставить один між послідовними повтореннями і жодного після останнього:

>>> step.repeat(3, insert_barriers=True).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A repeated BosonicCircuit with barriers.

Бар'єри та кодування​

Кодуванню потрібно кілька кубітів на моду, тож регістр мод і регістр кубітів різняться за розміром, а розкладка пов'язує цілі регістри. Тому мода не має власних кубітів, до яких можна було б звузити бар'єр. Під час синтезу «бозон у кубіт» бар'єр відображається на кожен кубіт кожного регістра, якого він торкався, що коректно за будь-якої розкладки:

>>> from qiskit_bosons.mappers import LevelSpec
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>> lowered = generate_preset_occupation_pass_manager(LevelSpec(2, "unary_projected")).run(program)
>>> lowered.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A QuantumCircuit showing the expansion of barrier to cover the entire qubit register.

Зчитування бозонного кола​

BosonicCircuit не має measure чи measure_all. Що означає виміряний біт, залежить від кодування. За унарного кодування заповнення моди розподілене між кількома кубітами і його треба зчитувати з візерунка бітів, а за компактного воно є двійковим числом, тож вимір, що називає моду, мав би знати кодування, чого бозонне коло свідомо не робить. Натомість вимірюй транспільоване QuantumCircuit і декодуй бітові рядки за допомогою LevelEncoding.level_to_bits() та зсувів LevelSpec, з яким ти знижував. Симуляція динаміки Бозе-Хаббарда з вибором кодування робить це для повної моделі.

>>> hasattr(BosonicCircuit, "measure_all")
False

Наступні кроки​