Praca z obwodami fermionowymi
Koncepcje opisane w tym przewodniku są obecnie dostępne tylko w API Pythona. Równoważna funkcjonalność zostanie udostępniona w API C w przyszłym wydaniu.
Ten przewodnik wyjaśnia, jak używać FermionicCircuit do implementowania algorytmów kwantowych bezpośrednio w przestrzeni fermionowej, zamiast najpierw mapować na kubity.
Dlaczego warto używać obwodów fermionowych
Rozważ tradycyjny przepływ pracy przy implementacji ewolucji czasowej hamiltonianu fermionowego w architekturze opartej na kubitach:
-
Zdefiniuj hamiltonian fermionowy (na przykład ).
-
Zmapuj go na operator kubitowy (na przykład za pomocą mapowania Jordana-Wignera).
-
Zaimplementuj ewolucję czasową w przestrzeni kubitów (za pomocą trotteryzacji lub innych metod).
To podejście ma fundamentalne ograniczenie: wczesne mapowanie na kubity odrzuca wiedzę o problemie zawartą w strukturze fermionowej. Standardowe przebiegi optymalizacji nie potrafią wykorzystać symetrii fermionowych ani dozwolonych zmian kolejności wyrazów, co prowadzi do nieoptymalnych obwodów o zbędnej głębokości i liczbie bramek.
FermionicCircuit umożliwia lepszy przepływ pracy:
-
Zdefiniuj hamiltonian fermionowy (na przykład ).
-
Zaimplementuj ewolucję czasową bezpośrednio w przestrzeni fermionowej za pomocą bramek fermionowych.
-
Zmapuj na przestrzeń kubitów w ramach procesu transpilacji.
To podejście rozwiązuje ten problem, zachowując wiedzę o problemie w przestrzeni fermionowej, gdzie przebiegi optymalizacji mogą wykorzystać strukturę fermionową i relacje komutacji, aby zmniejszyć głębokość obwodu. Obwód fermionowy opisuje, jakie obliczenie wykonać, a transpilacja zajmuje się tym, jak zmapować je na kubity, co zapewnia czystszy podział odpowiedzialności.
Ogólne indeksowanie modów
Zarówno moduł operators, jak i FermionicCircuit używają ogólnego indeksowania opartego na modach, które nie czyni żadnych założeń co do natury modów. Mod to po prostu abstrakcyjny indeks oznaczający fermionowy stopień swobody, bez żadnej wewnętrznej semantyki. Ten wybór projektowy odzwierciedla moduł operators i zapewnia maksymalną elastyczność. Na przykład możesz zaimplementować ewolucję czasową dla dowolnego operatora implementującego protokół OperatorTrait, niezależnie od jego reprezentacji matematycznej.
Budowanie obwodu fermionowego
Poniższy przykład implementuje ewolucję czasową opisaną w poprzedniej sekcji. Tworzy FermionicCircuit przez podanie liczby modów fermionowych, a następnie dodaje bramki fermionowe z qiskit_fermions.circuit.library, aby zaimplementować ewolucję czasową.
Przykład pokazuje także, jak włączyć wiedzę dziedzinową do operatora za pomocą grupowania wyrazów operatora. Przypisując indeksy grup do wyrazów hamiltonianu, zachowujesz informacje strukturalne, które przebiegi optymalizacji mogą wykorzystać w całym stosie transpilacji.
>>> 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)
Zwróć uwagę, że grupowanie wyrazów operatora jest zachowane nawet w prostych operacjach, takich jak dekompozycja. Pokazuje to, jak informacje strukturalne przepływają przez stos obwodu. Aby poznać pełną transpilację do kubitów, zapoznaj się z przewodnikiem Transpilacja obwodów fermionowych.
Rozdzielanie bramek za pomocą bariery
Przebiegi optymalizacji łączą sąsiednie bramki, gdy tylko mogą. Na przykład MergeOrbitalRotations łączy ciąg kolejnych bramek OrbitalRotation w jedną, a MergeSlaterDeterminantPreparation wtapia InitializeModes w następującą po nim rotację.
Użyj FermionicCircuit.barrier(), gdy chcesz temu zapobiec. Bariera nie ma efektu unitarnego, więc nie zmienia stanu przygotowywanego przez obwód; jedynie wyznacza punkt, przez który przebiegi nie łączą bramek. Wywołaj ją bez argumentów, aby objęła wszystkie mody, lub przekaż mody, do których ma się ograniczać:
>>> 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)
Bariery przetrwają transpilację z fermionów do kubitów, więc ograniczają także następującą po niej optymalizację na poziomie kubitów. Ten etap omawia przewodnik Transpilacja obwodów fermionowych.
Powtarzanie obwodu
Wiele algorytmów wielokrotnie stosuje ten sam blok bramek. Typowym przypadkiem jest ewolucja czasowa: krok Trottera buduje się raz, a następnie powtarza, aby osiągnąć czas docelowy. Użyj repeat(), aby zbudować powtórzony obwód, oraz compose(), aby dołączyć go do prefiksu przygotowującego stan.
Poniższy przykład buduje krok Trottera drugiego rzędu dla modelu Fermiego-Hubbarda. Krok jest symetryczny: dzieli wyraz przeskoku na dwie bramki OrbitalRotation o połowie czasu trwania, otaczające oddziaływanie na węźle.
>>> 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)
Powtórzenie kroku i złożenie go ze stanem odniesienia Hartree-Focka daje pełną ewolucję:
>>> 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
Powtórzenia są spłaszczane w zwracanym obwodzie, zamiast być opakowane w jedną bramkę, więc count_ops() zlicza dwie rotacje na krok. To spłaszczenie sprawia, że redundancja na granicach kroków staje się widoczna dla transpilatora: końcowa rotacja o połowie czasu jednego kroku i początkowa następnego sąsiadują ze sobą, więc MergeOrbitalRotations łączy je w jedną rotację.
>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.passes import MergeOrbitalRotations
>>>
>>> merged = FermionicPassManager([MergeOrbitalRotations()]).run(evolution)
>>> merged.count_ops()["OrbitalRotation"]
4
Wynik kosztuje jedną rotację na krok plus jedną, czyli tyle, ile uzyskasz, łącząc te półkroki ręcznie. Zapisanie kroku w naturalnej formie symetrycznej i jego powtórzenie daje ten sam obwód, więc łączenie zostaje pozostawione etapowi optymalizacji.
Gdy potrzebujesz zachować powtórzenia oddzielnie, przekaż insert_barriers=True, aby umieścić między nimi barrier(). Przebiegi optymalizacji nie łączą bramek przez barierę, więc każda rotacja zostaje zachowana:
>>> separated = trotter_step.repeat(reps, insert_barriers=True)
>>> FermionicPassManager([MergeOrbitalRotations()]).run(separated).count_ops()[
... "OrbitalRotation"
... ]
6
Argument reps w FermionicSuzukiTrotter to inna koncepcja. Dzieli on pojedynczą bramkę Evolution na więcej krótszych czynników, podczas gdy repeat() duplikuje całe obwody. Powtórzenie obwodu zawierającego jedną bramkę Evolution jedynie mnoży jej całkowity czas ewolucji.
Pomiar modów fermionowych
measure_all() odczytuje każdy mod do osobnego bitu klasycznego, łącząc mod i z bitem i. Użyj measure() z własnym ClassicalRegister, aby odczytać tylko niektóre z nich.
>>> measured = trotter_step.measure_all(inplace=False)
>>> measured.count_ops()["measure"]
4
Przekazanie inplace=False zwraca zmierzoną kopię i pozostawia oryginał bez zmian, co ma znaczenie, ponieważ pomiar nie jest unitarny: obwodu zawierającego pomiar nie można już zastosować do wektora stanu, więc zachowaj niezmierzony obwód, jeśli chcesz go także symulować.
Pomiar w tym miejscu, a nie na transpilowanym QuantumCircuit, sprawia, że próbkowane ciągi bitów są łatwe do odczytania. Pomiar fermionowy wskazuje mod, który odczytuje, więc każdy etap przesuwający mody (RelabelModes na poziomie fermionowym, układ i routing na poziomie kubitów) przenosi razem z nimi pomiar, a ciąg bitów pozostaje indeksowany modem, o który pytasz.
W przeciwieństwie do measure_all(), przed pomiarami nie jest wstawiana żadna barrier(). Jeśli chcesz ją tam mieć, najpierw wywołaj barrier() samodzielnie.
To, czy zmierzony bit znaczy to, czego oczekujesz, zależy od kodowania fermion–kubit, które jest wybierane podczas transpilacji, a nie tutaj. W kodowaniu w bazie obsadzeń, takim jak Jordan-Wigner, każdy mod jest reprezentowany przez jeden kubit, a bit jest obsadzeniem tego modu; zobacz FermionicMeasure.
Transpilacja obwodów fermionowych
Aby zaimplementować algorytm kwantowy reprezentowany przez Twój FermionicCircuit, należy go transpilować do QuantumCircuit. Jak to zrobić, dowiesz się z przewodnika Transpilacja obwodów fermionowych.
Kolejne kroki
-
Poznaj dostępne bramki fermionowe w dokumentacji
qiskit_fermions.circuit.library. -
Zapoznaj się z przewodnikiem objaśniającym operatory, aby zrozumieć, jak konstruować hamiltoniany fermionowe, których możesz używać z obwodami fermionowymi.
-
Przejrzyj przewodnik wprowadzający SQDRIFT, który zawiera praktyczny przykład od początku do końca.
-
Przeczytaj przewodnik o relacji z ffsim, aby zrozumieć, jak
FermionicCircuitmożna symulować bezpośrednio i jak te ogólne indeksy modów nabierają semantyki spinowej dopiero w czasie symulacji.

