Formuły wieloproduktowe do redukcji błędu Trottera
Szacowane użycie QPU: cztery minuty na procesorze Heron r2 (UWAGA: to tylko szacunek. Twój czas wykonania może się różnić.)
Podstawy
Ten samouczek pokazuje, jak używać Formuły Wieloproduktowej (MPF), aby uzyskać mniejszy błąd Trottera dla obserwabli w porównaniu z błędem generowanym przez najgłębszy obwód Trottera, który faktycznie wykonamy. MPF redukuje błąd Trottera dynamiki Hamiltonowskiej poprzez ważoną kombinację kilku wykonań obwodów. Rozważmy zadanie wyznaczenia wartości oczekiwanych obserwabli dla stanu kwantowego z hamiltonianem . Można użyć Formuł Produktowych (PF) do przybliżenia ewolucji czasowej w następujący sposób:
- Zapisz Hamiltonian jako gdzie są operatorami hermitowskimi takimi, że każda odpowiadająca im jedynka może być efektywnie zaimplementowana na urządzeniu kwantowym.
- Przybliż wyrazy , które nie komutują ze sobą.
Wtedy formuła produktowa pierwszego rzędu (formuła Lie-Trottera) to:
która ma kwadratowy człon błędu . Można też używać formuł produktowych wyższego rzędu (formuły Lie-Trotter-Suzuki), które zbiegają szybciej i są zdefiniowane rekurencyjnie jako:
gdzie jest rzędem symetrycznej PF, a . Dla długich ewolucji czasowych można podzielić przedział czasu na podprzedziałów, zwanych krokami Trottera, o długości i przybliżyć ewolucję czasową w każdym podprzedziale formułą produktową rzędu , . Tym samym PF rzędu dla operatora ewolucji czasowej po krokach Trottera wynosi:
gdzie człon błędu maleje wraz z liczbą kroków Trottera i rzędem PF.
Dla danej liczby całkowitej i formuły produktowej przybliżony stan po ewolucji czasowej można uzyskać z przez zastosowanie iteracji formuły produktowej .
jest przybliżeniem z błędem przybliżenia Trottera ||. Jeśli rozważymy liniową kombinację przybliżeń Trottera :
gdzie są współczynnikami wagowymi, jest macierzą gęstości odpowiadającą czystemu stanowi uzyskanemu przez ewolucję stanu początkowego formułą produktową z krokami Trottera, a indeksuje liczbę PF składających się na MPF. Wszystkie wyrazy w używają tej samej formuły produktowej jako podstawy. Celem jest poprawa || przez znalezienie z jeszcze mniejszym .
- nie musi być stanem fizycznym, ponieważ nie musi być dodatnie. Celem jest minimalizacja błędu wartości oczekiwanej obserwabli, a nie znalezienie fizycznego zastępstwa dla .
- wyznacza zarówno głębokość obwodu, jak i poziom przybliżenia Trottera. Mniejsze wartości prowadzą do krótszych obwodów, które mają mniej błędów, ale są mniej dokładnym przybliżeniem pożądanego stanu.
Kluczem jest to, że pozostały błąd Trottera dany przez jest mniejszy niż błąd Trottera, który uzyskano by po prostu używając największej wartości .
Możesz spojrzeć na użyteczność tej metody z dwóch perspektyw:
- Przy ustalonym budżecie kroków Trottera, które możesz wykonać, możesz uzyskać wyniki z błędem Trottera mniejszym w sumie.
- Dla pewnej docelowej liczby kroków Trottera, która jest zbyt duża do wykonania, możesz użyć MPF, aby znaleźć zestaw obwodów o mniejszej głębokości, których uruchomienie prowadzi do podobnego błędu Trottera.
Wymagania
Przed rozpoczęciem tego samouczka upewnij się, że masz zainstalowane:
- Qiskit SDK v1.0 lub nowszy, z obsługą wizualizacji
- Qiskit Runtime v0.22 lub nowszy (
pip install qiskit-ibm-runtime) - MPF Qiskit addons (
pip install qiskit_addon_mpf) - Qiskit addons utils (
pip install qiskit_addon_utils) - Biblioteka Quimb (
pip install quimb) - Biblioteka Qiskit Quimb (
pip install qiskit-quimb) - Numpy v0.21 dla zgodności między pakietami (
pip install numpy==0.21)
Część I. Przykład małej skali
Badanie stabilności MPF
Nie ma oczywistych ograniczeń co do wyboru liczby kroków Trottera składających się na stan MPF . Jednak należy je dobierać ostrożnie, aby uniknąć niestabilności wynikowych wartości oczekiwanych obliczonych z . Dobrą ogólną zasadą jest ustawienie najmniejszego kroku Trottera tak, aby . Jeśli chcesz dowiedzieć się więcej o tym i jak wybierać pozostałe wartości , zapoznaj się z przewodnikiem Jak wybrać kroki Trottera dla MPF.
W poniższym przykładzie badamy stabilność rozwiązania MPF, obliczając wartość oczekiwaną magnetyzacji dla zakresu czasów przy użyciu różnych stanów po ewolucji czasowej. Konkretnie porównujemy wartości oczekiwane obliczone z każdej przybliżonej ewolucji czasowej zaimplementowanej z odpowiednimi krokami Trottera oraz różnymi modelami MPF (współczynniki statyczne i dynamiczne) z dokładnymi wartościami obserwabli po ewolucji czasowej. Najpierw zdefiniujmy parametry dla formuł Trottera i czasów ewolucji
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-mpf qiskit-addon-utils qiskit-aer qiskit-ibm-runtime rustworkx scipy
import numpy as np
mpf_trotter_steps = [1, 2, 4]
order = 2
symmetric = False
trotter_times = np.arange(0.5, 1.55, 0.1)
exact_evolution_times = np.arange(trotter_times[0], 1.55, 0.05)
W tym przykładzie użyjemy stanu Neela jako stanu początkowego oraz modelu Heisenberga na łańcuchu 10 węzłów dla Hamiltonianu rządzącego ewolucją czasową
gdzie jest stałą sprzężenia dla krawędzi najbliższych sąsiadów.
from qiskit.transpiler import CouplingMap
from rustworkx.visualization import graphviz_draw
from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian
import numpy as np
L = 10
# Generate some coupling map to use for this example
coupling_map = CouplingMap.from_line(L, bidirectional=False)
graphviz_draw(coupling_map.graph, method="circo")
# Get a qubit operator describing the Heisenberg field model
hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(1.0, 1.0, 1.0),
ext_magnetic_field=(0.0, 0.0, 0.0),
)
print(hamiltonian)
SparsePauliOp(['IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII'],
coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
Obserwabla, którą będziemy mierzyć, to magnetyzacja pary qubitów w środku łańcucha.
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp.from_sparse_list(
[("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)
print(observable)
SparsePauliOp(['IIIIZZIIII'],
coeffs=[1.+0.j])
Definiujemy przebieg transpilera zbierający rotacje XX i YY w obwodzie jako pojedynczą bramkę XX+YY. Pozwoli to nam wykorzystać właściwości zachowania spinu TeNPy podczas obliczania MPO, co znacznie przyspieszy obliczenia.
from qiskit.circuit.library import XXPlusYYGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes.optimization.collect_and_collapse import (
CollectAndCollapse,
collect_using_filter_function,
collapse_to_operation,
)
from functools import partial
def filter_function(node):
return node.op.name in {"rxx", "ryy"}
collect_function = partial(
collect_using_filter_function,
filter_function=filter_function,
split_blocks=True,
min_block_size=1,
)
def collapse_to_xx_plus_yy(block):
param = 0.0
for node in block.data:
param += node.operation.params[0]
return XXPlusYYGate(param)
collapse_function = partial(
collapse_to_operation,
collapse_function=collapse_to_xx_plus_yy,
)
pm = PassManager()
pm.append(CollectAndCollapse(collect_function, collapse_function))
Następnie tworzymy obwody implementujące przybliżone ewolucje czasowe Trottera.
from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
)
from qiskit import QuantumCircuit
# Initial Neel state preparation
initial_state_circ = QuantumCircuit(L)
initial_state_circ.x([i for i in range(L) if i % 2 != 0])
all_circs = []
for total_time in trotter_times:
mpf_trotter_circs = [
generate_time_evolution_circuit(
hamiltonian,
time=total_time,
synthesis=SuzukiTrotter(reps=num_steps, order=order),
)
for num_steps in mpf_trotter_steps
]
mpf_trotter_circs = pm.run(
mpf_trotter_circs
) # Collect XX and YY into XX + YY
mpf_circuits = [
initial_state_circ.compose(circuit) for circuit in mpf_trotter_circs
]
all_circs.append(mpf_circuits)
mpf_circuits[-1].draw("mpl", fold=-1)

Następnie obliczamy wartości oczekiwane po ewolucji czasowej z obwodów Trottera.
from copy import deepcopy
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
aer_sim = AerSimulator()
estimator = Estimator(mode=aer_sim)
mpf_expvals_all_times, mpf_stds_all_times = [], []
for t, mpf_circuits in zip(trotter_times, all_circs):
mpf_expvals = []
circuits = [deepcopy(circuit) for circuit in mpf_circuits]
pm_sim = generate_preset_pass_manager(
backend=aer_sim, optimization_level=3
)
isa_circuits = pm_sim.run(circuits)
result = estimator.run(
[(circuit, observable) for circuit in isa_circuits], precision=0.005
).result()
mpf_expvals = [res.data.evs for res in result]
mpf_stds = [res.data.stds for res in result]
mpf_expvals_all_times.append(mpf_expvals)
mpf_stds_all_times.append(mpf_stds)
Obliczamy też dokładne wartości oczekiwane do porównania.
from scipy.linalg import expm
from qiskit.quantum_info import Statevector
exact_expvals = []
for t in exact_evolution_times:
# Exact expectation values
exp_H = expm(-1j * t * hamiltonian.to_matrix())
initial_state = Statevector(initial_state_circ).data
time_evolved_state = exp_H @ initial_state
exact_obs = (
time_evolved_state.conj()
@ observable.to_matrix()
@ time_evolved_state
).real
exact_expvals.append(exact_obs)
Statyczne współczynniki MPF
Statyczne MPF to takie, w których wartości nie zależą od czasu ewolucji . Rozważmy PF rzędu z krokami Trottera, co można zapisać jako:
gdzie są macierzami zależnymi od komutatorów wyrazów w dekompozycji Hamiltonianu. Ważne jest, że same w sobie są niezależne od czasu i liczby kroków Trottera . Dlatego możliwe jest anulowanie wyrazów błędu niższego rzędu wnoszących wkład do przez staranny dobór wag kombinacji liniowej. Aby anulować błąd Trottera dla pierwszych wyrazów (które dają największy wkład, bo odpowiadają mniejszej liczbie kroków Trottera) w wyrażeniu na , współczynniki muszą spełniać następujące równania:
z . Pierwsze równanie gwarantuje brak obciążenia w konstruowanym stanie , drugie zapewnia anulowanie błędów Trottera. Dla PF wyższego rzędu drugie równanie ma postać , gdzie dla symetrycznych PF i w przeciwnym razie, z . Wynikowy błąd (Ref. [1], [2]) wynosi wtedy
Wyznaczenie statycznych współczynników MPF dla danego zestawu wartości sprowadza się do rozwiązania układu równań liniowych zdefiniowanego przez powyższe dwa równania względem zmiennych : . Gdzie są szukanymi współczynnikami, jest macierzą zależną od i typu używanej PF (), a jest wektorem ograniczeń. Konkretnie:
gdzie to order, wynosi jeśli symmetric jest True, a w przeciwnym razie, to trotter_steps, a to zmienne do wyznaczenia. Indeksy i zaczynają się od . Można to też zobrazować w postaci macierzowej:
oraz
Więcej szczegółów znajdziesz w dokumentacji Układu Równań Liniowych (LSE).
Możemy znaleźć rozwiązanie dla analitycznie jako ; patrz np. Ref. [1] lub [2]. Jednak to dokładne rozwiązanie może być „źle uwarunkowane", co prowadzi do bardzo dużych norm L1 naszych współczynników , co może skutkować słabą wydajnością MPF. Zamiast tego można uzyskać przybliżone rozwiązanie minimalizujące normę L1 parametru , aby spróbować zoptymalizować zachowanie MPF.
Konfiguracja LSE
Teraz, gdy wybraliśmy wartości , musimy najpierw skonstruować LSE, zgodnie z powyższym opisem.
Macierz zależy nie tylko od , ale też od naszego wyboru PF, w szczególności jej rzędu.
Dodatkowo możesz uwzględnić, czy PF jest symetryczna czy nie (patrz [1]), ustawiając symmetric=True/False.
Nie jest to jednak wymagane, jak pokazuje Ref. [2].
from qiskit_addon_mpf.static import setup_static_lse
lse = setup_static_lse(mpf_trotter_steps, order=order, symmetric=symmetric)
Przeanalizujmy wartości wybrane powyżej, aby skonstruować macierz i wektor . Dla kroków Trottera , rzędu i wyboru niesymetrycznych kroków Trottera (), elementy macierzy poniżej pierwszego wiersza wyznacza wyrażenie , konkretnie:
lub w postaci macierzowej:
Można to sprawdzić, inspekcjonując obiekt lse:
lse.A
array([[1. , 1. , 1. ],
[1. , 0.25 , 0.0625 ],
[1. , 0.125 , 0.015625]])
Natomiast wektor ograniczeń ma następujące elementy:
Zatem
I analogicznie w lse:
lse.b
array([1., 0., 0.])
Obiekt lse ma metody do wyznaczania statycznych współczynników spełniających układ równań.
mpf_coeffs = lse.solve()
print(
f"The static coefficients associated with the ansatze are: {mpf_coeffs}"
)
The static coefficients associated with the ansatze are: [ 0.04761905 -0.57142857 1.52380952]
Optymalizacja za pomocą modelu dokładnego
Alternatywnie do obliczania , możesz też użyć setup_exact_model, aby skonstruować instancję cvxpy.Problem, która używa LSE jako ograniczeń, a jej optymalne rozwiązanie daje .
W następnej sekcji stanie się jasne, dlaczego ten interfejs istnieje.
from qiskit_addon_mpf.costs import setup_exact_problem
model_exact, coeffs_exact = setup_exact_problem(lse)
model_exact.solve()
print(coeffs_exact.value)
[ 0.04761905 -0.57142857 1.52380952]
Jako wskaźnik tego, czy MPF skonstruowane z tymi współczynnikami da dobre wyniki, możemy użyć normy L1 (patrz też Ref. [1]).
print(
"L1 norm of the exact coefficients:",
np.linalg.norm(coeffs_exact.value, ord=1),
) # ord specifies the norm. ord=1 is for L1
L1 norm of the exact coefficients: 2.1428571428556378
Optymalizacja za pomocą modelu przybliżonego
Może się zdarzyć, że norma L1 dla wybranego zestawu wartości zostanie uznana za zbyt wysoką. Jeśli tak jest i nie możesz wybrać innego zestawu wartości , możesz użyć przybliżonego rozwiązania LSE zamiast dokładnego.
W tym celu wystarczy użyć setup_approximate_model, aby skonstruować inną instancję cvxpy.Problem, która ogranicza normę L1 do wybranego progu, minimalizując jednocześnie różnicę i .
from qiskit_addon_mpf.costs import setup_sum_of_squares_problem
model_approx, coeffs_approx = setup_sum_of_squares_problem(
lse, max_l1_norm=1.5
)
model_approx.solve()
print(coeffs_approx.value)
print(
"L1 norm of the approximate coefficients:",
np.linalg.norm(coeffs_approx.value, ord=1),
)
[-1.10294118e-03 -2.48897059e-01 1.25000000e+00]
L1 norm of the approximate coefficients: 1.5
Zauważ, że masz pełną swobodę w sposobie rozwiązywania tego problemu optymalizacyjnego, co oznacza, że możesz zmieniać solver optymalizacyjny, jego progi zbieżności i tak dalej. Sprawdź odpowiedni przewodnik na temat Jak używać modelu przybliżonego.
Dynamiczne współczynniki MPF
W poprzedniej sekcji wprowadziliśmy statyczne MPF poprawiające standardowe przybliżenie Trottera. Jednak ta statyczna wersja niekoniecznie minimalizuje błąd przybliżenia. Konkretnie, statyczne MPF, oznaczone , nie jest optymalną projekcją na podprzestrzeń rozpiętą przez stany formuły produktowej .
Aby temu zaradzić, rozważamy dynamiczne MPF (wprowadzone w Ref. [2] i eksperymentalnie zademonstrowane w Ref. [3]), które minimalizuje błąd przybliżenia w normie Frobeniusa. Formalnie koncentrujemy się na minimalizacji
względem pewnych współczynników w każdej chwili . Optymalny projektor w normie Frobeniusa to , i nazywamy dynamicznym MPF. Podstawiając powyższe definicje:
gdzie jest macierzą Grama, zdefiniowaną przez