Skip to main content
IBM Quantum Platform

Implementare ed eseguire un modello di funzione Qiskit per la dinamica dell'Hamiltoniano AQC + Trotter


Panoramica

Questo è un modello di funzione Qiskit indipendente dall'esperimento per la dinamica hamiltoniana. Dato un Hamiltoniano di Pauli dei vicini più prossimi di tipo “ 1D ”, uno stato iniziale preparato (facoltativo) e un insieme di osservabili, l’algoritmo esegue l’evoluzione temporale di Trotter, la compressione dei circuiti tramite compilazione quantistica approssimativa (AQC) e l’esecuzione mitigata, per poi restituire le serie temporali di ciascun osservabile. Se si scambiano la fase di preparazione (PRE) e quella di analisi (POST), lo stesso nucleo dà origine a un esperimento diverso:

PRE (la tua configurazione)
FUNZIONE (implementata qui)
PUBBLICA (la tua analisi)
Preparare uno stato, come circuito o stato di prodotto, con un kick locale opzionaleSintesi Trotter → compressione AQC → esecuzione su statevector, fake, o runtime, con restituzione di un ⟨O⟩(t)\langle O \rangle(t)S(q,ω)S(q, \omega) per la diffusione dei neutroni, oppure per la magnetizzazione, il trasporto, la dinamica di quench e così via

Il modello è pubblicato nel repository dei modelli di funzione di Qiskit, insieme agli altri modelli di applicazione. Questo notebook lo distribuisce sul tuo account Qiskit Serverless. Eseguila una volta e qualsiasi notebook potrà poi richiamare la funzione con serverless.load("aqc-dynamics-function").

Per un esempio scientifico concreto, si veda “Simulazione della diffusione dei neutroni con un flusso di lavoro serverless basato su AQC e dinamica di Trotter”, che richiama questa funzione per calcolare il fattore di struttura dinamico di KCuF3_3. Questo notebook tratta invece l’implementazione e il contratto di input.


Requisiti

Prima di iniziare, assicurati che nell'ambiente del kernel di questo notebook siano presenti i seguenti elementi:

  • Qiskit SDK v2.0 o versioni successive (pip install qiskit).
  • Il client Qiskit IBM Catalog (pip install qiskit-ibm-catalog), che distribuisce ed esegue carichi di lavoro su Qiskit Serverless.

Non è necessario installare localmente le dipendenze scientifiche della funzione stessa (qiskit-addon-aqc-tensor, cotengrust, qiskit-aer).


Scarica i file sorgente del modello

La funzione è un piccolo pacchetto Python che Qiskit Serverless viene eseguito nel cloud, quindi il suo codice sorgente deve essere presente sotto forma di file locali che vengono caricati al momento della distribuzione. Il pacchetto è pubblicato nel repository dei modelli di funzione di Qiskit.

Scarica source_files

Il file da scaricare è un unico file ZIP, il cui nome corrisponde al percorso completo della directory presente nel repository:

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

  1. Decomprimilo nella cartella in cui si trova questo notebook.
  2. Rinomina la cartella estratta, sostituendo quel nome lungo con source_files.

La tua directory di lavoro avrà quindi questo aspetto:

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

Il nome deve corrispondere esattamente source_files, perché si tratta dei file caricati nella Fase working_dir 3.

program.py è il punto di ingresso che il gateway richiama. Tutto ciò che segue è source/ dedicato all'implementazione, suddivisa per fasi: sintesi dell'Hamiltoniano e di Trotter, compressione AQC ed esecuzione. Non è necessario apportare alcuna modifica per eseguire gli esempi che seguono. Il passaggio 3 carica l'intera directory, quindi ripeti quel passaggio ogni volta che modifichi un file.


1. Autenticazione

Utilizza qiskit-ibm-catalog per effettuare l'autenticazione su QiskitServerless con la tua chiave API (token) e l'istanza " CRN ", che puoi trovare nella dashboard di IBM Quantum® Platform. Con queste credenziali è possibile avviare localmente il client serverless per caricare o eseguire la funzione selezionata:

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

Se lo desideri, puoi utilizzare save_account() per salvare le tue credenziali nel tuo ambiente locale (consulta la guida “Configurazione dell’account IBM Cloud® ” ). Si noti che questa operazione salva le credenziali nello stesso file di QiskitRuntimeService.save_account():

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

Se l'account è stato salvato, non è necessario fornire il token per l'autenticazione:

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. Dichiarare le dipendenze

Pacchetti necessari alla funzione oltre all'immagine serverless di base gestita.

Note

Il gateway installa solo i nomi presenti nella propria lista di autorizzazioni (requirements-dynamic-dependencies.txt), identificati in base al nome del pacchetto e associati alla versione consentita tramite ==. Tutto il resto deve arrivare in modo transitivo (come dipendenza di un pacchetto presente nell'elenco dei pacchetti consentiti). La sintassi [extras] viene rispettata: qiskit-addon-aqc-tensor[quimb-jax] è ciò che installa e quimb jax. cotengrust è necessario per garantire l'efficienza della memoria durante la simulazione delle reti tensoriali. qiskit-aer è indicato separatamente per il backend fake (simulazione locale con rumore).

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

3. Definire e caricare la funzione

from qiskit_ibm_catalog import QiskitFunction

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

Output:

QiskitFunction(aqc-dynamics-function)

4. Verificare che sia stato registrato

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

Output:

QiskitFunction(aqc-dynamics-function)

Riferimento alle funzioni

Questa è una breve introduzione. Ogni campo è documentato in modo esaustivo nel file README del modello AQC Dynamics : la tabella completa degli input con le relative regole di validazione, i campi di output, i backend di esecuzione e ulteriori esempi pratici. Quella che segue è la versione sintetica, sufficiente per comprendere gli esempi riportati di seguito.

Input

Ogni esecuzione corrisponde a una singola chiamata fn.run(...) . Sono obbligatori solo i primi tre campi della tabella: hamiltonian, t_steps, e aqc_segments. Tutto ciò che segue è facoltativo e, in assenza di indicazioni, si ricorre al valore predefinito indicato; pertanto, una chiamata minima prevede tre argomenti, mentre il resto della tabella descrive le funzionalità che è possibile attivare a propria discrezione. L'Hamiltoniano num_qubits determina la lunghezza della catena, quindi non è previsto alcun parametro di input separato per la dimensione.

Input
Predefinito
Descrizione
Rubrica 4
hamiltonianobbligatorio1D Hamiltoniano di Pauli del vicino più prossimo come SparsePauliOp. Le stringhe sono operatori di Pauli, quindi non vi è alcun fattore implicito pari a mezzo.
t_stepsobbligatorioTotale passi di Trotter. Si evolve in e T = t_steps * dt riporta ogni grandezza osservabile in ogni t_k = k * dt.
aqc_segmentsobbligatorioPiano di compressione: un elenco di {"n_steps": k, "ansatz_steps": m}. sum(n_steps) i passi sono compressi; il resto viene eseguito come nel metodo Trotter standard.
dt0.2Il tempo fisico è avanzato di un passo di Trotter.
initial_state`0...0>`Un individuo pronto QuantumCircuit a evolversi. Aggiungi un tocco locale a questo circuito.
observablesper sito ZQualsiasi cosa EstimatorV2 accetta come argomento observables . Un dato osservabile per ogni colonna di output.
trotter_optionsSuzuki di secondo ordine{"method": ..., "synthesis_settings": {...}}. e reps time appartengono alla funzione.
aqc_optionsVedi la descrizionemax_bond (32), cutoff (1e-8), autodiff_backend ("jax"), fidelity_target (None), optimizer_settings (L-BFGS-B, jac=True, maxiter=300).
estimator_optionsDD, piroette, TREXEstimatorV2.options, approvato così com'è. Un dizionario fornito sostituisce integralmente quelli predefiniti, anziché fondersi con essi.
transpiler_options{"optimization_level": 3}generate_preset_pass_manager argomenti chiave. backend e target vengono scartati, poiché appartengono al percorso di esecuzione.
backend"runtime""statevector", "fake", oppure "runtime".
backend_namemeno trafficatoIBM® nome del backend per runtime, oppure un backend fittizio con un nome specifico.
batches1Dividere i circuiti tra N processi di esecuzione. Un batch invia un singolo processo e non crea alcuna sessione.
parallel_simFalseDistribuisci i percorsi del simulatore locale su tutti i core disponibili con Ray. Nessun effetto su runtime.
return_circuitsFalseRestituire i circuiti logici AQC + Trotter nel risultato insieme alle serie osservabili.

Backend di esecuzione

Tutti e tre i percorsi condividono lo stesso codice e le stesse impostazioni di mitigazione. Si differenziano solo per il percorso dei circuiti.

backend
Che cos’è
Credenziali
Note
"statevector"Esatta StatevectorEstimatorSolo per account serverlessIl percorso di riferimento esatto. Nessun tempo QPU.
"fake"Simulazione locale rumorosa su un backend fittizio di QiskitSolo per account serverlessUna fedele riproduzione del percorso runtime attenuato. Esigenze qiskit-aer. Il valore predefinito è il sistema da 127 qubit fake_sherbrooke.
"runtime" (predefinito)Le misure di mitigazione adottate EstimatorV2 nei confronti di una vera QPUAccount serverless e un'istanza con accesso alla QPUbackend_name facoltativo; se omesso, viene selezionato il dispositivo meno occupato.

Entrambi i percorsi del simulatore continuano a richiamare la funzione distribuita, quindi richiedono un account Serverless salvato anche se non utilizzano tempo di elaborazione QPU. I due esempi che seguono eseguono lo stesso carico di lavoro prima statevector su, poi su runtime.

Output

job.result() restituisce un semplice dizionario:

{
    "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 e sono circuit_stats i due da leggere per primi: insieme ti dicono se la compressione è rimasta fedele all’originale e se ha effettivamente conservato la profondità. Il runtimesistema riporta resource_usage il tempo di attesa in coda separatamente dal tempo di utilizzo della QPU che ti viene addebitato. Un input non valido viene rifiutato rapidamente come un ServerlessError codice strutturato 4615.


Esempio di simulatore

Esegui prima la funzione proprio su quel backend statevector . Non richiede tempo di elaborazione da parte della QPU e verifica la distribuzione dall'inizio alla fine. Il modello qui considerato è una catena di Ising a campo trasversale a otto qubit, e observables è stato omesso in modo che la funzione misuri l’ ZZ predefinito per sito.

Il piano di compressione è l'elemento che vale la pena comprendere. Ogni segmento {"n_steps": k, "ansatz_steps": m} comprime i passi consecutivi k di Trotter in un ansatz costruito a partire da un obiettivo di Trotter a m-passi, mentre eventuali passi successivi vengono sum(n_steps) eseguiti come metodo di Trotter standard. Le fasi iniziali, caratterizzate da un basso livello di entanglement, si comprimono bene in un ansatz a singolo strato poco profondo; le fasi successive, caratterizzate da un maggiore entanglement, richiedono invece un ansatz più profondo.

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)

Output:

job ID: ee1f3793-e995-427d-81d1-5924549beb38

Segui la corsa e leggi il risultato

status() riporta sia il ciclo di vita generale del processo sia gli stati parziali relativi a ciascuna fase che la funzione pubblica durante la sua esecuzione. Le stesse fasi valgono anche per la configurazione hardware descritta più avanti in questa guida:

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

status() Valore
Fase
RUNNING: OPTIMIZING_FOR_HARDWAREpreparazione dello stato, costruzione Trotter, compressione AQC
RUNNING: WAITING_FOR_QPUin coda sulla QPU (runtime solo backend)
RUNNING: EXECUTING_QPUcircuiti in esecuzione (i simulatori locali lo segnalano direttamente)
RUNNING: POST_PROCESSINGcreazione del dizionario dei risultati

Gli stati terminali sono DONE, ERROR, e CANCELED. Questa esecuzione statevector non ha una coda QPU, quindi salta RUNNING: WAITING_FOR_QPU. Utilizza job.logs() in qualsiasi momento per visualizzare i registri relativi a ciascuna fase, compresa la fedeltà AQC raggiunta in ogni fase.

print(job.status())  # re-run until this reports DONE

Output:

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)",
)

Output:

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)

Esempio di hardware

Una chiamata di funzione viene transposta backend="runtime" ed eseguita su un processore reale IBM Quantum, con le tecniche di mitigazione degli errori integrate nella funzione: disaccoppiamento dinamico ( XY4 ), gate twirling ed estinzione degli errori di lettura twirled (TREX). backend_name seleziona il dispositivo; se non viene specificato, la funzione sceglie quello meno occupato.

Non cambia nulla riguardo al codice scientifico. Ciò che differisce dall'esempio del simulatore è la lunghezza della catena, il numero di passi di Trotter, il piano di compressione, il backend e le impostazioni di mitigazione esplicita illustrate nella sezione seguente.

Determinazione delle dimensioni del lavoro per l’hardware di controllo

estimator_options Vale la pena impostare deliberatamente questo parametro? Il "gate twirling" crea circuiti casuali distinti num_randomizations per ogni PUB, e l'intero processo, ogni PUB con tutte le sue casualità, deve rientrare nella memoria di istruzioni del sistema di controllo classico della QPU. La funzione è impostata di default su 1000 randomizzazioni, quindi un’evoluzione in 10 fasi invia 11 PUB da 1000 circuiti ciascuno: circa 11.000 istanze di circuiti in un unico processo.

Se si supera il limite previsto dal sistema di controllo, l'operazione fallisce con l'errore 6073. La sezione "Limiti per job" indica le soglie e le modalità di calcolo, tra cui la principale è quella dell' 26.8 milione di istruzioni del sistema di controllo per qubit, applicata per singolo job anziché per singolo " PUB ". Il disaccoppiamento dinamico aggiunge porte che contribuiscono a determinarlo.

Due parametri determinano le dimensioni:

  • estimator_options stabilisce il budget per le riprese. Il numero totale di tentativi è num_randomizations * shots_per_randomization, quindi è possibile scambiare le randomizzazioni con il numero di tentativi per randomizzazione, mantenere le statistiche e ridurre comunque le dimensioni del programma. La cella seguente utilizza 100 randomizzazioni da 200 shot ciascuna, per un totale di 20.000 shot per ogni osservabile e circa un decimo delle istanze di circuito che verrebbero generate con le impostazioni predefinite. Per l'elenco completo dei campi, consultare le opzioni " TwirlingOptions " e "Estimator".
  • batches suddivide i PUB in altrettanti processi di esecuzione distinti, il che è proprio la soluzione suggerita dal messaggio di errore 6073 e spiega perché la suddivisione per processo sia importante. Questa impostazione invia batches=4 circa tre PUB per ogni processo invece di undici contemporaneamente, e i processi vengono inviati insieme in un unico batch, in modo che il gruppo venga messo in coda una sola volta anziché che ogni processo venga messo in coda separatamente.

Si ricordi che un valore specificato sostituisce estimator_options completamente i valori predefiniti della funzione, anziché fondersi con essi; pertanto, il disaccoppiamento dinamico e TREX vengono ridefiniti nella cella seguente per mantenerli attivi.

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)

Output:

job ID (save this to reconnect later): 7229a8bf-9f83-4785-8dd4-489844abc2d9
Ripristinare la connessione a un processo in esecuzione da molto tempo

Un'esecuzione hardware non è veloce e, nella maggior parte dei casi, avviene in modo classico anziché sulla QPU. La compressione AQC viene eseguita all'interno della funzione prima che qualsiasi dato raggiunga la QPU, mentre la coda della QPU si trova a livello superiore rispetto a essa. Non è necessario tenere aperto questo notebook o questo kernel mentre è in esecuzione.

Copia l'ID del lavoro visualizzato nella cella precedente e salvalo. Le tre caselle successive ti consentono di riprendere la sessione in un secondo momento:

  1. Riconnettere, operazione necessaria solo in una nuova sessione del kernel: rieseguire la cella di autenticazione per ricrearla serverless, quindi ricostruire l'handle job a partire dall'ID salvato. Salta questa cella se sei ancora nella sessione in cui hai inviato la richiesta, poiché l'handle è già attivo.
  2. Verifica lo stato: ripeti l'operazione finché non viene segnalato DONE.
  3. Recupera il risultato: esegui l'operazione solo quando lo stato è DONE.

Incolla l'ID che hai salvato al posto del segnaposto nella seguente cella di ricollegamento.

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

Output:

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)",
)

Output:

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)

Passi successivi

Suggerimenti
Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.