Gate Cutting per Ridurre la Larghezza del Circuit
In questo notebook, seguiremo i passi di un pattern Qiskit utilizzando il circuit cutting per ridurre il numero di Qubit in un Circuit. Taglieremo i Gate per poter ricostruire il valore di aspettazione di un Circuit a quattro Qubit usando solo esperimenti a due Qubit.
Questi sono i passi che seguiremo:
- 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 [Utilizza il cutting addon]:
- Taglia il Circuit e l'osservabile.
- Esegui il transpile dei sottoesperimenti per l'hardware.
- Passo 3: Esegui sull'hardware di destinazione:
- Esegui i sottoesperimenti ottenuti nel Passo 2 usando la primitiva
Sampler.
- Esegui i sottoesperimenti ottenuti nel Passo 2 usando la primitiva
- Passo 4: Post-elabora i risultati [Utilizza il cutting addon]:
- Combina i risultati del Passo 3 per ricostruire il valore di aspettazione dell'osservabile in questione.
Passo 1: Mappa​
Crea un Circuit da tagliare​
# 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
qc = efficient_su2(4, entanglement="linear", reps=2)
qc.assign_parameters([0.4] * len(qc.parameters), inplace=True)
qc.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​
Separa il Circuit e l'osservabile secondo una partizione di Qubit specificata​
Ogni etichetta in partition_labels corrisponde al Qubit del circuit con lo stesso indice. I Qubit che condividono un'etichetta di partizione comune verranno raggruppati insieme, e i Gate non locali che si estendono su più di una partizione verranno tagliati.
Nota: L'argomento observables di partition_problem è di tipo PauliList. I coefficienti e le fasi dei termini dell'osservabile vengono ignorati durante la decomposizione del problema e l'esecuzione dei sottoesperimenti. Possono essere riapplicati durante la ricostruzione del valore di aspettazione.
from qiskit_addon_cutting import partition_problem
partitioned_problem = partition_problem(
circuit=qc, partition_labels="AABB", observables=observable.paulis
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases
Visualizza il problema decomposto​
subobservables
{'A': PauliList(['II', 'ZI', 'ZZ', 'XI', 'ZZ', 'IX']),
'B': PauliList(['ZZ', 'IZ', 'II', 'XI', 'ZI', 'IX'])}
subcircuits["A"].draw("mpl", scale=0.8)

subcircuits["B"].draw("mpl", scale=0.8)

Calcola l'overhead di campionamento per i tagli scelti​
Qui tagliamo due Gate CNOT, risultando in un overhead di campionamento di .
Per ulteriori informazioni sull'overhead di campionamento derivante dal circuit cutting, consulta il materiale esplicativo.
import numpy as np
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 81.0
Genera i sottoesperimenti da eseguire sul Backend​
generate_cutting_experiments accetta gli argomenti circuits/observables come dizionari che mappano le etichette di partizione dei Qubit ai rispettivi subcircuit/subobservables.
Per simulare il valore di aspettazione del Circuit di dimensione completa, molti sottoesperimenti 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 per ogni campione univoco viene fornito un coefficiente combinato. Per ulteriori informazioni su come vengono calcolati i coefficienti, consulta il materiale esplicativo.
from qiskit_addon_cutting import generate_cutting_experiments
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits, observables=subobservables, num_samples=np.inf
)
Scegli un Backend​
Qui utilizziamo un Backend fittizio, che farà sì che Qiskit Runtime venga eseguito in modalità locale (ovvero su un simulatore locale).
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
backend = FakeManilaV2()
Prepara i sottoesperimenti per il Backend​
Dobbiamo eseguire il transpile dei Circuit con il nostro Backend come destinazione prima di inviarli a Qiskit Runtime.
from qiskit.transpiler import generate_preset_pass_manager
# Transpile the subexperiments to ISA circuits
pass_manager = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_subexperiments = {
label: pass_manager.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
Passo 3: Esegui​
Esegui i sottoesperimenti usando la primitiva Sampler di Qiskit Runtime​
from qiskit_ibm_runtime import SamplerV2, Batch
# Submit each partition's subexperiments to the Qiskit Runtime Sampler
# primitive, in a single batch so that the jobs will run back-to-back.
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}
/home/garrison/Qiskit/qiskit-ibm-runtime/qiskit_ibm_runtime/session.py:157: UserWarning: Session is not supported in local testing mode or when using a simulator.
warnings.warn(
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
Passo 4: Post-elabora​
Ricostruisci il valore di aspettazione​
Ricostruisci i valori di aspettazione per ogni termine dell'osservabile e combinali per ricostruire il valore di aspettazione dell'osservabile originale.
from qiskit_addon_cutting import reconstruct_expectation_values
# Get expectation values for each observable term
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
# 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([(qc, 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.6991539
Exact expectation value: 0.56254612
Error in estimation: 0.13660778
Relative error in estimation: 0.24283836