Taglio del circuito per la riduzione della profondità
Stima di utilizzo: Otto minuti con un processore Eagle (NOTA: questa è solo una stima). Il tempo di esecuzione potrebbe variare)
Sfondo
Questa esercitazione mostra come costruire un Qiskit pattern per tagliare le porte di un circuito quantistico per ridurre la profondità del circuito. Per una discussione più approfondita sul taglio dei circuiti, visita la documentazione del componente aggiuntivo Qiskit sul taglio dei circuiti.
Requisiti
Prima di iniziare questa esercitazione, assicuratevi di aver installato quanto segue:
- Qiskit SDK v2.0 o versioni successive, con supporto alla visualizzazione
- Qiskit Runtime v0.22 o successivamente (
pip install qiskit-ibm-runtime) - Taglio dei circuiti Add-on Qiskit v0.9.0 e o successive (
pip install qiskit-addon-cutting)
Configura
import numpy as np
from qiskit.circuit.library import EfficientSU2
from qiskit.quantum_info import PauliList, Statevector, SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2Fase 1: mappare gli input classici su un problema quantistico
Implementeremo il nostro modello Qiskit utilizzando i quattro passaggi descritti nella documentazione. In questo caso, simuleremo i valori di aspettativa su un circuito di una certa profondità tagliando le porte che risultano in porte di scambio ed eseguendo sottoesperimenti su circuiti meno profondi. Il taglio dei gate è rilevante per le fasi 2 (ottimizzazione del circuito per l'esecuzione quantistica mediante la scomposizione di gate distanti) e 4 (post-elaborazione per ricostruire i valori di aspettativa sul circuito originale). Nel primo passo, genereremo un circuito dalla libreria di circuiti Qiskit e definiremo alcuni osservabili.
- Ingresso: Parametri classici per definire un circuito
- Uscita: Circuito astratto e osservabili
circuit = EfficientSU2(num_qubits=4, entanglement="circular").decompose()
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)
observables = PauliList(["ZZII", "IZZI", "IIZZ", "XIXI", "ZIZZ", "IXIX"])
circuit.draw("mpl", scale=0.8, style="iqp")Output:
Fase 2: Ottimizzazione del problema per l'esecuzione su hardware quantistico
- Ingresso: Circuito astratto e osservabili
- Output: Circuito target e osservabili prodotti dal taglio di porte distanti per ridurre la profondità del circuito transpilato
Abbiamo scelto un layout iniziale che richiede due scambi per eseguire i gate tra i qubit 3 e 0 e altri due scambi per riportare i qubit alle loro posizioni iniziali. Abbiamo scelto optimization_level=3, che è il massimo livello di ottimizzazione disponibile con un gestore di passaggi preimpostato.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, min_num_qubits=circuit.num_qubits, simulator=False
)
pm = generate_preset_pass_manager(
optimization_level=3, initial_layout=[0, 1, 2, 3], backend=backend
)
transpiled_qc = pm.run(circuit)
print(f"Transpiled circuit depth: {transpiled_qc.depth()}")
transpiled_qc.draw("mpl", scale=0.4, idle_wires=False, style="iqp", fold=-1)Output:
Transpiled circuit depth: 103
Trova e taglia i gate distanti: sostituiremo i gate distanti (gate che collegano qubit non locali, 0 e 3) con TwoQubitQPDGate oggetti specificandone gli indici. cut_gates sostituirà i gate negli indici specificati con TwoQubitQPDGate oggetti e restituirà anche un elenco di QPDBasis istanze, una per ogni decomposizione del gate. L'oggetto QPDBasis contiene informazioni su come scomporre i gate tagliati in operazioni a singolo qubit.
# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(circuit.data)
if {circuit.find_bit(q)[0] for q in instruction.qubits} == {0, 3}
]
# Decompose distant CNOTs into TwoQubitQPDGate instances
qpd_circuit, bases = cut_gates(circuit, cut_indices)
qpd_circuit.draw("mpl", scale=0.8)Output:
Generare i sottoesperimenti da eseguire sul backend : generate_cutting_experiments accetta un circuito contenente istanze e osservabili di TwoQubitQPDGate come PauliList.
Per simulare il valore di aspettativa del circuito completo, vengono generati molti sottoesperimenti dalla distribuzione congiunta di quasiprobabilità delle porte scomposte e poi eseguiti su uno o più backend. Il numero di campioni prelevati dalla distribuzione è controllato da num_samples, e per ogni campione unico viene fornito un coefficiente combinato. Per maggiori informazioni sulle modalità di calcolo dei coefficienti, consultare il materiale esplicativo.
# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observables, num_samples=np.inf
)A titolo di confronto, vediamo che i sottoesempi QPD saranno meno profondi dopo il taglio di porte distanti : Ecco un esempio di un sottoesperimento scelto arbitrariamente e generato dal circuito QPD. La sua profondità è stata ridotta di oltre la metà. Molti di questi sottoesperimenti probabilistici devono essere generati e valutati per ricostruire un valore di aspettativa del circuito più profondo.
# Transpile the decomposed circuit to the same layout
transpiled_qpd_circuit = pm.run(subexperiments[100])
print(f"Original circuit depth after transpile: {transpiled_qc.depth()}")
print(
f"QPD subexperiment depth after transpile: {transpiled_qpd_circuit.depth()}"
)
transpiled_qpd_circuit.draw(
"mpl", scale=0.6, style="iqp", idle_wires=False, fold=-1
)Output:
Original circuit depth after transpile: 103
QPD subexperiment depth after transpile: 46
D'altra parte, il taglio comporta la necessità di un campionamento supplementare. Qui tagliamo tre porte CNOT, con un overhead di campionamento pari a . Per maggiori informazioni sull'overhead di campionamento generato dal taglio del circuito, consultare la documentazione di Circuit Knitting Toolbox.
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")Output:
Sampling overhead: 729.0
Passaggio 3: eseguire utilizzando Qiskit primitives
Eseguire i circuiti di destinazione ("sottoesperimenti") utilizzando la primitiva Sampler.
- Ingresso: Circuiti target
- Output: Distribuzioni di quasi-probabilità
# Transpile the subexperiments to the backend's instruction set architecture (ISA)
isa_subexperiments = pm.run(subexperiments)
# Set up the IBM Quantum Sampler primitive. For a fake backend, this will use a local simulator.
sampler = SamplerV2(backend)
# Submit the subexperiments
job = sampler.run(isa_subexperiments)# Retrieve the results
results = job.result()print(job.job_id())Output:
czypg1r6rr3g008mgp6g
Fase 4: Post-elaborazione e restituzione del risultato nel formato classico desiderato
Utilizzare i risultati del sottoesperimento, le sottoosservabili e i coefficienti di campionamento per ricostruire il valore di aspettativa del circuito originale.
Input: Distribuzioni di quasi-probabilità Output: Valori di aspettativa ricostruiti
reconstructed_expvals = reconstruct_expectation_values(
results,
coefficients,
observables,
)
# Reconstruct final expectation value
final_expval = np.dot(reconstructed_expvals, [1] * len(observables))
print("Final reconstructed expectation value")
print(final_expval)Output:
Final reconstructed expectation value
1.0751342773437473
ideal_expvals = [
Statevector(circuit).expectation_value(SparsePauliOp(observable))
for observable in observables
]
print("Ideal expectation value")
print(np.dot(ideal_expvals, [1] * len(observables)).real)Output:
Ideal expectation value
1.2283177520039992
Sondaggio tutorial
Vi invitiamo a partecipare a questo breve sondaggio per fornire un feedback su questa esercitazione. Le vostre indicazioni ci aiuteranno a migliorare la nostra offerta di contenuti e l'esperienza degli utenti.