Découpe de Gates pour Réduire la Profondeur du Circuit
Dans ce tutoriel, tu vas réduire la profondeur d'un Circuit en découpant des Gates distantes, ce qui permet d'éviter les gates de swap qui seraient sinon introduites par le routage.
Voici les étapes que nous allons suivre dans ce patron Qiskit :
- Étape 1 : Mapper le problème sur des circuits quantiques et des opérateurs :
- Mapper le hamiltonien sur un Circuit quantique.
- Étape 2 : Optimiser pour le matériel cible [Utilise l'addon de découpe] :
- Découper le Circuit et l'observable.
- Transpiler les sous-expériences pour le matériel.
- Étape 3 : Exécuter sur le matériel cible :
- Exécuter les sous-expériences obtenues à l'étape 2 à l'aide d'une primitive
Sampler.
- Exécuter les sous-expériences obtenues à l'étape 2 à l'aide d'une primitive
- Étape 4 : Post-traiter les résultats [Utilise l'addon de découpe] :
- Combiner les résultats de l'étape 3 pour reconstruire la valeur d'espérance de l'observable en question.
Étape 1 : Mapper
Créer un Circuit à exécuter sur le 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)

Spécifier un observable
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])
Étape 2 : Optimiser
Spécifier un Backend
Tu peux fournir soit un faux Backend, soit un Backend matériel provenant de Qiskit Runtime.
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
backend = FakeManilaV2()
Transpiler le Circuit, visualiser les swaps et noter la profondeur
Nous choisissons un agencement qui nécessite deux swaps pour exécuter les Gates entre les Qubits 3 et 0, et deux autres swaps pour ramener les Qubits à leurs positions initiales.
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)

Remplacer les Gates distantes par des TwoQubitQPDGates en spécifiant leurs indices
cut_gates remplacera les Gates aux indices spécifiés par des TwoQubitQPDGates et retournera également une liste d'instances QPDBasis — une pour chaque décomposition de 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)

Générer les sous-expériences à exécuter sur le Backend
generate_cutting_experiments accepte un Circuit contenant des instances TwoQubitQPDGate et des observables sous forme de PauliList.
Pour simuler la valeur d'espérance du Circuit complet, de nombreuses sous-expériences sont générées à partir de la distribution de quasiprobabilité conjointe des Gates décomposées, puis exécutées sur un ou plusieurs Backends. Le nombre d'échantillons prélevés depuis la distribution est contrôlé par num_samples, et un coefficient combiné est donné pour chaque échantillon unique. Pour plus d'informations sur la façon dont les coefficients sont calculés, consulte le matériel explicatif.
Remarque : L'argument observables de generate_cutting_experiments est de type PauliList. Les coefficients et phases des termes d'observable sont ignorés lors de la décomposition du problème et de l'exécution des sous-expériences. Ils peuvent être réappliqués lors de la reconstruction de la valeur d'espérance.
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
)
Calculer la surcharge d'échantillonnage pour les découpes choisies
Ici, nous découpons trois Gates CNOT, ce qui entraîne une surcharge d'échantillonnage de .
Pour en savoir plus sur la surcharge d'échantillonnage induite par la découpe de circuits, consulte le matériel explicatif.
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 729.0
Montrer que les sous-expériences QPD seront plus légères après la découpe des Gates distantes
Voici un exemple d'une sous-expérience choisie arbitrairement, générée à partir du Circuit QPD. Sa profondeur a été réduite de plus de moitié. De nombreuses sous-expériences probabilistes de ce type doivent être générées et évaluées afin de reconstruire une valeur d'espérance du Circuit plus profond.
# 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

Préparer les sous-expériences pour le Backend
# Transpile the subeperiments to the backend's instruction set architecture (ISA)
isa_subexperiments = pass_manager.run(subexperiments)
Étape 3 : Exécuter
Exécuter les sous-expériences à l'aide de la primitive Qiskit Runtime Sampler
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()
Étape 4 : Post-traitement
Reconstruire la valeur d'espérance
Reconstruire les valeurs d'espérance pour chaque terme observable et les combiner pour reconstruire la valeur d'espérance de l'observable original.
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)
Comparer la valeur d'espérance reconstruite avec la valeur d'espérance exacte du Circuit et de l'observable d'origine
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