Як цей пакет пов'язаний з ffsim
Концепції в цьому посібнику доступні лише в Python API.
ffsim і цей пакет розподіляють роботу, а не перекриваються, за єдиною лінією:
| ffsim відповідає за | qiskit-fermions відповідає за |
|---|---|
Високорівневі оператори анзаців (UCJOpSpinBalanced, UCCSDOpRestrictedReal та їхні родичі), побудовані з амплітуд coupled-cluster або вектора параметрів | Ферміонне коло (FermionicCircuit), яким ці оператори стають, і його транспіляцію в кубітне коло за будь-якого ферміон-кубітного кодування |
| Швидка симуляція в компактному просторі з фіксованою кількістю частинок | Фреймворк мапперів і структури даних операторів, з яких побудовано коло |
Практичний наслідок діє в обидва боки. Математика анзаців живе в ffsim, тож UCJ і UCC приймають оператор ffsim безпосередньо як вхід. І навпаки, власні гейти Qiskit у ffsim специфічні для перетворення Йордана-Вігнера, тож переведення одного з її операторів анзаців через інше кодування — це те, що дає перенесення його сюди.
Симуляція — це місце, де вони зустрічаються, і цей посібник пояснює, як. ffsim є бекендом симуляції цього пакета, до якого звертаються через власні протоколи, а не через обгортку: посібник SKQD викликає ffsim.apply_unitary(), ffsim.linear_operator() і ffsim.sample_state_vector() безпосередньо на об'єктах FermionicCircuitта FermionOperatorбез кроку перетворення.
Підключення до інтерфейсу симуляції ffsim
ffsim — це високопродуктивний симулятор ферміонних квантових кіл, який використовує збереження кількості частинок і спіну Z, щоб представляти вектори стану компактніше, ніж загальний -вимірний кубітний вектор стану. Він визначає протоколи, які може реалізувати будь-який об'єкт, щоб брати участь у його механізмі симуляції (див. qiskit_fermions.protocols щодо власних протоколів цього пакета, які мають той самий дизайн):
-
ffsim.SupportsApplyUnitaryчерез метод_apply_unitary_(vec, norb, nelec, copy), що застосовує об'єкт як унітарний оператор до вектора стану з фіксованою кількістю частинок -
ffsim.SupportsLinearOperatorчерез метод_linear_operator_(norb, nelec), що повертає подання об'єкта у виглядіscipy.sparse.linalg.LinearOperatorу тому самому секторі -
ffsim.SupportsTraceчерез метод_trace_(norb, nelec), що повертає слід об'єкта в цьому секторі.
Цей пакет реалізує цей інтерфейс: кожен ферміонний гейт у qiskit_fermions.circuit.library надає _apply_unitary_, а FermionOperator надає _linear_operator_ і _trace_. Саме це робить оператори та кола цього пакета сумісними з ffsim, тож його інструменти працюють з ними нативно, без кроку перетворення. ffsim.apply_unitary() може симулювати FermionicCircuit від початку до кінця, ffsim.linear_operator() може діагоналізувати FermionOperator, а утиліти семплування, як-от ffsim.sample_state_vector() (використовується в посібнику SKQD, щоб перетворити симульований вектор стану на лічильники вимірювань), працюють без змін.
Те, що повертає _linear_operator_, — це звичайний LinearOperator зі SciPy, тож scipy.sparse.linalg.eigsh() та подібні функції працюють і з ним.
>>> import ffsim
>>> import numpy as np
>>>
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.operators import FermionOperator, ann, cre
>>>
>>> norb, nelec = 2, (1, 1)
>>> hamiltonian = FermionOperator.from_terms([
... ([cre(0), ann(1)], 0.5),
... ([cre(1), ann(0)], 0.5),
... ])
>>>
>>> circuit = FermionicCircuit(2 * norb)
>>> circuit.append(Evolution(2 * norb, hamiltonian, time=1.0), circuit.modes)
>>>
>>> reference = ffsim.hartree_fock_state(norb, nelec)
>>> state = ffsim.apply_unitary(reference, circuit, norb=norb, nelec=nelec) # native ffsim call
Зв'язок через протоколи ffsim дає ось що: його екосистема утиліт симуляції та семплування, що активно розвивається, застосовується до кіл та операторів цього пакета без змін, а користувачі, які вже працюють з ffsim, можуть додавати гейти й оператори цього пакета, не вивчаючи ще один API симуляції.
ffsim — необов'язкова залежність
ffsim оголошено як необов'язкове доповнення (pip install "qiskit-fermions[ffsim]", або транзитивно через [all]), що охороняється під час виконання через HAS_FFSIM (як показано вище). Його встановлення відкриває симуляцію: ffsim.SupportsLinearOperator і ffsim.SupportsTrace реалізовано через перетворення FermionOperator цього пакета на ffsim.FermionOperator, тож без ffsim вони викликають MissingOptionalLibraryError. Усе, що не симулює (побудова операторів, їх відображення та транспіляція отриманих кіл), не потребує нічого з цього.
Ферміонна симуляція відбувається у секторі з фіксованою кількістю частинок
І _apply_unitary_, і _linear_operator_ приймають аргумент nelec і представляють вектор стану в базисі детермінантів із фіксованою кількістю частинок для цього сектора (norb, nelec), віддзеркалюючи налаштування простору FCI в ffsim (і, транзитивно, PySCF), а не повний -вимірний простір, який використав би загальний кубітний симулятор. Це набагато менший простір (його розмір — добуток біноміальних коефіцієнтів, а не степінь двійки), але з жорстким обмеженням. У ньому можна представити лише оператори та гейти, що зберігають кількість частинок (а у спіновому випадку — окремо кількість частинок кожного сорту спіну). Оператору, дія якого перемістила б амплітуду до іншої кількості частинок, нікуди її подіти.
ffsim розв'язує це, прямо відхиляючи такий оператор: перетворення його на лінійний оператор викликає ValueError із зазначенням, який закон збереження порушено. Це правильна поведінка за замовчуванням, бо застосування для гамільтоніана із доданком, що не зберігає кількість частинок, інакше перетворило б імовірно унітарне відображення на неунітарне й фізично безглузде.
-
Evolutionпередає цю відмову від оператора, еволюцію якого він описує. -
OrbitalRotationперевіряє, що її (вбудована) матриця обертання є блочно-діагональною щодо розділення на alpha/beta у спіновому випадку, і викликаєValueError, якщо вона змішує спінові сектори.
>>> from qiskit_fermions.circuit.library import Evolution
>>>
>>> non_conserving = FermionOperator.from_terms([([cre(0), cre(1)], 1.0)]) # creates 2 particles
>>> gate = Evolution(2 * norb, non_conserving, time=1.0)
>>>
>>> try:
... gate._apply_unitary_(reference, norb, nelec, copy=True)
... except ValueError as exc:
... print("rejected:", exc)
rejected: The given FermionOperator could not be converted to a LinearOperator because it does not conserve particle number and the z component of spin. Conserves particle number: False Conserves spin z: False
Тому симуляція без збереження числа частинок недосяжна у ферміонному просторі за побудовою представлення з фіксованим сектором, і це ціна компактного FCI-простору, який узагалі робить ферміонну симуляцію здійсненною. Якщо твоєму алгоритму потрібні оператори, що порушують кількість частинок (наприклад, кубітний канал помилок або оператор, побудований для відображеного гамільтоніана, який не зберігає числа частинок доданок за доданком), спершу виконай транспіляцію до кубітів і натомість симулюй отриманий QuantumCircuit кубітним симулятором. Для цього підійде будь-який маршрут транспіляції; посібник зі ферміонних схем і посібник з транспіляції розглядають це детальніше, але generate_preset_jw_pass_manager() є розумним вибором за замовчуванням.
Конвенція блочного спіну для спінових систем
nelec має тип int | tuple[int, int], і саме його тип, а не окремий прапорець, обирає одну з підтримуваних розкладок мод:
-
int обирає безспінову інтерпретацію.
norbмод трактуються безпосередньо як безспінові орбіталі, аnelecє загальною кількістю частинок. -
Пара
(n_alpha, n_beta)обирає спінову інтерпретацію2 * norbмод за фіксованою конвенцією блочного спіну. Моди0 .. norbє alpha (спін угору) орбіталями, а модиnorb .. 2 * norbє beta (спін униз) орбіталями. Кожен спіновий сорт зберігається незалежно; доданок лише з alpha може переміщувати електрон між alpha-модами, але ніколи в beta-моду, і навпаки.
Цей розподіл відбувається в кожній точці входу протоколу ffsim у цьому пакеті (конструктори гейтів, реалізації _apply_unitary_placed_ та компіляція секторів нативного ядра на Rust), і це конвенція, що використовується в посібниках LUCJ та SKQD (наприклад, InitializeModes.from_hartree_fock() записує alpha-заповнення в моди 0 .. n_alpha, а beta-заповнення в моди norb .. norb + n_beta). Варто протиставити це модулю qiskit_fermions.operators та FermionicCircuit загалом, які використовують загальні індекси мод без власної спінової семантики (див. посібник зі ферміонних схем). Блочно-спіновий сенс накладається лише тоді, коли у виклик симуляції передається спіновий nelec, а не вбудований у саме представлення оператора чи схеми.
>>> from qiskit_fermions.circuit.library import InitializeModes
>>>
>>> norb, nelec = 3, (2, 1)
>>> init = InitializeModes.from_hartree_fock(norb, nelec)
>>> print([bool(occ) for occ in init.occupation]) # alpha modes 0,1; beta mode 3 (= norb + 0)
[True, True, False, True, False, False]
Наступні кроки
-
Пройди посібник SKQD, щоб побачити, як
ffsim.apply_unitary()таffsim.sample_state_vector()використовуються разом для вибірки зі схем, побудованих з гейтів цього пакета, і для обчислення очікуваного значення гамільтоніана черезffsim.linear_operator()(який сам спирається на той самий метод протоколу_linear_operator_, описаний тут). -
Пройди посібник LUCJ, щоб побачити другу половину історії: як оператор анзаца ffsim проходить через конвеєр транспіляції цього пакета і потрапляє на карту зв'язності пристрою.
-
Прочитай посібник зі ферміонних схем про загальне, незалежне від спіну індексування мод поза викликами симуляції.
-
Прочитай посібник з транспіляції, щоб дізнатися, як вийти з ферміонного простору
і симулювати на кубітах, що потрібно для операторів, які не зберігають число частинок.
-
Перегляньте
qiskit_fermions.protocolsдля повного каталогу протоколів, які визначає цей пакет, зокрема протоколи перетворення (SupportsFermionOperator,SupportsMajoranaOperator), які не пов'язані з ffsim. -
Прочитай власні посібники ffsim про робочі процеси, якими він володіє, зокрема побудову анзаца UCJ та його варіаційну оптимізацію. Вектори параметрів, якими керують ці посібники, — це те, що приймають
UCJіUCCчерез оператори, які вони будують.