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?

Wdrażanie i uruchamianie szablonu Qiskit Function dla dynamiki hamiltonianu AQC + Trotter

Przegląd​

To jest niezależny od eksperymentu szablon Qiskit Function dla dynamiki hamiltonianu. Mając 1D hamiltonian Pauliego z oddziaływaniem najbliższych sąsiadów, przygotowany stan początkowy (opcjonalnie) oraz zestaw obserwabli, uruchamia on ewolucję czasową Trottera, kompresję obwodu za pomocą przybliżonej kompilacji kwantowej (AQC) oraz wykonanie z łagodzeniem błędów, a następnie zwraca szereg czasowy dla każdej obserwabli. Zamień konfigurację (PRE) i analizę (POST), a ten sam rdzeń napędza inny eksperyment:

PRE (twoja konfiguracja)FUNKCJA (wdrożona tutaj)POST (twoja analiza)
Przygotuj stan, jako obwód lub stan produktowy, z opcjonalnym lokalnym kopnięciemSynteza Trottera → kompresja AQC → wykonanie na statevector, fake lub runtime, zwracające ⟨O⟩(t)\langle O \rangle(t)S(q,ω)S(q, \omega) dla rozpraszania neutronów, lub magnetyzacja, transport, dynamika quench i tak dalej

Szablon jest opublikowany w repozytorium szablonów Qiskit Function, obok innych szablonów aplikacji. Ten notatnik wdraża go na twoim własnym koncie Qiskit Serverless. Uruchom go raz, a każdy notatnik będzie mógł potem wywołać funkcję za pomocą serverless.load("aqc-dynamics-function").

Aby zobaczyć przepracowany przykład naukowy, zobacz Symulacja rozpraszania neutronów za pomocą przepływu pracy Serverless AQC + Trotter dynamics, który wywołuje tę funkcję w celu obliczenia czynnika struktury dynamicznej KCuF3_3. Ten notatnik dotyczy zamiast tego wdrożenia i kontraktu wejściowego.

Wymagania​

Zanim zaczniesz, upewnij się, że masz następujące elementy w środowisku kernela tego notatnika:

  • Qiskit SDK w wersji 2.0 lub nowszej (pip install qiskit).

  • Klient Qiskit IBM Catalog (pip install qiskit-ibm-catalog), który wdraża i uruchamia zadania w Qiskit Serverless.

Własne zależności naukowe funkcji (qiskit-addon-aqc-tensor, cotengrust, qiskit-aer) nie muszą być instalowane lokalnie.

Pobierz pliki źródłowe szablonu​

Funkcja jest małym pakietem Python, który Qiskit Serverless uruchamia w chmurze, więc jej źródło musi istnieć jako lokalne pliki, które są przesyłane w momencie wdrożenia. Pakiet jest opublikowany w repozytorium szablonów Qiskit Function.

Pobierz source_files

Pobrany plik to pojedyncze archiwum zip, nazwane zgodnie z pełną ścieżką katalogu w repozytorium:

qiskit-community qiskit-function-templates main physics aqc_trotter source_files.zip

  1. Rozpakuj go do katalogu, w którym znajduje się ten notatnik.

  2. Zmień nazwę wypakowanego folderu z tej długiej nazwy na source_files.

Twój katalog roboczy będzie wtedy wyglądał tak:

your-working-directory/
├── function-template-aqc-trotter.ipynb <- this notebook
└── source_files/ <- the renamed folder
├── __init__.py
├── program.py
└── source/
├── __init__.py
├── _serverless.py
├── app_function.py
├── aqc.py
├── build.py
├── execute.py
└── hamiltonian.py

Nazwa musi brzmieć dokładnie source_files, ponieważ to jest working_dir, który przesyła Krok 3.

program.py jest punktem wejścia wywoływanym przez bramę. Wszystko pod source/ jest implementacją, podzieloną na etapy: synteza hamiltonianu i Trottera, kompresja AQC oraz wykonanie. Żaden z tych plików nie wymaga edycji, aby uruchomić poniższe przykłady. Krok 3 przesyła cały katalog, więc powtarzaj ten krok za każdym razem, gdy zmienisz plik.

1. Uwierzytelnianie​

Użyj qiskit-ibm-catalog, aby uwierzytelnić się w QiskitServerless za pomocą klucza API (tokenu) i CRN (instancji), które znajdziesz na pulpicie IBM Quantum® Platform. Dzięki tym danym uwierzytelniającym możesz utworzyć lokalną instancję klienta serverless, aby przesyłać lub uruchamiać wybraną funkcję:

from qiskit_ibm_catalog import QiskitServerless
serverless = QiskitServerless(channel="ibm_quantum_platform", token="MY_TOKEN", instance="MY_CRN")

Możesz opcjonalnie użyć save_account(), aby zapisać swoje dane uwierzytelniające w lokalnym środowisku (zobacz przewodnik Konfiguracja konta IBM Cloud®). Zauważ, że zapisuje to twoje dane uwierzytelniające do tego samego pliku, co QiskitRuntimeService.save_account():

QiskitServerless.save_account(channel="ibm_quantum_platform", token="MY_TOKEN", instance="MY_CRN")

Jeśli konto zostało zapisane, nie ma potrzeby podawania tokenu w celu uwierzytelnienia:

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_catalog": "qiskit-ibm-catalog"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit_ibm_catalog import QiskitServerless

# Authenticate to the remote cluster
# In this case, loading a saved account
serverless = QiskitServerless()

# REPLACE WITH YOUR OWN CREDENTIALS or SAVED ACCOUNT
# serverless = QiskitServerless(channel="ibm_quantum_platform", token="MY_TOKEN", instance="MY_CRN")

2. Deklarowanie zależności​

Pakiety, których funkcja potrzebuje oprócz zarządzanego bazowego obrazu serverless.

uwaga

Brama instaluje jedynie nazwy z jej listy dozwolonych (requirements-dynamic-dependencies.txt), dopasowywane po nazwie pakietu i przypięte do dozwolonej wersji za pomocą ==. Wszystko inne musi dotrzeć tranzytywnie (jako zależność pakietu z listy dozwolonych). Składnia [extras] jest respektowana: qiskit-addon-aqc-tensor[quimb-jax] instaluje quimb i jax. cotengrust jest potrzebny dla wydajności pamięciowej podczas symulacji sieci tensorowej. qiskit-aer jest wymieniony osobno dla backendu fake (lokalna symulacja z szumem).

DEPENDENCIES = [
"qiskit-addon-aqc-tensor[quimb-jax]==0.3.1",
"qiskit-aer==0.17.2",
"cotengrust==0.2.0",
]

3. Definiowanie i przesyłanie funkcji​

from qiskit_ibm_catalog import QiskitFunction

fn = QiskitFunction(
title="aqc-dynamics-function",
entrypoint="program.py",
working_dir="source_files/",
dependencies=DEPENDENCIES,
)
serverless.upload(fn)
QiskitFunction(aqc-dynamics-function)

4. Weryfikacja rejestracji​

next(p for p in serverless.list() if p.title == "aqc-dynamics-function")
QiskitFunction(aqc-dynamics-function)

Dokumentacja funkcji​

To jest krótkie wprowadzenie. Każde pole jest w pełni udokumentowane w README szablonu AQC Dynamics: pełna tabela wejść z regułami walidacji, pola wyjściowe, backendy wykonania oraz dalsze przepracowane przykłady. Poniżej znajduje się skrócona wersja, wystarczająca do zapoznania się z kolejnymi przykładami.

Wejścia​

Każde uruchomienie to pojedyncze wywołanie fn.run(...). Wymagane są tylko pierwsze trzy wejścia w tabeli: hamiltonian, t_steps i aqc_segments. Wszystko po nich jest opcjonalne i przyjmuje pokazaną wartość domyślną, więc minimalne wywołanie to trzy argumenty, a reszta tabeli to funkcjonalność, w którą możesz się opcjonalnie włączyć. num_qubits hamiltonianu ustala długość łańcucha, więc nie ma osobnego wejścia rozmiaru.

WejścieDomyślnieOpis
hamiltonianwymagane1D hamiltonian Pauliego z oddziaływaniem najbliższych sąsiadów jako SparsePauliOp. Ciągi to operatory Pauliego, więc nie ma niejawnego współczynnika jednej drugiej.
t_stepswymaganeCałkowita liczba kroków Trottera. Ewoluuje do T = t_steps * dt i raportuje każdą obserwablę przy każdym t_k = k * dt.
aqc_segmentswymaganePlan kompresji: lista {"n_steps": k, "ansatz_steps": m}. Kroki sum(n_steps) są kompresowane; reszta działa jako zwykły Trotter.
dt0.2Czas fizyczny, o jaki przesuwa jeden krok Trottera.
initial_state|0...0>Przygotowany QuantumCircuit do ewolucji. Dowolne lokalne kopnięcie umieść bezpośrednio w tym obwodzie.
observablesZ dla każdego miejscaWszystko, co EstimatorV2 akceptuje jako swój argument observables. Jedna obserwabla na kolumnę wyjściową.
trotter_optionsSuzuki 2. rzędu{"method": ..., "synthesis_settings": {...}}. reps i time są własnością funkcji.
aqc_optionspatrz opismax_bond (32), cutoff (1e-8), autodiff_backend ("jax"), fidelity_target (None), optimizer_settings (L-BFGS-B, jac=True, maxiter=300).
estimator_optionsDD, twirling, TREXEstimatorV2.options, przekazywane bez zmian. Podany słownik zastępuje wartości domyślne w całości, zamiast się z nimi łączyć.
transpiler_options{"optimization_level": 3}Argumenty nazwane funkcji generate_preset_pass_manager. backend i target są odrzucane, ponieważ są własnością ścieżki wykonania.
backend"runtime""statevector", "fake" lub "runtime".
backend_namenajmniej obciążonyNazwa backendu IBM® dla runtime lub nazwanego fałszywego backendu.
batches1Podziel obwody na N zadań runtime. Jedna partia przesyła pojedyncze zadanie i nie tworzy sesji.
parallel_simFalseRozłóż ścieżki lokalnego symulatora na wszystkie dostępne rdzenie za pomocą Ray. Bez wpływu na runtime.
return_circuitsFalseZwróć logiczne obwody AQC + Trotter w wyniku obok szeregu obserwabli.

Backendy wykonania​

Wszystkie trzy ścieżki współdzielą ten sam kod i te same ustawienia łagodzenia błędów. Różnią się tylko miejscem, w którym uruchamiane są obwody.

backendCo to jestDane uwierzytelniająceUwagi
"statevector"Dokładny StatevectorEstimatorTylko konto ServerlessDokładna ścieżka referencyjna. Bez czasu QPU.
"fake"Symulacja lokalna z szumem na fałszywym backendzie QiskitTylko konto ServerlessWierna próba generalna ścieżki runtime z łagodzeniem błędów. Wymaga qiskit-aer. Domyślnie 127-kubitowy fake_sherbrooke.
"runtime" (domyślnie)EstimatorV2 z łagodzeniem błędów na prawdziwym QPUKonto Serverless i instancja z dostępem do QPUbackend_name opcjonalne; pominięcie go wybiera najmniej obciążone urządzenie.

Obie ścieżki symulatora nadal wywołują wdrożoną funkcję, więc wymagają zapisanego konta Serverless, mimo że nie korzystają z czasu QPU. Dwa poniższe przykłady uruchamiają to samo zadanie najpierw na statevector, a następnie na runtime.

Wynik​

job.result() zwraca zwykły słownik:

{
"times": [...], # length t_steps + 1, t_k = k * dt (t=0 is the prepared state)
"expectation_values": [[...]], # shape (n_times, n_observables)
"observable_labels": [...], # for example: ["Z_0", "ZZ_0_1"]
"metadata": {
"n", "t_steps", "dt", "tier",
"aqc_compressed_steps": 5, # total compressed steps (= sum of segment n_steps)
"aqc_segments": [ # per segment: the plan plus its own results
{"n_steps": 3, "ansatz_steps": 1, "steps": [1, 2, 3], "n_params": 133,
"fidelities": {"1": ..., "2": ..., "3": ...}},
{"n_steps": 2, "ansatz_steps": 2, "steps": [4, 5], "n_params": 245,
"fidelities": {"4": ..., "5": ...}},
],
"execution_backend",
"aqc_fidelities": {"1": ..., "2": ...}, # flat per-step fidelity, all compressed steps
"circuit_stats": { # per-step 2q depth and gate count, full Trotter vs AQC
"1": {"full_trotter": {"depth_2q": ..., "num_2q_gates": ...},
"aqc_trotter": {"depth_2q": ..., "num_2q_gates": ...}},
"2": {...},
},
"warnings": [...], # non-fatal notices; for example, a cotengrust fallback
"resource_usage": { # per stage; QPU_TIME is the charged QPU time
"RUNNING: OPTIMIZING_FOR_HARDWARE": {"CPU_TIME": ...},
"RUNNING: WAITING_FOR_QPU": {"CPU_TIME": ...},
"RUNNING: EXECUTING_QPU": {"QPU_TIME": ...},
},
},
# present only when return_circuits=True
"circuits": [QuantumCircuit, ...], # one per evolved step; circuits[i] is at times[i + 1]
}

aqc_fidelities i circuit_stats to dwa pola, które warto przeczytać najpierw: razem mówią ci, czy kompresja pozostała wierna i czy rzeczywiście zaoszczędziła głębokość. W runtime resource_usage raportuje czas oczekiwania w kolejce oddzielnie od czasu QPU, za który jesteś obciążany. Odrzucone wejście kończy się szybko strukturalnym błędem ServerlessError (kod 4615).

Przykład symulatora​

Najpierw uruchom funkcję na dokładnym backendzie statevector. Nie zużywa on czasu QPU i weryfikuje wdrożenie od początku do końca. Modelem jest tutaj ośmiokubitowy łańcuch Isinga z polem poprzecznym, a observables jest pominięte, więc funkcja mierzy domyślne ZZ dla każdego miejsca.

Plan kompresji to wejście warte zrozumienia. Każdy segment {"n_steps": k, "ansatz_steps": m} kompresuje k kolejnych kroków Trottera do ansatzu zbudowanego z celu Trottera o m krokach, a wszelkie kroki poza sum(n_steps) działają jako zwykły Trotter. Wczesne kroki o niskim splątaniu kompresują się dobrze do płytkiego jednowarstwowego ansatzu; późniejsze, bardziej splątane kroki wymagają głębszego.

from qiskit.quantum_info import SparsePauliOp

fn = serverless.load("aqc-dynamics-function")

n = 8
H = SparsePauliOp.from_sparse_list(
[("ZZ", [i, i + 1], 1.0) for i in range(n - 1)]
+ [("X", [i], 0.8) for i in range(n)],
num_qubits=n,
)

job = fn.run(
t_steps=8,
aqc_segments=[
{
"n_steps": 4,
"ansatz_steps": 1,
}, # early steps -> shallow 1-layer ansatz
{
"n_steps": 2,
"ansatz_steps": 2,
}, # later steps -> deeper 2-layer ansatz
],
hamiltonian=H,
aqc_options={"max_bond": 32},
backend="statevector",
)
print("job ID:", job.job_id)
job ID: ee1f3793-e995-427d-81d1-5924549beb38

Śledzenie uruchomienia i odczyt wyniku​

status() raportuje zarówno ogólny cykl życia zadania, jak i podstatus dla poszczególnych etapów, publikowany przez funkcję w trakcie działania. Te same etapy dotyczą uruchomienia na sprzęcie później w tym przewodniku:

QUEUED -> INITIALIZING -> RUNNING: OPTIMIZING_FOR_HARDWARE -> RUNNING: WAITING_FOR_QPU -> RUNNING: EXECUTING_QPU -> RUNNING: POST_PROCESSING -> DONE

wartość status()Etap
RUNNING: OPTIMIZING_FOR_HARDWAREprzygotowanie stanu, budowa Trottera, kompresja AQC
RUNNING: WAITING_FOR_QPUw kolejce na QPU (tylko backend runtime)
RUNNING: EXECUTING_QPUobwody są wykonywane (lokalne symulatory oznaczają to bezpośrednio)
RUNNING: POST_PROCESSINGbudowanie słownika wyniku

Stanami końcowymi są DONE, ERROR i CANCELED. To uruchomienie statevector nie ma kolejki QPU, więc pomija RUNNING: WAITING_FOR_QPU. Użyj job.logs() w dowolnym momencie, aby zobaczyć logi poszczególnych etapów, w tym wierność AQC osiągniętą przy każdym kroku.

print(job.status()) # re-run until this reports DONE
DONE
import numpy as np

result = job.result()
ev = np.array(result["expectation_values"])

print("observables:", result["observable_labels"])
print("shape:", ev.shape, "-> (n_times, n_observables)")
print("first row (t = 0, the prepared state):", np.round(ev[0], 4))
print("last row (t = t_steps * dt):", np.round(ev[-1], 4))
print(
"AQC fidelities:",
{k: round(v, 4) for k, v in result["metadata"]["aqc_fidelities"].items()},
)

# What the compression bought: 2-qubit depth at the final time step.
stats = result["metadata"]["circuit_stats"][
str(result["metadata"]["t_steps"])
]
print(
"2q depth at the final step:",
stats["full_trotter"]["depth_2q"],
"(full Trotter) ->",
stats["aqc_trotter"]["depth_2q"],
"(AQC + Trotter)",
)
observables: ['Z_0', 'Z_1', 'Z_2', 'Z_3', 'Z_4', 'Z_5', 'Z_6', 'Z_7']
shape: (9, 8) -> (n_times, n_observables)
first row (t = 0, the prepared state): [1. 1. 1. 1. 1. 1. 1. 1.]
last row (t = t_steps * dt): [0.1442 0.2956 0.4686 0.4877 0.4869 0.4686 0.2963 0.1441]
AQC fidelities: {'1': 1.0, '2': 1.0, '3': 1.0, '4': 1.0, '5': 1.0, '6': 0.9999}
2q depth at the final step: 210 (full Trotter) -> 79 (AQC + Trotter)

Przykład na sprzęcie​

Wywołanie funkcji z backend="runtime" transpiluje i wykonuje na prawdziwym procesorze IBM Quantum, z wbudowanym łagodzeniem błędów funkcji: dynamicznym odsprzęganiem (XY4), twirlingiem bramek oraz twirlowaną eliminacją błędów odczytu (TREX). backend_name wybiera urządzenie; pomiń go, a funkcja weźmie najmniej obciążone.

Nic w kodzie naukowym się nie zmienia. To, co różni się od przykładu symulatora, to długość łańcucha, liczba kroków Trottera, plan kompresji, backend oraz jawne ustawienia łagodzenia błędów omówione w następnej sekcji.

Dobór rozmiaru zadania dla sprzętu sterującego​

estimator_options to wejście warte świadomego ustawienia. Twirling bramek buduje num_randomizations oddzielnych zrandomizowanych obwodów dla każdego PUB, a całe zadanie, każdy PUB ze wszystkimi swoimi randomizacjami, musi zmieścić się w pamięci instrukcji klasycznego systemu sterowania QPU. Funkcja domyślnie ustawia 1000 randomizacji, więc 10-krokowa ewolucja przesyła 11 PUB-ów po 1000 obwodów każdy: około 11 000 instancji obwodów w pojedynczym zadaniu.

Przekroczenie tego, co pomieści system sterowania, powoduje niepowodzenie zadania z błędem 6073. Limity zadań podają progi i sposób ich obliczania, przy czym głównym z nich jest 26,8 miliona instrukcji systemu sterowania na kubit, stosowane na zadanie, a nie na PUB. Dynamiczne odsprzęganie dodaje bramki, które wliczają się do tego limitu.

Dwa wejścia kontrolują rozmiar:

  • estimator_options ustawia budżet strzałów. Całkowita liczba strzałów to num_randomizations * shots_per_randomization, więc możesz wymieniać randomizacje na strzały na randomizację, zachowując statystyki, a jednocześnie zmniejszając program. Poniższa komórka używa 100 randomizacji po 200 strzałów każda, co daje 20 000 strzałów na obserwablę i około jedną dziesiątą liczby instancji obwodów, które przesłałyby wartości domyślne. Zobacz TwirlingOptions oraz Opcje Estimatora, aby poznać pełny zestaw pól.

  • batches dzieli PUB-y na tyle oddzielnych zadań runtime, co jest środkiem zaradczym sugerowanym przez sam błąd 6073 i dlatego liczy się podejście na zadanie. Ustawienie batches=4 wysyła około trzech PUB-ów na zadanie zamiast jedenastu naraz, a zadania wychodzą razem w jednej partii, więc grupa staje w kolejce raz, a nie każde zadanie osobno.

Pamiętaj, że podane estimator_options zastępuje wartości domyślne funkcji w całości, zamiast się z nimi łączyć, więc dynamiczne odsprzęganie i TREX są ponownie ustawione w poniższej komórce, aby pozostały włączone.

from qiskit.quantum_info import SparsePauliOp

fn = serverless.load("aqc-dynamics-function")

n = 10
H = SparsePauliOp.from_sparse_list(
[("ZZ", [i, i + 1], 1.0) for i in range(n - 1)]
+ [("X", [i], 0.8) for i in range(n)],
num_qubits=n,
)

job = fn.run(
t_steps=10,
aqc_segments=[
{
"n_steps": 3,
"ansatz_steps": 1,
}, # early steps -> shallow 1-layer ansatz
{
"n_steps": 3,
"ansatz_steps": 2,
}, # later steps -> deeper 2-layer ansatz
],
hamiltonian=H,
aqc_options={"max_bond": 32},
backend="runtime",
backend_name="ibm_marrakesh",
# The function defaults to 1000 twirling randomizations, which was too large
# for this device. Total shots is num_randomizations *
# shots_per_randomization, so this is 20,000 shots per observable.
estimator_options={
"dynamical_decoupling": {"enable": True, "sequence_type": "XY4"},
"twirling": {
"enable_gates": True,
"num_randomizations": 100,
"shots_per_randomization": 200,
},
"resilience": {"measure_mitigation": True},
},
)
print("job ID (save this to reconnect later):", job.job_id)
job ID (save this to reconnect later): 7229a8bf-9f83-4785-8dd4-489844abc2d9
Ponowne łączenie się z długo działającym zadaniem

Uruchomienie na sprzęcie nie jest szybkie, a większość czasu zajmują obliczenia klasyczne, a nie praca na QPU. Kompresja AQC działa wewnątrz funkcji, zanim cokolwiek dotrze do QPU, a kolejka QPU dochodzi do tego dodatkowo. Nie musisz utrzymywać otwartego tego notatnika ani kernela podczas jego działania.

Skopiuj ID zadania wydrukowany przez poprzednią komórkę i zapisz go. Kolejne trzy komórki pozwalają ci wrócić do zadania później:

  1. Ponowne połączenie, potrzebne tylko w nowej sesji kernela: uruchom ponownie komórkę Uwierzytelnianie, aby odtworzyć serverless, a następnie odbuduj uchwyt job na podstawie zapisanego ID. Pomiń tę komórkę, jeśli nadal jesteś w sesji, w której przesłałeś zadanie, ponieważ uchwyt jest już aktywny.

  2. Sprawdź status: uruchamiaj ponownie, aż zgłosi DONE.

  3. Pobierz wynik: uruchom tylko wtedy, gdy status to DONE.

Wklej zapisany identyfikator w miejsce symbolu zastępczego w poniższej komórce ponownego łączenia.

# Reconnect to a previously submitted job by its ID. Only needed in a NEW kernel
# session; if you are still in the session where you submitted, the `job` handle
# from the preceding cell is already live, so skip this cell. Replace the ID that follows with your own.
job = serverless.get_job_by_id("<your job ID>")
# Re-run this until it reports DONE, then fetch the result in the following cell.
print(job.status())
DONE
import numpy as np

# Run this only once the preceding status cell reports DONE. result() blocks until
# the job finishes, so calling it earlier just waits.
result = job.result()
ev = np.array(result["expectation_values"])

print("backend:", result["metadata"]["execution_backend"])
print("shape:", ev.shape, "-> (n_times, n_observables)")
print("last row (t = t_steps * dt):", np.round(ev[-1], 4))
print(
"AQC fidelities:",
{k: round(v, 4) for k, v in result["metadata"]["aqc_fidelities"].items()},
)

# What the compression bought: 2-qubit depth at the final time step.
stats = result["metadata"]["circuit_stats"][
str(result["metadata"]["t_steps"])
]
print(
"2q depth at the final step:",
stats["full_trotter"]["depth_2q"],
"(full Trotter) ->",
stats["aqc_trotter"]["depth_2q"],
"(AQC + Trotter)",
)
backend: runtime
shape: (11, 10) -> (n_times, n_observables)
last row (t = t_steps * dt): [0.1504 0.1361 0.218 0.2144 0.2275 0.1783 0.1749 0.1599 0.0915 0.0922]
AQC fidelities: {'1': 1.0, '2': 1.0, '3': 1.0, '4': 1.0, '5': 0.9999, '6': 0.9999}
2q depth at the final step: 342 (full Trotter) -> 171 (AQC + Trotter)

Kolejne kroki​

Zalecenia