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

Принципи дизайну подань операторів

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

Огляд​

Подання операторів, які надає цей модуль, мають кілька спільних фундаментальних принципів дизайну:

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

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

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

  • Групування доданків та співвідношення комутації: Оператори нативно підтримують інформацію про групування, яка пов'язує доданки з індексами груп. Це дає змогу оптимізувати та зберігати фізичну структуру без окремих структур даних. Практичне використання дивись у посібнику з групування.

  • Арифметичні та математичні операції: Усі оператори реалізують узгоджений набір арифметичних операцій (таких як додавання, множення та композиція) і математичних функцій за допомогою протоколу OperatorTrait, що забезпечує однорідний код для різних типів операторів.

  • Упорядкування доданків оператора та нормальні форми: Математично еквівалентні оператори можуть мати дуже різні подання та поведінку в квантових алгоритмах. Усі подання операторів підтримують різні нормальні форми (на основі специфічних для алгебри співвідношень комутації) для досягнення канонічних, передбачуваних та оптимізовних подань операторів.

Розріджена структура даних​

Усі оператори використовують розріджене подання, у якому відстежуються лише нетотожні операції. Кожна нетотожна операція складається з коефіцієнта (комплексне число) і послідовності дій на конкретних модах.

Такий підхід різко зменшує використання пам'яті та час обчислень, особливо для систем із багатьма модами, але порівняно небагатьма значущими внесками. Кодуючи лише нетотожні операції, обчислення зосереджуються лише на суттєвому, що дає змогу працювати з великими системами, нездійсненними при щільних поданнях. Крім того, оператори природно масштабуються на будь-яку кількість мод; оператор, що діє на моди {0, 1}, працює без змін у системах із набагато більшою кількістю мод, оскільки незадіяні моди неявно тотожні.

Однакові доданки зберігаються окремо під час арифметичних операцій і, за потреби, мають бути об'єднані явно.

Внутрішній формат зберігання​

Внутрішньо оператори зберігаються в масивах, натхненних форматами розріджених матриць:

  • Масив коефіцієнтів: Комплексний коефіцієнт кожного доданка

  • Масив індексів мод: Ферміонні моди, на які діє кожна дія

  • Масив меж: Індекси, що позначають, де починаються й закінчуються моди кожного доданка в масиві мод

Важливо

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

Нижче показано, як організовані ці масиви. Спочатку пряма побудова за допомогою розріджених масивів:

>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> # Construct operators directly using sparse arrays
>>> # First operator: 1.0 * +0 -1
>>> op1 = FermionOperator(
... coeffs=[1.0],
... actions=[True, False],
... modes=[0, 1],
... boundaries=[0, 2],
... )
>>>
>>> # Second operator: 1.0 * +2 -3
>>> op2 = FermionOperator(
... coeffs=[1.0],
... actions=[True, False],
... modes=[2, 3],
... boundaries=[0, 2],
... )
>>>
>>> # Combine the sparse operators
>>> op1 += op2
>>> print(format(op1))
1.000000e0 +0.000000e0j * (+0 -1)
1.000000e0 +0.000000e0j * (+2 -3)

Зручні методи побудови​

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

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct operators using operator algebra notation
>>> op1 = FermionOperator.from_dict({(cre(0), ann(1)): 1.0})
>>> op2 = FermionOperator.from_dict({(cre(2), ann(3)): 1.0})
>>>
>>> # The result is sparse even when combining them
>>> op1 += op2
>>> print(format(op1))
1.000000e0 +0.000000e0j * (+0 -1)
1.000000e0 +0.000000e0j * (+2 -3)
Підказка

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

Ітерація за термами та відновлення​

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

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct an operator with terms of different orders
>>> op = FermionOperator.from_dict({
... (): 0.5, # constant term (order 0)
... (cre(0), ann(1)): 1.0, # two-body term (order 2)
... (cre(0), cre(1), ann(1), ann(0)): 0.25 # four-body term (order 4)
... })
>>>
>>> # Filter to keep only terms of order 2
>>> order_two_terms = [
... (term, coeff) for term, coeff in op.iter_terms()
... if len(term) == 2
... ]
>>>
>>> # Reconstruct operator from filtered terms
>>> filtered_op = FermionOperator.from_terms(order_two_terms)
>>> print(f"Original operator has {len(op)} terms")
Original operator has 3 terms
>>> print(f"Filtered operator has {len(filtered_op)} term")
Filtered operator has 1 term

Індексація за модами​

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

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

Важливо

Поточні реалізації використовують безспінові моди: усі подання операторів, які наразі надає цей модуль, розглядають моди як безспінові ферміонні ступені свободи. Це означає, що якщо у твоїй системі є електрони чи ферміони і зі спіном угору, і зі спіном униз, їх потрібно явно відобразити на різні моди (наприклад, моди 0-3 для спіну вгору чотирьох просторових орбіталей, моди 4-7 для спіну вниз або будь-яка інша обрана тобою конвенція).

Такий дизайн зберігає базові подання простими й загальними та водночас не нав'язує конкретної конвенції впорядкування спінів. Допоміжні модулі, як-от qiskit_fermions.operators.library, надають зручні функції (наприклад, FCIDump.from_file()), які виконують таке відображення за тебе під час завантаження даних електронної структури.

Підказка

З розвитком пакета можуть з'явитися подання операторів зі спіном, які нативно підтримують спінові ступені свободи у своїй моделі даних. Їх буде чітко відокремлено від поточних безспінових реалізацій, і вони співіснуватимуть з ними в модулі.

Групування термів і комутаційні співвідношення​

Як і коефіцієнти та індекси мод, оператори можуть за потреби зберігати масив груп, який зіставляє кожному термові індекс групи. Оскільки групування інтегровано безпосередньо в подання оператора як частину розрідженої структури даних, інформація про групування природно супроводжує оператор під час перетворень. Це дає змогу систематично використовувати структуру, чи то з фізичних властивостей, алгебраїчних співвідношень або симетрій конкретної задачі. Таку структурну інформацію можна потім використати в подальших операціях, як-от синтез і декомпозиція схем, за допомогою методів на кшталт OperatorTrait.split_out_groups().

Докладніші вказівки щодо групування термів оператора у твоїх робочих процесах дивися в посібнику групування.

>>> from qiskit_fermions.operators import MajoranaOperator, gamma
>>> op = MajoranaOperator.from_dict({
... (gamma(0, False),): 1.0,
... (gamma(1, False),): 1.0,
... (gamma(2, False), gamma(3, False)): 1.0
... })
>>> # Assign group indices to terms
>>> op.groups = [0, 0, 1]
>>> # Partition operator by groups
>>> grouped_ops = op.split_out_groups()

Арифметичні та математичні операції​

Усі оператори реалізують протокол OperatorTrait, що надає єдиний набір операцій для різних типів операторів. Це гарантує, що код, написаний для одного подання оператора, однаково працює з іншими. Про те, як цей протокол співвідноситься з іншими протоколами, визначеними в цьому пакеті, дивися qiskit_fermions.protocols.

Протокол містить арифметичні операції (такі як додавання, множення та композиція), структурні операції (ітерація за термами, аналіз підтримуваних мод, перейменування), математичні функції (нормальне впорядкування, спрощення, перевірка еквівалентності) тощо. Повний перелік усіх доступних операцій дивися в документації OperatorTrait.

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct a Hermitian operator: H = +0 -1 + +1 -0
>>> op = FermionOperator.from_dict({
... (cre(0), ann(1)): 1.0,
... (cre(1), ann(0)): 1.0
... })
>>>
>>> # Check if the operator is Hermitian
>>> print(f"Operator is Hermitian: {op.is_hermitian(atol=1e-10)}")
Operator is Hermitian: True
Примітка

is_hermitian() перевіряє, що H−H†=0H - H^\dagger = 0, зводячи цю різницю до нормальної форми та порівнюючи її з zero(). Якщо розписати через інші операції протоколу, вона виконує таке:

>>> difference = (op - op.adjoint()).normal_ordered().simplify(atol=1e-10)
>>> difference.equiv(FermionOperator.zero(), atol=1e-10)
True

Надійність цієї перевірки залежить від типу оператора, адже вона залежить від того, наскільки повно нормальна форма цього типу скорочує сусідні генератори. Результат True завжди надійний, але для деяких типів результат False є консервативним: докладніше дивися в is_hermitian().

Важливо

У прикладі використано atol=1e-10. Параметр atol (абсолютна похибка) задає поріг. Коефіцієнти з модулем, меншим за atol, вважаються нульовими й відкидаються. Це суттєво для числової стабільності під час порівняння операторів, оскільки арифметика з плаваючою комою може вносити невеликі похибки округлення, які інакше завадили б розпізнати еквівалентні оператори як такі.

Підказка

Хоча протокол OperatorTrait надає спільний інтерфейс, окремі реалізації операторів можуть пропонувати додаткові зручні методи, що не входять до протоколу. Наприклад, FermionOperator і MajoranaOperator надають метод max_rank(), який повідомляє довжину їхнього найдовшого терма. Завжди звертайся до документації API свого типу оператора, щоб дізнатися про всі доступні можливості.

Впорядкування термів оператора та нормальні форми​

Фундаментальна складність алгебри квантових операторів полягає в тому, що математично еквівалентні оператори можна подати багатьма різними способами, кожен з яких по-різному впливає на квантові алгоритми. Той самий оператор можна записати в алгебраїчно еквівалентних формах (наприклад, a†ba^\dagger b можна виразити як ba†+[a†,b]ba^\dagger + [a^\dagger,b]), але ці подання призводять до різної поведінки під час синтезу схем, спрощення та в числових алгоритмах.

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

Важливо

Різні подання операторів можуть реалізовувати нормальне впорядкування на основі різних комутаційних співвідношень, що відповідають їхній алгебрі. Наприклад, ферміонне нормальне впорядкування використовує антикомутаційні співвідношення ({ci,cj†}=δij\{c_i, c_j^\dagger\} = \delta_{ij}), тоді як нормальне впорядкування Майорани використовує інші алгебраїчні конвенції ({γi,γj}=2δij\{\gamma_i, \gamma_j\} = 2\delta_{ij}). Завжди звертайся до документації свого типу оператора, щоб зрозуміти, як реалізовано нормальне впорядкування.

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Two different representations of the same operator
>>> op1 = FermionOperator.from_dict({(ann(0), cre(0)): 1.0})
>>> op2 = FermionOperator.from_dict({(): 1.0, (cre(0), ann(0)): -1.0})
>>>
>>> # Direct comparison fails due to different forms
>>> op1.equiv(op2, atol=1e-10)
False
>>>
>>> # Normal-order both and compare again
>>> op1_normal = op1.normal_ordered()
>>> op2_normal = op2.normal_ordered()
>>> op1_normal.equiv(op2_normal, atol=1e-10)
True
Підказка

Метод протоколу OperatorTrait.normal_ordered() навмисно не специфікує свої позиційні та іменовані аргументи, що дає змогу конкретним реалізаціям операторів визначати налаштовувані параметри, які керують точною канонічною формою результату. Це уможливлює оптимізації та варіанти нормальної форми, специфічні для оператора, відповідно до його алгебри чи випадку використання. Перегляньте документацію API для свого типу оператора, щоб побачити, які параметри доступні.