Робота з бозонними колами
BosonicCircuit виражає обчислення безпосередньо на бозонних модах. Він не несе ні обрізання, ні кодування, ні кількості кубітів: вони з'являються пізніше, під час транспіляції, і саме тому одне коло можна знизити під кількома кодуваннями та обрізаннями без перебудови.
Він навмисно надає скорочений API порівняно з QuantumCircuit з Qiskit, щоб кубітні гейти не можна було застосувати до бозонної моди, адже це не було б коректно визначеною операцією. Цей посібник охоплює операції, які доступні для збирання більших кіл із менших.
Навіщо використовувати бозонні кола
Розглянь традиційний процес перенесення часової еволюції бозонного гамільтоніана на кубітну архітектуру:
-
Визнач бозонний гамільтоніан, наприклад .
-
Обери обрізання та кодування і відобрази з ними гамільтоніан на кубітний оператор.
-
Реалізуй часову еволюцію в кубітному просторі за допомогою тротеризації або іншого методу.
Другий крок іде рано, і він ухвалює два рішення, які дорого відкликати. Обрізання фіксує, скільки рівнів зберігає кожна мода, а кодування фіксує, у скільки кубітів це обійдеться, тож обидва зафіксовані в кубітному операторі ще до того, як із колом щось зроблено. Щоб розглянути ту саму фізику за іншого обрізання чи кодування, потрібно знову відобразити гамільтоніан і знову побудувати коло. Крім того, відображення відкидає бозонну структуру: які члени належать разом і що члени пари стрибка є спряженими один до одного.
BosonicCircuit переносить ці рішення в кінець:
-
Визнач бозонний гамільтоніан.
-
Вирази часову еволюцію безпосередньо на бозонних модах за допомогою бозонних гейтів.
-
Обери обрізання та кодування як частину транспіляції.
Коло описує, яке обчислення виконати, а транспіляція вирішує, як розмістити його на кубітах. Те саме коло потім знижується за кожного обрізання та під кожним кодуванням, а групування оператора залишається для проходів, які можуть його використати. Наступні розділи показують, як збирається таке коло; Транспіляція бозонних кіл показує його зниження.
Загальне індексування мод
Модуль 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>
За замовчуванням повертається нове коло, а обидва операнди залишаються недоторканими. Передай 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>
Як операнд приймається лише інше 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>
Це спосіб тротеризації на рівні кола: еволюціонуй короткий час і повторюй. Про альтернативу вибору формули добутку на самому гейті та про те, чому це дві різні речі, що випадково поділяють слово «повторення», див. Синтез гейта 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>
repeat(..., insert_barriers=True) ставить один між послідовними повтореннями і жодного після останнього:
>>> step.repeat(3, insert_barriers=True).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Бар'єри та кодування
Кодуванню потрібно кілька кубітів на моду, тож регістр мод і регістр кубітів різняться за розміром, а розкладка пов'язує цілі регістри. Тому мода не має власних кубітів, до яких можна було б звузити бар'єр. Під час синтезу «бозон у кубіт» бар'єр відображається на кожен кубіт кожного регістра, якого він торкався, що коректно за будь-якої розкладки:
>>> 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>
Зчитування бозонного кола
BosonicCircuit не має measure чи measure_all. Що означає виміряний біт, залежить від кодування. За унарного кодування заповнення моди розподілене між кількома кубітами і його треба зчитувати з візерунка бітів, а за компактного воно є двійковим числом, тож вимір, що називає моду, мав би знати кодування, чого бозонне коло свідомо не робить. Натомість вимірюй транспільоване QuantumCircuit і декодуй бітові рядки за допомогою LevelEncoding.level_to_bits() та зсувів LevelSpec, з яким ти знижував. Симуляція динаміки Бозе-Хаббарда з вибором кодування робить це для повної моделі.
>>> hasattr(BosonicCircuit, "measure_all")
False
Наступні кроки
-
Транспіляція бозонних кіл пояснює, як бозонне коло стає кубітним колом, етап за етапом.
-
Обертання бозонних мод розглядає гейт, що змішує моди та зберігає число бозонів.
-
Синтез гейта Evolution у бозонному просторі показує, як тротеризувати еволюцію в бозонному просторі.
-
Принципи проєктування представлень операторів пояснює оператори, які несуть гейти.