Przejdź do głównej treści
Ten projekt jest w wersji beta. Znalazłeś błąd? Otwórz zgłoszenie na GitHub. Masz pytanie lub pomysł? Zapytaj w Discussions — chętnie poznamy Twoją opinię!
Jak oceniasz jakość tłumaczenia tej strony?

Relacja tego pakietu do ffsim

Ważne

Koncepcje opisane w tym przewodniku są dostępne tylko w API Pythona.

ffsim i ten pakiet dzielą się pracą, zamiast się na siebie nakładać, wzdłuż jednej linii:

ffsim odpowiada zaqiskit-fermions odpowiada za
Wysokopoziomowe operatory ansatzów (UCJOpSpinBalanced, UCCSDOpRestrictedReal i ich pokrewne), budowane z amplitud sprzężonych klastrów lub wektora parametrówObwód fermionowy (FermionicCircuit), którym stają się te operatory, oraz jego transpilację do obwodu kubitowego w dowolnym kodowaniu fermion–kubit
Szybka symulacja w zwartej przestrzeni stałej liczby cząstekFramework mapperów i struktury danych operatorów, z których zbudowany jest obwód

Praktyczna konsekwencja działa w obie strony. Matematyka ansatzów mieszka w ffsim, więc UCJ i UCC przyjmują operator ffsim bezpośrednio jako dane wejściowe. Natomiast własne bramki Qiskit z ffsim są specyficzne dla transformacji Jordana-Wignera, więc sprowadzenie jednego z jego operatorów ansatzów przez inne kodowanie to właśnie to, co zyskujesz, przenosząc go tutaj.

Symulacja to miejsce, w którym obie strony się spotykają, i ten przewodnik wyjaśnia, jak. ffsim jest backendem symulacyjnym tego pakietu, osiąganym przez jego własne protokoły, a nie przez opakowanie: przewodnik SKQD wywołuje ffsim.apply_unitary(), ffsim.linear_operator() i ffsim.sample_state_vector() bezpośrednio na obiektach FermionicCircuiti obiektach FermionOperatorbez kroku konwersji.

Podłączenie do interfejsu symulacji ffsim​

ffsim to wydajny symulator obwodów kwantowych fermionów, który wykorzystuje zachowanie liczby cząstek i spinu Z, aby reprezentować wektory stanu bardziej zwięźle niż ogólny 2n2^n-wymiarowy wektor stanu kubitów. Definiuje protokoły, które może zaimplementować dowolny obiekt, aby uczestniczyć w jego mechanizmie symulacji (zobacz qiskit_fermions.protocols dla własnych protokołów tego pakietu, które mają ten sam projekt):

Ten pakiet implementuje ten interfejs: każda bramka fermionowa w qiskit_fermions.circuit.library udostępnia _apply_unitary_, a FermionOperator udostępnia _linear_operator_ i _trace_. Dzięki temu operatory i obwody tego pakietu są zgodne z ffsim, więc jego narzędzia działają na nich natywnie, bez kroku konwersji. ffsim.apply_unitary() potrafi zasymulować FermionicCircuit od początku do końca, ffsim.linear_operator() potrafi zdiagonalizować FermionOperator, a narzędzia do próbkowania, takie jak ffsim.sample_state_vector() (użyte w przewodniku SKQD do zamiany symulowanego wektora stanu na zliczenia pomiarów), działają bez modyfikacji.

To, co zwraca _linear_operator_, to zwykły LinearOperator z SciPy, więc scipy.sparse.linalg.eigsh() i podobne funkcje działają na nim również.

>>> 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

Sprzężenie przez protokoły ffsim daje to, że jego aktywnie rozwijany ekosystem narzędzi do symulacji i próbkowania stosuje się do obwodów i operatorów tego pakietu bez zmian, a użytkownicy, którzy już pracują z ffsim, mogą dołączać bramki i operatory tego pakietu bez uczenia się kolejnego API symulacji.

ffsim jest opcjonalną zależnością​

ffsim jest zadeklarowany jako opcjonalny dodatek (pip install "qiskit-fermions[ffsim]" lub pośrednio przez [all]), zabezpieczony w czasie wykonania przez HAS_FFSIM (jak pokazano powyżej). Jego instalacja odblokowuje symulację: ffsim.SupportsLinearOperator i ffsim.SupportsTrace są zaimplementowane przez konwersję FermionOperator tego pakietu na ffsim.FermionOperator, więc bez ffsim zgłaszają MissingOptionalLibraryError. Wszystko, co nie symuluje (budowanie operatorów, ich mapowanie i transpilacja powstałych obwodów), nie potrzebuje niczego z tego.

Symulacja fermionowa odbywa się w sektorze stałej liczby cząstek​

Zarówno _apply_unitary_, jak i _linear_operator_ przyjmują argument nelec i reprezentują wektor stanu w bazie wyznaczników o stałej liczbie cząstek dla danego sektora (norb, nelec), odzwierciedlając ustawienie przestrzeni FCI w ffsim (a pośrednio w PySCF), zamiast pełnej przestrzeni o wymiarze 2num_modes2^{\text{num\_modes}}, której użyłby ogólny symulator kubitów. Jest to znacznie mniejsza przestrzeń (jej rozmiar jest iloczynem współczynników dwumianowych, a nie potęgą dwójki), ale wiąże się z twardym ograniczeniem. Można w niej reprezentować tylko operatory i bramki zachowujące liczbę cząstek (a w przypadku ze spinem także liczbę cząstek każdego gatunku spinowego osobno). Operator, którego działanie przeniosłoby amplitudę do innej liczby cząstek, nie ma gdzie jej umieścić.

ffsim rozwiązuje to, odrzucając taki operator wprost: konwersja takiego operatora na operator liniowy zgłasza ValueError wskazujący, które prawo zachowania zawodzi. To właściwe ustawienie domyślne, ponieważ zastosowanie exp⁡(−itH)\exp(-i t H) dla hamiltonianu HH z wyrazem niezachowującym liczby cząstek zamieniłoby w przeciwnym razie mającą być unitarną transformację w nieunitarne, fizycznie pozbawione sensu odwzorowanie.

  • Evolution ujawnia to odrzucenie z operatora, który ewoluuje.

  • OrbitalRotation sprawdza, czy jego (osadzona) macierz rotacji jest blokowo-diagonalna względem podziału na alfa/beta w przypadku spinowym, i zgłasza ValueError, jeśli miesza sektory spinowe.

>>> 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

Symulacja bez zachowania liczby cząstek jest zatem niemożliwa w przestrzeni fermionowej, z samej konstrukcji reprezentacji ze stałym sektorem. Jest to cena za zwartą przestrzeń FCI, która w ogóle umożliwia praktyczną symulację fermionową. Jeśli twój algorytm potrzebuje operatorów naruszających liczbę cząstek (na przykład natywnego dla kubitów kanału błędów albo operatora zbudowanego dla zmapowanego hamiltonianu, który nie zachowuje liczby cząstek wyraz po wyrazie), najpierw dokonaj transpilacji do kubitów i zasymuluj otrzymany QuantumCircuit za pomocą symulatora na poziomie kubitów. Działa tu każda ścieżka transpilacji; przewodniki o obwodzie fermionowym i transpilacji opisują to szerzej, ale rozsądnym wyborem domyślnym jest generate_preset_jw_pass_manager().

Konwencja bloków spinowych dla układów ze spinem​

nelec ma typ int | tuple[int, int], a o wyborze jednego z obsługiwanych układów modów decyduje jego typ, a nie osobna flaga:

  • int wybiera interpretację bezspinową. norb modów jest traktowanych bezpośrednio jako orbitale bezspinowe, a nelec to łączna liczba cząstek.

  • Para (n_alpha, n_beta) wybiera interpretację ze spinem dla 2 * norb modów według stałej konwencji bloków spinowych. Mody 0 .. norb to orbitale alfa (spin w górę), a mody norb .. 2 * norb to orbitale beta (spin w dół). Każdy gatunek spinowy jest zachowywany niezależnie; wyraz tylko-alfa może przenieść elektron między modami alfa, ale nigdy do modu beta, i odwrotnie.

To rozdzielenie następuje w każdym punkcie wejścia protokołu ffsim w tym pakiecie (konstruktory bramek, implementacje _apply_unitary_placed_ oraz kompilacja sektorów natywnego jądra w Rust) i jest to konwencja używana we wszystkich przewodnikach LUCJ i SKQD (na przykład InitializeModes.from_hartree_fock() wpisuje obsadzenia alfa do modów 0 .. n_alpha, a obsadzenia beta do modów norb .. norb + n_beta). Warto porównać to z modułem qiskit_fermions.operators i ogólnie z FermionicCircuit, które używają ogólnych indeksów modów bez wbudowanej semantyki spinu (zobacz przewodnik o obwodzie fermionowym). Znaczenie bloków spinowych jest narzucane dopiero wtedy, gdy do wywołania symulacji zostanie przekazane nelec ze spinem, a nie jest wpisane w samą reprezentację operatora czy obwodu.

>>> 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]

Następne kroki​