Skip to main content
IBM Quantum Platform

Découpe de circuits pour réduire la profondeur

Estimation de l'utilisation : Huit minutes sur un processeur Eagle (NOTE : Il s'agit uniquement d'une estimation. Votre durée d'exécution peut varier.)


Arrière-plan

Ce tutoriel montre comment construire un Qiskit pattern pour couper les portes d'un circuit quantique afin de réduire la profondeur du circuit. Pour une discussion plus approfondie sur le découpage des circuits, consultez la documentation du module complémentaire Qiskit sur le découpage des circuits.


Exigences

Avant de commencer ce tutoriel, assurez-vous que les éléments suivants sont installés :

  • Qiskit SDK v2.0 ou plus tard, avec prise en charge de la visualisation
  • Qiskit Runtime v0.22 ou plus tard (pip install qiskit-ibm-runtime)
  • Add-on Qiskit pour la découpe de circuits v0.9.0 ou version ultérieure (pip install qiskit-addon-cutting)

Configuration

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

Étape 1 : Mettre en correspondance les entrées classiques avec un problème quantique

Nous allons mettre en œuvre notre modèle Qiskit en suivant les quatre étapes décrites dans la documentation. Dans ce cas, nous simulerons les valeurs d'espérance sur un circuit d'une certaine profondeur en coupant des portes résultant en des portes d'échange et en exécutant des sous-expériences sur des circuits moins profonds. Le découpage de portes est pertinent pour les étapes 2 (optimiser le circuit pour l'exécution quantique en décomposant les portes éloignées) et 4 (post-traitement pour reconstruire les valeurs d'espérance sur le circuit d'origine). Dans un premier temps, nous allons générer un circuit à partir de la bibliothèque de circuits Qiskit et définir quelques observables.

  • Entrée : Paramètres classiques pour définir 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")

Output:

Output of the previous code cell

Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique

  • Entrée : Circuit abstrait et observables
  • Résultat : Circuit cible et observables produits en coupant les portes éloignées pour réduire la profondeur du circuit transpilé

Nous choisissons une disposition initiale qui nécessite deux permutations pour exécuter les portes entre les qubits 3 et 0 et deux autres permutations pour ramener les qubits à leur position initiale. Nous choisissons optimization_level=3, qui est le niveau d'optimisation le plus élevé disponible avec un gestionnaire de passe prédéfini.

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)
Carte de couplage montrant les qubits qui devront être échangés
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
Output of the previous code cell

Rechercher et couper les portes distantes : nous remplacerons les portes distantes (portes reliant les qubits non locaux, 0 et 3) par TwoQubitQPDGate des objets en spécifiant leurs indices. cut_gates remplacera les portes dans les indices spécifiés par TwoQubitQPDGate des objets et renverra également une liste QPDBasis d'instances, une pour chaque décomposition de porte. L'objet QPDBasis contient des informations sur la manière de décomposer les portes coupées en opérations à 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)

Output:

Output of the previous code cell

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 en tant que 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é commune des portes décomposées, puis exécutées sur un ou plusieurs backends. Le nombre d'échantillons prélevés dans la distribution est contrôlé par num_samples, et un coefficient combiné est donné pour chaque échantillon unique. Pour plus d'informations sur le calcul des coefficients, se référer au matériel explicatif.

# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
    circuits=qpd_circuit, observables=observables, num_samples=np.inf
)

À titre de comparaison, nous constatons que les sous-expériences QPD seront moins profondes après avoir coupé les portes éloignées : Voici un exemple de sous-expérience choisie arbitrairement et générée à partir du circuit QPD. Sa profondeur a été réduite de plus de la moitié. Un grand nombre de ces sous-expériences probabilistes doivent être générées et évaluées afin de reconstruire une valeur d'espérance du circuit 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
)

Output:

Original circuit depth after transpile: 103
QPD subexperiment depth after transpile: 46
Output of the previous code cell

D' autre part, la coupe entraîne la nécessité d'un échantillonnage supplémentaire. Ici, nous coupons trois portes CNOT, ce qui entraîne une surcharge d'échantillonnage de 939^3. Pour en savoir plus sur la surcharge d'échantillonnage induite par le découpage des circuits, reportez-vous à la documentation de la boîte à outils pour le tricotage de circuits.

print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")

Output:

Sampling overhead: 729.0

Étape 3 : Exécutez à l'aide d' Qiskit primitives

Exécutez les circuits cibles (« sous-expériences ») à l'aide de la primitive Sampler.

  • Entrée : Circuits cibles
  • Sortie : Distributions de 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

Étape 4 : Post-traitement et restitution du résultat dans le format classique souhaité

Utiliser les résultats des sous-expériences, les sous-observables et les coefficients d'échantillonnage pour reconstruire la valeur d'espérance du circuit original.

Entrée : Distributions de quasi-probabilité Sortie : Valeurs d'espérance 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)

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

Enquête tutorielle

Veuillez répondre à cette courte enquête pour nous faire part de vos commentaires sur ce didacticiel. Vos commentaires nous aideront à améliorer notre offre de contenu et l'expérience des utilisateurs.

Lien vers l'enquête

Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.