Přeskočit na hlavní obsah

Nasazení a spuštění šablony Qiskit Function pro Hamiltonovskou dynamiku AQC + Trotter

Přehled

Toto je šablona Qiskit Function nezávislá na experimentu pro Hamiltonovskou dynamiku. Na základě 1D Hamiltoniánu s Pauliho operátory a interakcí nejbližších sousedů, (volitelně) připraveného počátečního stavu a sady pozorovatelných spustí Trotterův časový vývoj, kompresi obvodu pomocí aproximované kvantové kompilace (AQC) a zmírněné (mitigované) spuštění, poté vrátí časovou řadu pro každou pozorovatelnou. Zaměň nastavení (PRE) a analýzu (POST) a stejné jádro pohání jiný experiment:

PRE (tvoje nastavení)FUNKCE (nasazená zde)POST (tvoje analýza)
Připrav stav jako obvod nebo produktový stav, volitelně s lokálním kopnutímTrotterova syntéza → komprese AQC → spuštění na statevector, fake nebo runtime, které vrací O(t)\langle O \rangle(t)S(q,ω)S(q, \omega) pro neutronový rozptyl, nebo magnetizace, transport, quench dynamika a podobně

Šablona je publikována v repozitáři šablon Qiskit Functions spolu s ostatními aplikačními šablonami. Tento notebook ji nasadí do tvého vlastního účtu Qiskit Serverless. Spusť ho jednou a jakýkoli notebook pak může funkci volat pomocí serverless.load("aqc-dynamics-function").

Podrobný vědecký příklad najdeš v tutoriálu Simulace neutronového rozptylu pomocí Serverless workflow s dynamikou AQC + Trotter, který volá tuto funkci k výpočtu dynamického strukturního faktoru KCuF3_3. Tento notebook se místo toho zabývá nasazením a vstupní smlouvou (input contract).

Požadavky

Než začneš, ujisti se, že máš v prostředí kernelu tohoto notebooku následující:

  • Qiskit SDK v2.0 nebo novější (pip install qiskit).

  • Klient Qiskit IBM Catalog (pip install qiskit-ibm-catalog), který nasazuje a spouští úlohy v Qiskit Serverless.

Vlastní vědecké závislosti funkce (qiskit-addon-aqc-tensor, cotengrust, qiskit-aer) není třeba instalovat lokálně.

Získání zdrojových souborů šablony

Funkce je malý balíček Pythonu, který Qiskit Serverless spouští v cloudu, takže jeho zdroj musí existovat jako lokální soubory, které se nahrávají v době nasazení. Balíček je publikován v repozitáři šablon Qiskit Function.

Stáhni source_files

Stažení je jeden zip soubor, pojmenovaný podle plné cesty adresáře v repozitáři:

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

  1. Rozbal ho do adresáře, který obsahuje tento notebook.

  2. Přejmenuj rozbalenou složku z tohoto dlouhého názvu na source_files.

Tvůj pracovní adresář pak vypadá takto:

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

Název musí být přesně source_files, protože to je working_dir, který nahrává krok 3.

program.py je vstupní bod, který volá gateway. Vše pod source/ je implementace, rozdělená podle fáze: syntéza Hamiltoniánu a Trottera, komprese AQC a spuštění. Nic z toho není třeba upravovat, aby fungovaly následující příklady. Krok 3 nahrává celý adresář, takže tento krok opakuj, kdykoli změníš soubor.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-catalog

1. Autentizace

Použij qiskit-ibm-catalog k autentizaci ke QiskitServerless pomocí svého API klíče (tokenu) a CRN (instance), které najdeš na dashboardu IBM Quantum® Platform. S těmito přihlašovacími údaji můžeš lokálně vytvořit instanci klienta serverless pro nahrání nebo spuštění vybrané funkce:

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

Volitelně můžeš použít save_account() k uložení svých přihlašovacích údajů v lokálním prostředí (viz průvodce Nastavení tvého účtu IBM Cloud®). Všimni si, že se tím zapíšou tvoje přihlašovací údaje do stejného souboru jako QiskitRuntimeService.save_account():

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

Pokud je účet uložen, není třeba zadávat token k autentizaci:

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. Deklarace závislostí

Balíčky, které funkce potřebuje nad rámec spravovaného základního serverless obrazu.

poznámka

Gateway instaluje pouze názvy ze svého povoleného seznamu (requirements-dynamic-dependencies.txt), spárované podle názvu balíčku a připnuté na povolenou verzi pomocí ==. Cokoliv jiného musí dorazit tranzitivně (jako závislost balíčku ze seznamu). Syntax [extras] je respektována: qiskit-addon-aqc-tensor[quimb-jax] je to, co instaluje quimb a jax. cotengrust je potřeba pro efektivitu paměti během simulace tenzorové sítě. qiskit-aer je uveden samostatně pro backend fake (lokální zašuměná simulace).

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

3. Definice a nahrání funkce

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. Ověření registrace

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

Referenční příručka funkce

Toto je stručný úvod. Každé pole je plně zdokumentováno v README šablony AQC Dynamics: kompletní tabulka vstupů s validačními pravidly, výstupní pole, prováděcí backendy a další podrobné příklady. Následuje krátká verze, dostatečná pro pochopení dalších příkladů.

Vstupy

Každé spuštění je jedno volání fn.run(...). Povinné jsou pouze první tři vstupy v tabulce: hamiltonian, t_steps a aqc_segments. Vše po nich je volitelné a využívá zobrazenou výchozí hodnotu, takže minimální volání jsou tři argumenty a zbytek tabulky je funkcionalita, kterou si můžeš volitelně zapnout. num_qubits Hamiltoniánu určuje délku řetězce, takže neexistuje samostatný vstup pro velikost.

VstupVýchozí hodnotaPopis
hamiltonianpovinné1D Hamiltonián s Pauliho operátory a interakcí nejbližších sousedů jako SparsePauliOp. Řetězce jsou Pauliho operátory, takže zde není implicitní faktor jedné poloviny.
t_stepspovinnéCelkový počet Trotterových kroků. Vyvíjí do T = t_steps * dt a hlásí každou pozorovatelnou v každém t_k = k * dt.
aqc_segmentspovinnéPlán komprese: seznam {"n_steps": k, "ansatz_steps": m}. Komprimuje se sum(n_steps) kroků; zbytek běží jako obyčejný Trotter.
dt0.2Fyzikální čas posunutý o jeden Trotterův krok.
initial_state|0...0>Připravený QuantumCircuit k vývoji. Jakékoli lokální kopnutí zapeč přímo do tohoto obvodu.
observablesZ na každém místěCokoli, co EstimatorV2 přijímá jako argument observables. Jedna pozorovatelná na jeden výstupní sloupec.
trotter_optionsSuzuki druhého řádu{"method": ..., "synthesis_settings": {...}}. reps a time vlastní funkce.
aqc_optionsviz popismax_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, předáno beze změny. Poskytnutý slovník nahradí výchozí hodnoty celé, místo aby se s nimi sloučil.
transpiler_options{"optimization_level": 3}Klíčové argumenty pro generate_preset_pass_manager. backend a target jsou odmítnuty, protože je vlastní prováděcí cesta.
backend"runtime""statevector", "fake" nebo "runtime".
backend_namenejméně vytíženýNázev backendu IBM® pro runtime, nebo pojmenovaný falešný backend.
batches1Rozděl obvody mezi N runtime úloh. Jedna dávka odešle jednu úlohu a nevytváří Session.
parallel_simFalseRozprostři cesty lokálního simulátoru na všechna dostupná jádra pomocí Ray. Nemá vliv na runtime.
return_circuitsFalseVrať logické obvody AQC + Trotter ve výsledku spolu s řadou pozorovatelných.

Prováděcí backendy

Všechny tři cesty sdílejí stejný kód a stejná nastavení zmírnění chyb. Liší se pouze v tom, kde obvody běží.

backendCo to jePřihlašovací údajePoznámky
"statevector"Přesný StatevectorEstimatorPouze účet ServerlessPřesná referenční cesta. Žádný čas na QPU.
"fake"Zašuměná lokální simulace na falešném backendu QiskitPouze účet ServerlessVěrná zkouška zmírněné cesty runtime. Vyžaduje qiskit-aer. Standardně používá 127-Qubit fake_sherbrooke.
"runtime" (výchozí)Zmírněný EstimatorV2 proti skutečnému QPUÚčet Serverless a instance s přístupem k QPUbackend_name volitelné; jeho vynechání vybere nejméně vytížené zařízení.

Obě simulátorové cesty stále volají nasazenou funkci, takže potřebují uložený účet Serverless, i když nevyužívají žádný čas na QPU. Následující dva příklady spouští stejnou úlohu nejprve na statevector, poté na runtime.

Výstup

job.result() vrací obyčejný slovník:

{
"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 a circuit_stats jsou dvě položky, které je vhodné číst nejprve: společně ti řeknou, zda komprese zůstala věrná a zda skutečně ušetřila hloubku. Na runtime resource_usage hlásí čekání ve frontě odděleně od času na QPU, za který je ti účtováno. Odmítnutý vstup selže rychle jako strukturovaná ServerlessError (kód 4615).

Příklad se simulátorem

Nejprve spusť funkci na přesném backendu statevector. Nespotřebuje žádný čas na QPU a ověří nasazení od konce do konce. Modelem je zde osmi-Qubitový Isingův řetězec s příčným polem a observables je vynechán, takže funkce měří výchozí ZZ na každém místě.

Plán komprese je vstup, který stojí za pochopení. Každý segment {"n_steps": k, "ansatz_steps": m} komprimuje k po sobě jdoucích Trotterových kroků do ansatzu postaveného na základě m-krokového Trotterova cíle a jakékoli kroky nad sum(n_steps) běží jako obyčejný Trotter. Rané kroky s nízkým provázáním se dobře komprimují do mělkého jednovrstvého ansatzu; pozdější, více provázané kroky potřebují hlubší ansatz.

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

Sledování běhu a čtení výsledku

status() hlásí jak hrubý životní cyklus úlohy, tak dílčí stav pro každou fázi, který funkce publikuje během běhu. Stejné fáze platí i pro spuštění na hardwaru později v této příručce:

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

hodnota status()Fáze
RUNNING: OPTIMIZING_FOR_HARDWAREpříprava stavu, sestavení Trottera, komprese AQC
RUNNING: WAITING_FOR_QPUve frontě na QPU (pouze backend runtime)
RUNNING: EXECUTING_QPUobvody se provádějí (lokální simulátory tento stav označí přímo)
RUNNING: POST_PROCESSINGsestavování výsledného slovníku

Koncové stavy jsou DONE, ERROR a CANCELED. Toto spuštění na statevector nemá žádnou frontu na QPU, takže přeskočí RUNNING: WAITING_FOR_QPU. Použij job.logs() kdykoli, abys viděl protokoly pro jednotlivé fáze, včetně věrnosti AQC dosažené v každém 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)

Příklad na hardwaru

Volání funkce s backend="runtime" transpiluje a provádí na skutečném procesoru IBM Quantum se zabudovaným zmírněním chyb funkce: dynamickým odpojením (XY4), twirlingem hradel a twirlovaným vyloučením chyb čtení (TREX). backend_name vybírá zařízení; jeho vynecháním funkce vybere nejméně vytížené.

Na vědeckém kódu se nic nemění. Od příkladu se simulátorem se liší délka řetězce, počet Trotterových kroků, plán komprese, backend a explicitní nastavení zmírnění chyb popsaná v následující sekci.

Přizpůsobení velikosti úlohy řídicímu hardwaru

estimator_options je vstup, který stojí za pečlivé nastavení. Twirling hradel vytváří num_randomizations samostatných randomizovaných obvodů pro každý PUB a celá úloha, každý PUB se všemi svými randomizacemi, se musí vejít do paměti instrukcí klasického řídicího systému QPU. Funkce má standardně 1000 randomizací, takže vývoj o 10 krocích odešle 11 PUB po 1000 obvodech: zhruba 11 000 instancí obvodů v jedné úloze.

Pokud překročíš to, co řídicí systém pojme, úloha selže s chybou 6073. Limity úloh uvádí prahové hodnoty a jak proti nim počítat, přičemž hlavní z nich je 26,8 milionu instrukcí řídicího systému na Qubit, aplikovaných na úlohu, nikoli na PUB. Dynamické odpojení přidává hradla, která se do toho počítají.

Velikost řídí dva vstupy:

  • estimator_options nastavuje rozpočet shotů. Celkový počet shotů je num_randomizations * shots_per_randomization, takže můžeš vyměnit randomizace za shoty na randomizaci, zachovat statistiku a přesto zmenšit program. Následující buňka používá 100 randomizací po 200 shotech, což je 20 000 shotů na pozorovatelnou a zhruba desetina instancí obvodů, které by odeslaly výchozí hodnoty. Kompletní sadu polí najdeš v TwirlingOptions a možnostech Estimatoru.

  • batches rozdělí PUB mezi tolik samostatných runtime úloh, což je náprava, kterou navrhuje samotná chyba 6073, a proto záleží na rámci na úroveň úlohy. Nastavení batches=4 pošle zhruba tři PUB na úlohu místo jedenácti najednou a úlohy jdou ven společně v jedné dávce, takže se skupina zařadí do fronty jednou, místo aby se každá úloha řadila do fronty zvlášť.

Nezapomeň, že poskytnutý estimator_options nahradí výchozí hodnoty funkce celé, místo aby se s nimi sloučil, takže dynamické odpojení a TREX jsou v následující buňce znovu uvedeny, aby zůstaly zapnuté.

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
Opětovné připojení k dlouho běžící úloze

Spuštění na hardwaru není rychlé a většina času je klasická, nikoli na QPU. Komprese AQC běží uvnitř funkce dřív, než cokoli dorazí na QPU, a fronta na QPU je navíc k tomu. Tento notebook ani kernel nemusíš mít během běhu otevřený.

Zkopíruj ID úlohy vypsané předchozí buňkou a ulož si ho. Následující tři buňky ti umožní se k výsledku vrátit později:

  1. Opětovné připojení, potřeba jen v nové Session kernelu: znovu spusť buňku Autentizace, abys znovu vytvořil serverless, poté znovu sestav handle job z uloženého ID. Tuto buňku přeskoč, pokud jsi stále ve Session, kde jsi úlohu odeslal, protože handle je již aktivní.

  2. Zkontroluj stav: spouštěj opakovaně, dokud nehlásí DONE.

  3. Získej výsledek: spusť pouze poté, co je stav DONE.

Vlož své uložené ID přes zástupný text v následující buňce pro opětovné připojení.

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

Další kroky

Doporučení