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

Робота з ферміонними колами

Важливо

Концепції в цьому посібнику наразі доступні лише в Python API. Еквівалентна функціональність стане доступною в C API в одному з майбутніх релізів.

Цей посібник пояснює, як використовувати FermionicCircuit для реалізації квантових алгоритмів безпосередньо в ферміонному просторі, а не після попереднього відображення на кубіти.

Навіщо використовувати ферміонні кола​

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

  1. Визнач ферміонний гамільтоніан (наприклад, H=∑pqhpqcp†cqH = \sum_{pq} h_{pq} c^\dagger_p c_q).

  2. Відобрази його на кубітний оператор (наприклад, за допомогою відображення Йордана-Вігнера).

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

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

FermionicCircuit уможливлює кращий робочий процес:

  1. Визнач ферміонний гамільтоніан (наприклад, H=∑pqhpqcp†cqH = \sum_{pq} h_{pq} c^\dagger_p c_q).

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

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

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

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

І модуль operators, і FermionicCircuit використовують загальну індексацію на основі мод, яка не робить жодних припущень щодо природи мод. Мода — це просто абстрактний індекс, що позначає ферміонний ступінь вільності, без власної семантики. Цей вибір дизайну відображає модуль operators і забезпечує максимальну гнучкість. Наприклад, ти можеш реалізувати часову еволюцію для будь-якого оператора, що реалізує протокол OperatorTrait, незалежно від його математичного представлення.

Побудова ферміонного кола​

Наведений нижче приклад реалізує часову еволюцію, описану в попередньому розділі. Він конструює FermionicCircuit, задаючи кількість ферміонних мод, а потім додає ферміонні гейти з qiskit_fermions.circuit.library для реалізації часової еволюції.

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

>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Create a circuit with 4 fermionic modes
>>> circuit = FermionicCircuit(4)
>>>
>>> # Define a simple fermionic Hamiltonian
>>> hamiltonian = FermionOperator.from_terms([
... ([cre(0), ann(2)], 0.5),
... ([cre(2), ann(0)], 0.5),
... ([cre(1), ann(3)], 0.5),
... ([cre(3), ann(1)], 0.5),
... ])
>>> # Add some problem structure by grouping our Hamiltonian terms
>>> hamiltonian.groups = [0, 0, 1, 1]
>>>
>>> # Add an evolution gate to implement exp(-i * t * H)
>>> evolution = Evolution(4, hamiltonian, time=1.0)
>>> circuit.append(evolution, circuit.register)

A simple `FermionicCircuit` with a single time evolution gate.

A decomposed `FermionicCircuit` with several time evolution gates.

Зверни увагу, що групування доданків оператора зберігається навіть у простих операціях, як-от декомпозиція. Це демонструє, як структурна інформація проходить крізь стек кіл. Щоб зрозуміти повну транспіляцію на кубіти, див. посібник Транспіляція ферміонних кіл.

Розділення гейтів за допомогою бар'єра​

Проходи оптимізації об'єднують сусідні гейти, де це можливо. Наприклад, MergeOrbitalRotations об'єднує послідовність гейтів OrbitalRotation в один, а MergeSlaterDeterminantPreparation вбирає InitializeModes в обертання, що йде за ним.

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

>>> from qiskit_fermions.circuit.library import OrbitalRotation
>>> import numpy as np
>>>
>>> rotations = FermionicCircuit(4)
>>> rotations.append(OrbitalRotation(np.eye(4, dtype=complex)), rotations.register)
>>> rotations.barrier()
>>> rotations.append(OrbitalRotation(np.eye(4, dtype=complex)), rotations.register)

A `FermionicCircuit` with a barrier separating two orbital rotations.

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

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

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

Наведений нижче приклад будує крок Троттера другого порядку для моделі Фермі-Хаббарда. Крок симетричний: він розбиває доданок перескоку на два гейти OrbitalRotation половинної тривалості, що оточують локальну взаємодію.

>>> import scipy.linalg
>>> from qiskit_fermions.circuit.library import InitializeModes, OrbitalRotation
>>>
>>> norb, reps, time = 2, 3, 1.0
>>> num_modes = 2 * norb
>>> dt = time / reps
>>>
>>> hopping = np.zeros((norb, norb))
>>> hopping[0, 1] = hopping[1, 0] = -1.0
>>> interaction = FermionOperator.from_dict(
... {(cre(p), ann(p), cre(p + norb), ann(p + norb)): 2.0 for p in range(norb)}
... )
>>>
>>> def rotation(duration):
... propagator = scipy.linalg.expm(-1j * duration * hopping)
... # the same rotation acts on the alpha and beta halves of the register
... return scipy.linalg.block_diag(propagator, propagator)
>>>
>>> trotter_step = FermionicCircuit(num_modes)
>>> trotter_step.append(OrbitalRotation(rotation(dt / 2)), trotter_step.modes)
>>> trotter_step.append(
... Evolution(num_modes, interaction, dt, atomic=True), trotter_step.modes
... )
>>> trotter_step.append(OrbitalRotation(rotation(dt / 2)), trotter_step.modes)

Повторення кроку та його композиція з опорним станом Хартрі-Фока дає повну еволюцію:

>>> evolution = FermionicCircuit(num_modes)
>>> evolution.append(InitializeModes([True, False, True, False]), evolution.modes)
>>> evolution.compose(trotter_step.repeat(reps), inplace=True)
>>> evolution.count_ops()["OrbitalRotation"]
6

A `FermionicCircuit` with repeated steps.

Повторення розгортаються в повернуте коло, а не загортаються в один гейт, тому count_ops() рахує два обертання на крок. Саме таке розгортання робить надлишковість на межах кроків видимою для транспілятора: завершальне обертання половинної тривалості одного кроку та початкове наступного стоять поруч, тож MergeOrbitalRotations об'єднує їх в одне обертання.

>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.passes import MergeOrbitalRotations
>>>
>>> merged = FermionicPassManager([MergeOrbitalRotations()]).run(evolution)
>>> merged.count_ops()["OrbitalRotation"]
4

A `FermionicCircuit` with repeated steps and merged orbital rotations.

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

Коли потрібно залишити повторення розділеними, передай insert_barriers=True, щоб розмістити між ними barrier(). Проходи оптимізації не об'єднують гейти через бар'єр, тому кожне обертання зберігається:

>>> separated = trotter_step.repeat(reps, insert_barriers=True)
>>> FermionicPassManager([MergeOrbitalRotations()]).run(separated).count_ops()[
... "OrbitalRotation"
... ]
6

A `FermionicCircuit` with repeated steps separated by barriers.

Примітка

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

Вимірювання ферміонних мод​

measure_all() зчитує кожну моду в окремий класичний біт, зіставляючи моду i з бітом i. Використовуй measure() зі власним ClassicalRegister, щоб зчитати лише деякі з них.

>>> measured = trotter_step.measure_all(inplace=False)
>>> measured.count_ops()["measure"]
4

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

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

Примітка

На відміну від measure_all(), перед вимірюваннями не вставляється жодного barrier(). Спершу виклич barrier() самостійно, якщо хочеш мати його там.

Чи означає виміряний біт те, що ти очікуєш, залежить від ферміон-кубітного кодування, яке обирається під час транспіляції, а не тут. За кодування в базисі заповнення, як-от Йордана-Вігнера, кожна мода переноситься одним кубітом, і біт є заповненням цієї моди; див. FermionicMeasure.

Транспіляція ферміонних кіл​

Щоб реалізувати квантовий алгоритм, представлений твоїм FermionicCircuit, його потрібно транспілювати в QuantumCircuit. Як це зробити, можна дізнатися в посібнику Транспіляція ферміонних кіл.

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

  • Дізнайся про доступні ферміонні гейти в документації qiskit_fermions.circuit.library.

  • Ознайомся з поясненням щодо операторів, щоб зрозуміти, як будувати ферміонні гамільтоніани, які можна використовувати з ферміонними колами.

  • Перегляньте SQDRIFT — посібник для початку роботи з практичним наскрізним прикладом.

  • Прочитай посібник про зв'язок з ffsim, щоб зрозуміти, як FermionicCircuit можна симулювати безпосередньо і як ці загальні індекси мод набувають спінової семантики лише під час симуляції.