Coupe de circuits pour la reduction de profondeur
Estimation d'utilisation : huit minutes sur un processeur Eagle (REMARQUE : il s'agit uniquement d'une estimation. Votre temps d'execution peut varier.)
Contexte
Ce tutoriel montre comment construire un Qiskit pattern pour couper des portes dans un circuit quantique afin de reduire la profondeur du circuit. Pour une discussion plus approfondie sur la coupe de circuits, consultez la documentation de l'addon Qiskit pour la coupe de circuits.
Prerequis
Avant de commencer ce tutoriel, assurez-vous d'avoir installe les elements suivants :
- Qiskit SDK v2.0 ou ulterieur, avec prise en charge de la visualisation
- Qiskit Runtime v0.22 ou ulterieur (
pip install qiskit-ibm-runtime) - Addon Qiskit pour la coupe de circuits v0.9.0 ou ulterieur (
pip install qiskit-addon-cutting)
Configuration
# Added by doQumentation — installs packages not in the Binder environment
%pip install -q qiskit-addon-cutting
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, SamplerV2
Etape 1 : Convertir les entrees classiques en un probleme quantique
Nous allons implementer notre patron Qiskit en suivant les quatre etapes decrites dans la documentation. Dans ce cas, nous allons simuler des valeurs d'esperance sur un circuit d'une certaine profondeur en coupant des portes generant des portes swap et en executant des sous-experiences sur des circuits moins profonds. La coupe de portes est pertinente pour les etapes 2 (optimiser le circuit pour l'execution quantique en decomposant les portes distantes) et 4 (post-traitement pour reconstruire les valeurs d'esperance sur le circuit original). Dans la premiere etape, nous allons generer un circuit a partir de la bibliotheque de circuits Qiskit et definir quelques observables.
- Entree : Parametres classiques pour definir un circuit
- Sortie : Circuit abstrait et observables
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")
Etape 2 : Optimiser le probleme pour l'execution sur du materiel quantique
- Entree : Circuit abstrait et observables
- Sortie : Circuit cible et observables produits en coupant les portes distantes pour reduire la profondeur du circuit transpile
Nous choisissons une disposition initiale qui necessite deux permutations (swaps) pour executer les portes entre les qubits 3 et 0 et deux autres permutations pour ramener les qubits a leurs positions initiales. Nous choisissons optimization_level=3, qui est le niveau d'optimisation le plus eleve disponible avec un gestionnaire de passes predefini.
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)
Transpiled circuit depth: 103
Trouver et couper les portes distantes : Nous allons remplacer les portes distantes (portes connectant des qubits non locaux, 0 et 3) par des objets TwoQubitQPDGate en specifiant leurs indices. cut_gates remplacera les portes aux indices specifies par des objets TwoQubitQPDGate et retournera egalement une liste d'instances QPDBasis -- une pour chaque decomposition de porte. L'objet QPDBasis contient des informations sur la facon de decomposer les portes coupees en operations a un seul 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)
Generer les sous-experiences a executer 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'esperance du circuit complet, de nombreuses sous-experiences sont generees a partir de la distribution de quasi-probabilite conjointe des portes decomposees, puis executees sur un ou plusieurs backends. Le nombre d'echantillons preleves dans la distribution est controle par num_samples, et un coefficient combine est attribue pour chaque echantillon unique. Pour plus d'informations sur le calcul des coefficients, consultez la documentation explicative.
# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observables, num_samples=np.inf
)
A titre de comparaison, nous constatons que les sous-experiences QPD seront moins profondes apres la coupe des portes distantes : Voici un exemple d'une sous-experience choisie arbitrairement, generee a partir du circuit QPD. Sa profondeur a ete reduite de plus de la moitie. De nombreuses sous-experiences probabilistes de ce type doivent etre generees et evaluees afin de reconstruire une valeur d'esperance du circuit plus profond.
# 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
)
Original circuit depth after transpile: 103
QPD subexperiment depth after transpile: 46
En revanche, la coupe entraine un besoin d'echantillonnage supplementaire. Ici, nous coupons trois portes CNOT, ce qui resulte en un surcout d'echantillonnage de . Pour en savoir plus sur le surcout d'echantillonnage engendre par la coupe de circuits, consultez la documentation du Circuit Knitting Toolbox.
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 729.0
Etape 3 : Executer a l'aide des primitives Qiskit
Executez les circuits cibles (les "sous-experiences") avec la primitive Sampler.
- Entree : Circuits cibles
- Sortie : Distributions de quasi-probabilites
# Transpile the subexperiments to the backend's instruction set architecture (ISA)
isa_subexperiments = pm.run(subexperiments)
# 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()
print(job.job_id())
czypg1r6rr3g008mgp6g
Etape 4 : Post-traitement et restitution du resultat dans le format classique souhaite
Utilisez les resultats des sous-experiences, les sous-observables et les coefficients d'echantillonnage pour reconstruire la valeur d'esperance du circuit original.
Entree : Distributions de quasi-probabilites Sortie : Valeurs d'esperance reconstruites
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)
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)
Ideal expectation value
1.2283177520039992
Enquete sur le tutoriel
Veuillez repondre a cette courte enquete pour nous faire part de vos commentaires sur ce tutoriel. Vos retours nous aideront a ameliorer notre contenu et l'experience utilisateur.