Découpe de circuits pour les conditions aux limites périodiques
Estimation d'utilisation : deux minutes sur un processeur Eagle (REMARQUE : il s'agit uniquement d'une estimation. Votre temps d'exécution peut varier.)
Contexte
Dans ce notebook, nous considérons la simulation d'une chaîne périodique de qubits où une opération à deux qubits est appliquée entre chaque paire de qubits adjacents, y compris entre le premier et le dernier. Les chaînes périodiques se retrouvent fréquemment dans les problèmes de physique et de chimie tels que les modèles d'Ising et la simulation moléculaire.
Les dispositifs IBM Quantum® actuels sont planaires. Il est possible d'intégrer certaines chaînes périodiques directement sur la topologie lorsque les premier et dernier qubits sont voisins. Cependant, pour des problèmes suffisamment grands, les premier et dernier qubits peuvent être éloignés, nécessitant ainsi de nombreuses portes SWAP pour l'opération à 2 qubits entre ces deux qubits. Ce type de problème à conditions aux limites périodiques a été étudié dans cet article.
Dans ce notebook, nous montrons l'utilisation de la découpe de circuits pour traiter un problème de chaîne périodique à grande échelle où les premier et dernier qubits ne sont pas voisins. La découpe de cette connectivité longue distance évite les portes SWAP supplémentaires au prix de l'exécution de plusieurs instances du circuit, ainsi que d'un post-traitement classique. En résumé, la découpe peut être intégrée pour calculer logiquement les opérations à 2 qubits longue distance. Autrement dit, cette approche conduit à une augmentation effective de la connectivité de la carte de couplage, entraînant ainsi un nombre réduit de portes SWAP.
Notez qu'il existe deux types de découpes : couper le fil d'un circuit (appelé wire cutting), ou remplacer une porte à 2 qubits par plusieurs opérations à un seul qubit (appelé gate cutting). Dans ce notebook, nous nous concentrerons sur le gate cutting. Pour plus de détails sur le gate cutting, consultez les documents explicatifs de qiskit-addon-cutting, ainsi que les références correspondantes. Pour plus de détails sur le wire cutting, consultez le tutoriel Découpe de fils pour l'estimation des valeurs d'espérance, ou les tutoriels de qiskit-addon-cutting.
Prérequis
Avant de commencer ce tutoriel, assurez-vous d'avoir installé les éléments suivants :
- Qiskit SDK v1.2 ou ultérieur (
pip install qiskit) - Qiskit Runtime v0.3 ou ultérieur (
pip install qiskit-ibm-runtime) - Addon de découpe de circuits Qiskit v.9.0 ou ultérieur (
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
import matplotlib.pyplot as plt
import matplotlib as mpl
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
BasisTranslator,
Optimize1qGatesDecomposition,
)
from qiskit.circuit.equivalence_library import (
SessionEquivalenceLibrary as sel,
)
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.result import sampled_expectation_value
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import TwoLocal
from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, SamplerOptions, Batch
Étape 1 : Mapper les entrées classiques vers un problème quantique
Ici, nous allons générer un circuit TwoLocal et définir quelques observables.
- Entrée : Paramètres pour créer un circuit
- Sortie : Circuit abstrait et observables
Nous considérons une carte d'intrication à efficacité matérielle pour le circuit TwoLocal avec une connectivité périodique entre les dernier et premier qubits de la carte d'intrication. Cette interaction longue distance peut entraîner des portes SWAP supplémentaires lors de la transpilation, augmentant ainsi la profondeur du circuit.
Sélection du backend et du placement initial
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
Pour ce notebook, nous considérons une chaîne 1D périodique de 109 qubits, qui est la plus longue chaîne 1D dans la topologie d'un dispositif IBM Quantum à 127 qubits. Il n'est pas possible de disposer une chaîne périodique de 109 qubits sur un dispositif de 127 qubits de sorte que les premier et dernier qubits soient voisins sans incorporer des portes SWAP supplémentaires.
init_layout = [
13,
12,
11,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1,
0,
14,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
36,
51,
50,
49,
48,
47,
46,
45,
44,
43,
42,
41,
40,
39,
38,
37,
52,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
74,
89,
88,
87,
86,
85,
84,
83,
82,
81,
80,
79,
78,
77,
76,
75,
90,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
112,
126,
125,
124,
123,
122,
121,
120,
119,
118,
117,
116,
115,
114,
113,
]
# the number of qubits in the circuit is governed by the length of the initial layout
num_qubits = len(init_layout)
num_qubits
109
Construction de la carte d'intrication pour le circuit TwoLocal
coupling_map = [(i, i + 1) for i in range(0, len(init_layout) - 1)]
coupling_map.append(
(len(init_layout) - 1, 0)
) # adding in the periodic connectivity
Le circuit TwoLocal permet la répétition des rotation_blocks et de la carte d'intrication plusieurs fois. Dans ce cas, le nombre de répétitions détermine le nombre de portes périodiques à découper. Étant donné que le surcoût d'échantillonnage augmente exponentiellement avec le nombre de découpes (consultez le tutoriel Découpe de fils pour l'estimation des valeurs d'espérance pour plus de détails), nous fixons le nombre de répétitions à 2 dans ce notebook.
num_reps = 2
entangler_map = []
for even_edge in coupling_map[0 : len(coupling_map) : 2]:
entangler_map.append(even_edge)
for odd_edge in coupling_map[1 : len(coupling_map) : 2]:
entangler_map.append(odd_edge)
ansatz = TwoLocal(
num_qubits=num_qubits,
rotation_blocks="rx",
entanglement_blocks="cx",
entanglement=entangler_map,
reps=num_reps,
).decompose()
ansatz.draw("mpl", fold=-1)

Afin de vérifier la qualité du résultat obtenu par découpe de circuits, nous devons connaître le résultat idéal. Le circuit actuel dépasse les capacités de simulation classique par force brute. Par conséquent, nous fixons soigneusement les paramètres du circuit pour le rendre clifford.
Nous assignerons la valeur de paramètre pour les deux premières couches de portes Rx, et la valeur pour la dernière couche. Cela garantit que le résultat idéal de ce circuit est , étant le nombre de qubits. Par conséquent, les valeurs d'espérance de et , où est l'indice du qubit, sont respectivement et .
params_last_layer = [np.pi] * ansatz.num_qubits
params = [0] * (ansatz.num_parameters - ansatz.num_qubits)
params.extend(params_last_layer)
ansatz.assign_parameters(params, inplace=True)
Sélection des observables
Pour quantifier les bénéfices du gate cutting, nous mesurons les valeurs d'espérance des observables et . Comme discuté précédemment, les valeurs d'espérance idéales sont respectivement et .
observables = []
for i in range(num_qubits):
obs = "I" * (i) + "Z" + "I" * (num_qubits - i - 1)
observables.append(obs)
for i in range(num_qubits):
if i == num_qubits - 1:
obs = "Z" + "I" * (num_qubits - 2) + "Z"
else:
obs = "I" * i + "ZZ" + "I" * (num_qubits - i - 2)
observables.append(obs)
observables = SparsePauliOp(observables)
paulis = observables.paulis
coeffs = observables.coeffs
Étape 2 : Optimiser le problème pour l'exécution sur matériel quantique
- Entrée : Circuit abstrait et observables
- Sortie : Circuit cible et observables produits par la découpe des portes longue distance
Transpilation du circuit
Notez que le circuit peut être transpilé à cette étape, ou après la découpe. Si nous transpilons après la découpe, cela nécessitera de transpiler chacune des sous-expériences générées en raison du surcoût d'échantillonnage. Il est donc plus judicieux de transpiler à cette étape pour réduire le surcoût de transpilation.
Cependant, si la transpilation est effectuée à cette étape avec la connectivité matérielle native, le transpileur ajoutera plusieurs portes SWAP pour placer l'opération périodique à 2 qubits — masquant ainsi les avantages de la découpe de circuits. Pour éviter ce problème, nous pouvons tirer parti du fait que nous connaissons exactement les portes à découper. Plus précisément, nous pouvons créer une carte de couplage virtuelle en ajoutant des connexions virtuelles entre des qubits éloignés pour accommoder ces portes périodiques à 2 qubits. Cela garantira que le circuit peut être transpilé à cette étape sans incorporer les portes SWAP supplémentaires.
coupling_map = backend.configuration().coupling_map
# create a virtual coupling map with long range connectivity
virtual_coupling_map = coupling_map.copy()
virtual_coupling_map.append([init_layout[-1], init_layout[0]])
virtual_coupling_map.append([init_layout[0], init_layout[-1]])
pm_virtual = generate_preset_pass_manager(
optimization_level=1,
coupling_map=virtual_coupling_map,
initial_layout=init_layout,
basis_gates=backend.configuration().basis_gates,
)
virtual_mapped_circuit = pm_virtual.run(ansatz)
virtual_mapped_circuit.draw("mpl", fold=-1, idle_wires=False)

Découpe des connectivités périodiques longue distance
Nous découpons maintenant les portes dans le circuit transpilé. Notez que les portes à 2 qubits qui doivent être découpées sont celles connectant les dernier et premier qubits du placement.
# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(virtual_mapped_circuit.data)
if {virtual_mapped_circuit.find_bit(q)[0] for q in instruction.qubits}
== {init_layout[-1], init_layout[0]}
]
Nous allons appliquer le placement du circuit transpilé à l'observable.
trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)
Enfin, les sous-expériences sont générées en échantillonnant sur différentes bases de mesure et de préparation.
qpd_circuit, bases = cut_gates(virtual_mapped_circuit, cut_indices)
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit,
observables=trans_observables.paulis,
num_samples=np.inf,
)
Notez que la découpe des interactions longue distance entraîne l'exécution de multiples échantillons du circuit qui diffèrent par les bases de mesure et de préparation. Plus d'informations à ce sujet sont disponibles dans Constructing a virtual two-qubit gate by sampling single-qubit operations et Cutting circuits with multiple two-qubit unitaries.
Le nombre de portes périodiques à découper est égal au nombre de répétitions de la couche TwoLocal, défini par num_reps ci-dessus. Le surcoût d'échantillonnage du gate cutting est de 6. Par conséquent, le nombre total de sous-expériences sera .
print(f"Number of subexperiments is {len(subexperiments)} = 6**{num_reps}")
Number of subexperiments is 36 = 6**2
Transpilation des sous-expériences
À ce stade, les sous-expériences contiennent des circuits avec certaines portes à 1 qubit qui ne font pas partie du jeu de portes de base. Cela est dû au fait que les qubits découpés sont mesurés dans différentes bases, et les portes de rotation utilisées à cet effet n'appartiennent pas nécessairement au jeu de portes de base. Par exemple, la mesure dans la base X implique l'application d'une porte de Hadamard avant la mesure habituelle dans la base Z. Mais la porte de Hadamard ne fait pas partie du jeu de portes de base.
Au lieu d'appliquer l'ensemble du processus de transpilation sur chacun des circuits des sous-expériences, nous pouvons utiliser des passes de transpilation spécifiques. Consultez cette documentation pour une description détaillée de toutes les passes de transpilation disponibles.
Nous allons appliquer les passes BasisTranslator puis Optimize1qGatesDecomposition pour nous assurer que toutes les portes de ces circuits appartiennent au jeu de portes de base. L'utilisation de ces deux passes est plus rapide que l'ensemble du processus de transpilation, puisque d'autres étapes telles que le routage et la sélection du placement initial ne sont pas effectuées à nouveau.
pass_ = PassManager(
[Optimize1qGatesDecomposition(basis=backend.configuration().basis_gates)]
)
subexperiments = pass_.run(
[
dag_to_circuit(
BasisTranslator(sel, target_basis=backend.basis_gates).run(
circuit_to_dag(circ)
)
)
for circ in subexperiments
]
)
Étape 3 : Exécuter à l'aide des primitives Qiskit
- Entrée : Circuits cibles
- Sortie : Distributions de quasi-probabilités
Nous utilisons une primitive SamplerV2 pour l'exécution des circuits découpés. Nous désactivons le découplage dynamique et le twirling afin que toute amélioration obtenue dans le résultat soit uniquement due à l'application efficace du gate cutting pour ce type de circuit.
options = SamplerOptions()
options.default_shots = 10000
options.dynamical_decoupling.enable = False
options.twirling.enable_gates = False
options.twirling.enable_measure = False
Nous allons maintenant soumettre les tâches en mode batch.
with Batch(backend=backend) as batch:
sampler = SamplerV2(options=options)
cut_job = sampler.run(subexperiments)
print(f"Job ID {cut_job.job_id()}")
Job ID cwxf7wq60bqg008pvt8g
result = cut_job.result()
Étape 4 : Post-traitement et retour du résultat dans le format classique souhaité
- Entrée : Distributions de quasi-probabilités
- Sortie : Valeurs d'espérance reconstruites
reconstructed_expvals = reconstruct_expectation_values(
result,
coefficients,
paulis,
)
Nous calculons maintenant la moyenne des observables Z de poids 1 et de poids 2.
cut_weight_1 = np.mean(reconstructed_expvals[:num_qubits])
cut_weight_2 = np.mean(reconstructed_expvals[num_qubits:])
print(f"Average of weight-1 expectation values is {cut_weight_1}")
print(f"Average of weight-2 expectation values is {cut_weight_2}")
Average of weight-1 expectation values is -0.741733944954063
Average of weight-2 expectation values is 0.6968862385320495
Vérification croisée : Obtenir les valeurs d'espérance sans découpe
Il est utile de vérifier l'avantage de la technique de découpe de circuits par rapport à l'absence de découpe. Ici, nous allons calculer les valeurs d'espérance sans découper le circuit. Notez qu'un tel circuit non découpé souffrira d'un grand nombre de portes SWAP nécessaires pour implémenter l'opération à 2 qubits entre les premier et dernier qubits. Nous utiliserons la fonction sampled_expectation_value pour obtenir les valeurs d'espérance du circuit non découpé après avoir obtenu la distribution de probabilité via SamplerV2. Cela permet une utilisation homogène de la primitive sur toutes les instances. Notez cependant que nous aurions également pu utiliser EstimatorV2 pour calculer directement les valeurs d'espérance.
if ansatz.num_clbits == 0:
ansatz.measure_all()
pm_uncut = generate_preset_pass_manager(
optimization_level=1, backend=backend, initial_layout=init_layout
)
transpiled_circuit = pm_uncut.run(ansatz)
sampler = SamplerV2(mode=backend, options=options)
uncut_job = sampler.run([transpiled_circuit])
uncut_job_id = uncut_job.job_id()
print(f"The job id for the uncut clifford circuit is {uncut_job_id}")
The job id for the uncut clifford circuit is cwxfads2ac5g008jhe7g
uncut_result = uncut_job.result()[0]
uncut_counts = uncut_result.data.meas.get_counts()
Nous allons maintenant calculer les valeurs d'espérance moyennes de tous les observables Z de poids 1 et de poids 2 sans découpe.
uncut_expvals = [
sampled_expectation_value(uncut_counts, obs) for obs in paulis
]
uncut_weight_1 = np.mean(uncut_expvals[:num_qubits])
uncut_weight_2 = np.mean(uncut_expvals[num_qubits:])
print(f"Average of weight-1 expectation values is {uncut_weight_1}")
print(f"Average of weight-2 expectation values is {uncut_weight_2}")
Average of weight-1 expectation values is -0.32494128440366965
Average of weight-2 expectation values is 0.32340917431192656
Visualisation
Visualisons maintenant l'amélioration obtenue pour les observables de poids 1 et de poids 2 lors de l'utilisation du gate cutting pour un circuit à chaîne périodique.
mpl.rcParams.update(mpl.rcParamsDefault)
fig = plt.subplots(figsize=(12, 8), dpi=200)
width = 0.25
labels = ["Weight-1", "Weight-2"]
x = np.arange(len(labels))
ideal = [-1, 1]
cut = [cut_weight_1, cut_weight_2]
uncut = [uncut_weight_1, uncut_weight_2]
br1 = np.arange(len(ideal))
br2 = [x + width for x in br1]
br3 = [x + width for x in br2]
plt.bar(
br1, ideal, width=width, edgecolor="k", label="Ideal", color="#4589ff"
)
plt.bar(br2, cut, width=width, edgecolor="k", label="Cut", color="#a56eff")
plt.bar(
br3, uncut, width=width, edgecolor="k", label="Uncut", color="#009d9a"
)
plt.axhline(y=0, color="k", linestyle="-")
plt.xticks([r + width for r in range(len(ideal))], labels, fontsize=14)
plt.yticks(fontsize=14)
plt.legend(fontsize=14)
plt.show()

Résumé
En résumé, nous avons calculé les valeurs d'espérance moyennes des observables de type Z de poids 1 et de poids 2 pour une chaîne 1D périodique de 109 qubits. Pour ce faire, nous avons
- créé une carte de couplage virtuelle en ajoutant une connectivité longue distance entre les premier et dernier qubits de la chaîne 1D, puis transpilé le circuit.
- la transpilation à cette étape nous a permis d'éviter le surcoût de transpilation de chaque sous-expérience séparément après la découpe,
- l'utilisation d'une carte de couplage virtuelle nous a permis d'éviter les portes SWAP supplémentaires pour l'opération à 2 qubits entre les premier et dernier qubits.
- supprimé la connectivité longue distance du circuit transpilé via le gate cutting.
- converti les circuits découpés dans le jeu de portes de base en appliquant les passes de transpilation appropriées.
- exécuté les circuits découpés sur un dispositif IBM Quantum à l'aide d'une primitive
SamplerV2. - obtenu les valeurs d'espérance en reconstruisant les résultats des circuits découpés.
Conclusions
Nous observons à partir des résultats que les moyennes des observables de type de poids 1 et de poids 2 sont significativement améliorées par la découpe des portes périodiques. Notez que cette étude n'inclut aucune technique de suppression ou d'atténuation des erreurs. L'amélioration observée est uniquement due à l'utilisation appropriée du gate cutting pour ce problème. Les résultats auraient pu être davantage améliorés en utilisant des techniques d'atténuation et de suppression des erreurs.
Cette étude montre un exemple d'utilisation efficace du gate cutting pour améliorer les performances du calcul.
Enquête sur le tutoriel
Veuillez répondre à cette courte enquête pour nous faire part de vos commentaires sur ce tutoriel. Vos retours nous aideront à améliorer notre contenu et l'expérience utilisateur.