Gate Cutting per Ridurre la Profondità del Circuit
In questo tutorial, ridurremo la profondità di un Circuit tagliando Gate distanti, evitando i gate di swap che altrimenti verrebbero introdotti dal routing.
Questi sono i passaggi che seguiremo in questo pattern Qiskit:
- Passo 1: Mappa il problema su circuit quantistici e operatori:
- Mappa l'hamiltoniano su un circuit quantistico.
- Passo 2: Ottimizza per l'hardware di destinazione [Usa l'addon di cutting]:
- Taglia il Circuit e l'osservabile.
- Trascrivi i sottosperimenti per l'hardware con il Transpiler.
- Passo 3: Esegui sull'hardware di destinazione:
- Esegui i sottosperimenti ottenuti nel Passo 2 usando la primitiva
Sampler.
- Esegui i sottosperimenti ottenuti nel Passo 2 usando la primitiva
- Passo 4: Post-elabora i risultati [Usa l'addon di cutting]:
- Combina i risultati del Passo 3 per ricostruire il valore di aspettazione dell'osservabile in questione.
Passo 1: Mappa
Crea un Circuit da eseguire sul Backend
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
from qiskit.circuit.library import efficient_su2
circuit = efficient_su2(num_qubits=4, entanglement="circular")
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)
circuit.draw("mpl", scale=0.8)

Specifica un osservabile
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])
Passo 2: Ottimizza
Specifica un Backend
Puoi fornire sia un Backend finto che un Backend hardware da Qiskit Runtime.
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
backend = FakeManilaV2()
Trascrivi il Circuit con il Transpiler, visualizza gli swap e prendi nota della profondità
Scegliamo un layout che richiede due swap per eseguire i Gate tra i Qubit 3 e 0 e altri due swap per riportare i Qubit nelle loro posizioni iniziali.
from qiskit.transpiler import generate_preset_pass_manager
pass_manager = generate_preset_pass_manager(
optimization_level=1, backend=backend, initial_layout=[0, 1, 2, 3]
)
transpiled_qc = pass_manager.run(circuit)
print(f"Transpiled circuit depth: {transpiled_qc.depth(lambda x: len(x.qubits) >= 2)}")
Transpiled circuit depth: 30
transpiled_qc.draw("mpl", scale=0.4, idle_wires=False, fold=-1)

Sostituisci i Gate distanti con TwoQubitQPDGate specificando i loro indici
cut_gates sostituirà i Gate agli indici specificati con TwoQubitQPDGate e restituirà anche una lista di istanze QPDBasis -- una per ciascuna decomposizione di Gate.
from qiskit_addon_cutting import cut_gates
# 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)

Genera i sottosperimenti da eseguire sul Backend
generate_cutting_experiments accetta un Circuit contenente istanze TwoQubitQPDGate e osservabili come PauliList.
Per simulare il valore di aspettazione del Circuit di dimensione completa, molti sottosperimenti vengono generati dalla distribuzione di quasiprobabilità congiunta dei Gate decomposti e poi eseguiti su uno o più Backend. Il numero di campioni prelevati dalla distribuzione è controllato da num_samples, e un coefficiente combinato viene fornito per ciascun campione unico. Per ulteriori informazioni su come vengono calcolati i coefficienti, consulta il materiale esplicativo.
Nota: Il parametro observables di generate_cutting_experiments è di tipo PauliList. I coefficienti dei termini dell'osservabile e le fasi vengono ignorati durante la decomposizione del problema e l'esecuzione dei sottosperimenti. Possono essere riapplicati durante la ricostruzione del valore di aspettazione.
import numpy as np
from qiskit_addon_cutting import generate_cutting_experiments
# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observable.paulis, num_samples=np.inf
)
Calcola il sovraccarico di campionamento per i tagli scelti
Qui tagliamo tre Gate CNOT, risultando in un sovraccarico di campionamento di .
Per ulteriori informazioni sul sovraccarico di campionamento causato dal circuit cutting, consulta il materiale esplicativo.
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 729.0
Dimostra che i sottosperimenti QPD saranno più superficiali dopo il taglio dei Gate distanti
Ecco un esempio di un sottosperimento scelto arbitrariamente generato dal Circuit QPD. La sua profondità è stata ridotta di oltre la metà. Molti di questi sottosperimenti probabilistici devono essere generati e valutati per ricostruire un valore di aspettazione del Circuit più profondo.
# Transpile the decomposed circuit to the same layout
transpiled_qpd_circuit = pass_manager.run(subexperiments[100])
print(
f"Original circuit depth after transpile: {transpiled_qc.depth(lambda x: len(x.qubits) >= 2)}"
)
print(
f"QPD subexperiment depth after transpile: {transpiled_qpd_circuit.depth(lambda x: len(x.qubits) >= 2)}"
)
transpiled_qpd_circuit.draw("mpl", scale=0.8, idle_wires=False, fold=-1)
Original circuit depth after transpile: 30
QPD subexperiment depth after transpile: 7

Prepara i sottosperimenti per il Backend
# Transpile the subeperiments to the backend's instruction set architecture (ISA)
isa_subexperiments = pass_manager.run(subexperiments)
Passo 3: Esegui
Esegui i sottosperimenti usando la primitiva Sampler di Qiskit Runtime
from qiskit_ibm_runtime import SamplerV2
# Set up the Qiskit Runtime 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()
Passo 4: Post-elabora
Ricostruisci il valore di aspettazione
Ricostruisci i valori di aspettazione per ciascun termine dell'osservabile e combinali per ricostruire il valore di aspettazione per l'osservabile originale.
from qiskit_addon_cutting import reconstruct_expectation_values
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
observable.paulis,
)
# Reconstruct final expectation value
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)
Confronta il valore di aspettazione ricostruito con il valore di aspettazione esatto del Circuit e dell'osservabile originali
from qiskit_aer.primitives import EstimatorV2
estimator = EstimatorV2()
exact_expval = estimator.run([(circuit, observable)]).result()[0].data.evs
print(f"Reconstructed expectation value: {np.real(np.round(reconstructed_expval, 8))}")
print(f"Exact expectation value: {np.round(exact_expval, 8)}")
print(f"Error in estimation: {np.real(np.round(reconstructed_expval-exact_expval, 8))}")
print(
f"Relative error in estimation: {np.real(np.round((reconstructed_expval-exact_expval) / exact_expval, 8))}"
)
Reconstructed expectation value: 0.44018555
Exact expectation value: 0.50497603
Error in estimation: -0.06479049
Relative error in estimation: -0.12830408