Aller au contenu principal
Ce projet est en phase bêta. Tu as trouvé un bug ? Ouvrir un ticket GitHub. Une question ou une idée ? Pose ta question dans Discussions — tes retours sont les bienvenus !
Quelle est la qualité de la traduction sur cette page ?

Tronquer les termes de Pauli pendant la rétropropagation

Ce guide explique comment configurer le mécanisme de troncature des termes de Pauli fourni par le module qiskit_addon_obp.utils.truncating.

La rétropropagation d'opérateurs (OBP) peut servir à réduire la profondeur des circuits quantiques au prix d'une observable plus complexe. Pour obtenir des résultats significatifs avec l'OBP, il faut généralement tronquer des termes de l'observable afin d'éviter qu'elle ne devienne trop grande. Une façon de permettre une rétropropagation plus profonde dans le circuit tout en empêchant l'opérateur de trop grossir consiste à tronquer les termes de faible coefficient plutôt que de les ajouter à l'opérateur. La troncature de termes peut réduire le nombre de circuits quantiques à exécuter, mais elle entraîne une certaine erreur dans le calcul final de la valeur moyenne, proportionnelle à la magnitude des coefficients des termes tronqués.

La méthode backpropagate accepte un TruncationErrorBudget facultatif, qui configure la troncature des termes de Pauli de faible poids pour chaque observable après la rétropropagation réussie de chaque tranche. Le nombre de termes tronqués dépend de divers paramètres de configuration spécifiés par l'utilisateur. Pour l'instant, une seule stratégie de troncature est disponible : la méthode truncate_binary_search. Étant donné une observable et un certain budget, elle effectue une recherche binaire sur les termes de Pauli et les coefficients de cette observable pour trouver le seuil optimal, de sorte que la somme des coefficients tronqués soit maximale mais inférieure au budget.

Remarque : par défaut, la norme L1 est utilisée pour évaluer et borner l'erreur de troncature ; toutefois, le paramètre p_norm permet de spécifier la norme Lp à utiliser. Pour plus d'informations sur l'utilisation de ce paramètre, consulte le guide Utiliser différentes normes Lp pour la troncature des termes de Pauli. Les exemples suivants illustrent diverses façons de construire un TruncationErrorBudget à l'aide de la fonction associée setup_budget.

Construire un circuit d'exemple​

Ce guide utilise les tranches de circuit suivantes :

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "qiskit": "qiskit", "qiskit_addon_obp": "qiskit-addon-obp", "qiskit_addon_utils": "qiskit-addon-utils", "rustworkx": "rustworkx"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import rustworkx.generators
from qiskit.synthesis import LieTrotter
from qiskit_addon_utils.problem_generators import (
PauliOrderStrategy,
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types

# Generate a linear chain of 10 qubits
linear_chain = rustworkx.generators.path_graph(10)

# Use an arbitrary XY model
hamiltonian = generate_xyz_hamiltonian(
linear_chain,
coupling_constants=(0.05, 0.02, 0.0),
ext_magnetic_field=(0.02, 0.08, 0.0),
pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,
)
# Evolve for some time
circuit = generate_time_evolution_circuit(
hamiltonian, synthesis=LieTrotter(reps=3), time=2.0
)
# slice the circuit by gate type
slices = slice_by_gate_types(circuit)

# for visualization purposes, recombine the slices with barriers between them and draw the resulting circuit
combine_slices(slices, include_barriers=True).draw("mpl", fold=50, scale=0.6)

Output of the previous code cell

Nous examinerons une seule observable simple :

from qiskit.quantum_info import SparsePauliOp

obs = SparsePauliOp("IIIIIZIIII")

Le cas le plus simple : un budget de troncature fixe pour chaque tranche​

Le budget disponible pour la troncature des termes de Pauli peut varier à chaque étape de la rétropropagation. Pour comprendre comment cela fonctionne, nous examinons d'abord le cas le plus simple d'un budget de troncature fixe spécifié par l'utilisateur.

La façon la plus directe de spécifier le budget de troncature est d'utiliser l'argument max_error_per_slice. C'est d'ailleurs ce qui est fait dans le tutoriel Réduire la profondeur des circuits avec la rétropropagation d'opérateurs. Fixer max_error_per_slice à un float alloue à chaque tranche un budget égal à cette valeur. Dans l'exemple ci-dessous, nous fixons cette valeur à 0.001, ce qui garantit implicitement une erreur de troncature d'au plus 0.018, si les 18 tranches étaient rétropropagées.

Remarque que tout budget d'erreur restant après la rétropropagation d'une tranche et la troncature des termes de faible coefficient sera toujours ajouté au budget d'erreur de la tranche suivante.

from qiskit_addon_obp.utils.truncating import setup_budget

truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.simplify import OperatorBudget

op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
New observable contains 29 terms and 10 commuting groups.

Nous utilisons ensuite l'instance OBPMetadata et les outils fournis par le module visualization pour visualiser le processus de rétropropagation.

  • Le graphique en haut à gauche montre que nous avons assez de budget pour commencer à tronquer des termes de l'observable après la rétropropagation de la troisième tranche. À partir de la troisième tranche, nous savons que nous tronquons au moins un terme de chaque tranche rétropropagée, car nous introduisons une certaine erreur de troncature après chaque tranche.

  • Le graphique en haut à droite montre que le budget d'erreur passe à .003 pour la troisième tranche. Nous voyons une forte chute du budget restant, ce qui signifie que des termes ont été tronqués de l'observable. C'est cohérent avec ce que nous avons déduit du graphique en haut à gauche.

  • Le graphique en bas à gauche montre qu'à mesure que nous tronquons des termes de notre observable, notre erreur cumulée globale croît de façon monotone. Ce graphique confirme aussi qu'aucun terme n'a été tronqué avant que la troisième tranche ait été rétropropagée.

  • Le graphique en bas à droite montre que le nombre de groupes de Pauli commutants dans notre observable a atteint la limite spécifiée de 10. Ce graphique montre aussi comment la rétropropagation d'une couche de plus ferait dépasser à notre observable la borne spécifiée, comme tu peux le voir au croisement des lignes noire et rouge.

Remarque que, dans tous ces graphiques, l'axe des x énumère les tranches rétropropagées, mais comme l'OBP travaille à partir de la fin du circuit, slice 1 est la toute dernière tranche, slice 2 est celle d'avant, et ainsi de suite.

from matplotlib import pyplot as plt
from qiskit_addon_obp.utils.visualization import (
plot_accumulated_error,
plot_left_over_error_budget,
plot_num_qwc_groups,
plot_slice_errors,
)

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

Spécifier explicitement le budget de chaque tranche​

Si tu sais comment attribuer un budget à chaque tranche de façon à optimiser les performances de la rétropropagation, tu peux vouloir attribuer explicitement un budget à chaque tranche. À des fins de démonstration, nous allons attribuer un budget nul aux trois premières tranches et utiliser un budget de .001 par tranche pour les tranches restantes.

# Zero out the first 3 slices' budgets
max_error_per_slice = [0.0] * 3 + [0.001] * (len(slices) - 3)

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
New observable contains 32 terms and 10 commuting groups.

Retirer le budget des trois premières couches a fait qu'aucun terme n'a été tronqué avant la quatrième couche, comme on peut le confirmer sur trois de ces graphiques. Ce qui peut sembler un peu surprenant, c'est que bien qu'aucun budget restant n'ait été transmis à la quatrième tranche, un terme a tout de même été tronqué grâce au budget alloué de .001. Cela est évident sur le graphique en haut à gauche, mais s'observe aussi sur le graphique en haut à droite, car la courbe du budget restant s'aplatit entre les tranches trois et quatre. Autre détail notable : au moins un terme a été tronqué à partir de ce point, comme dans l'exemple précédent.

L'enseignement clé est que, bien que nous ayons introduit moins d'erreur de troncature dans le deuxième exemple en mettant à zéro le budget de certaines tranches, nous avons pu rétropropager le même nombre de tranches, et notre observable contient le même nombre de groupes de Pauli commutants. Nous pouvons confirmer que la borne sur notre erreur est plus petite dans le deuxième exemple en inspectant le graphique en bas à gauche.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

Spécifier le budget de façon cyclique​

Si tu as un circuit avec un motif répétable, comme un circuit de Trotter, tu peux vouloir spécifier un budget pour ce sous-ensemble répété de tranches et faire utiliser ce budget pour toutes les répétitions suivantes de ces tranches.

Plus concrètement, le circuit d'exemple que nous utilisons comporte six tranches répétées trois fois, soit 18 tranches au total. Nous attribuerons arbitrairement un budget nul aux couches à un qubit et RYY, et un budget de .003 à chacune des couches RXX. Nous observerons comment le fait de spécifier le budget comme une séquence de longueur 6 entraîne l'application cyclique du budget aux 18 tranches.

Encore une fois, remarque que les tranches sont rétropropagées en ordre inverse (c'est-à-dire en partant de la fin). Ainsi, la première entrée de notre budget cyclique est en réalité utilisée pour la dernière tranche, la deuxième entrée pour la tranche précédente, et ainsi de suite.

# Specify a length-6 per-slice budget.
# This will be cycled over three times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=inf, p_norm=1)
op_budget = OperatorBudget(max_qwc_groups=20)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 13 circuit slices.
New observable contains 49 terms and 14 commuting groups.

Comme on le voit sur les images en haut à gauche et en bas à gauche, aucune troncature n'a été effectuée sur les quatre premières tranches puisqu'aucun budget d'erreur ne leur était alloué. Les tranches cinq et six ont vu certains de leurs termes tronqués dès que du budget est devenu disponible.

Comme on le voit sur l'image en haut à gauche, une quantité d'erreur relativement importante a été introduite après la rétropropagation de la tranche sept, bien qu'aucun budget ne lui ait été alloué. C'est parce que seulement environ .002 des .006 de budget total alloué aux tranches cinq et six ont été utilisés ; le reste a donc été transmis à la tranche sept et en grande partie dépensé, comme on le voit sur l'image en haut à droite.

Les images en haut à gauche et en haut à droite montrent que la petite quantité de budget restant est épuisée entre les tranches 8 à 10, et qu'un nouveau budget devient disponible à la tranche 11, comme prévu. Les images en haut à gauche, en haut à droite et en bas à gauche démontrent toutes le comportement cyclique de l'argument max_error_per_slice lorsque sa longueur est inférieure au nombre de tranches. Ce comportement cyclique se serait poursuivi sur toutes les tranches, mais le critère d'arrêt max_qwc_groups a été atteint après la rétropropagation de 13 tranches, comme on le voit sur l'image en bas à droite.

Il est aussi intéressant de noter que le nombre de groupes de Pauli diminue effectivement après la deuxième vague de budget disponible à la tranche 11. C'est parce qu'il y avait des groupes de faibles coefficients qui ne pouvaient pas être tronqués tant qu'il n'y avait pas plus de budget après la rétropropagation de la tranche 11 ; ils se sont donc accumulés dans l'observable pendant plusieurs itérations. Ce cas souligne aussi que max_qwc_groups doit être dépassé pour que l'algorithme se termine.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

Plafonner l'erreur totale​

En plus de spécifier le budget d'erreur par tranche, on peut spécifier la quantité maximale d'erreur introduite par la troncature. Une fois cette limite atteinte, plus aucune troncature n'est effectuée ; en revanche, la rétropropagation continue jusqu'à ce que l'observable devienne trop grande et que l'un des critères d'arrêt soit atteint.

Nous relançons ensuite l'expérience ci-dessus, en fixant un plafond sur max_error_total tel que le budget d'erreur soit épuisé après la rétropropagation de la tranche sept.

# Specify a length-6 per-slice budget.
# This will be cycled over 3 times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice, max_error_total=0.006
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=0.006, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 10 circuit slices.
New observable contains 67 terms and 20 commuting groups.

Comme prévu, notre erreur de troncature plafonne à .006 (graphique en bas à gauche). Il est notable que, dans cette exécution, nous n'avons pas pu rétropropager la tranche 11. C'est parce que nous n'avions pas assez de budget pour tronquer des termes et que le nombre de groupes de Pauli commutants a dépassé la limite de 20, comme on le voit sur l'image en bas à droite.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

Il peut être souhaitable de simplement plafonner le budget global avec max_error_total sans spécifier max_error_per_slice.

truncation_error_budget = setup_budget(max_error_total=0.018)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.018], max_error_total=0.018, p_norm=1)

La sortie de la cellule ci-dessus peut sembler un peu surprenante car le per_slice_budget est fixé à max_error_total. Cela indique que tout le budget disponible sera consommé de façon gloutonne. Tu peux le voir ainsi : le budget entier est disponible pour chaque tranche (parce que nous bouclons sur le per_slice_budget). Mais tout budget déjà consommé sera déduit du budget disponible à ce point de l'algorithme.

op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 9 circuit slices.
New observable contains 25 terms and 9 commuting groups.

L'image en haut à droite montre comment l'intégralité du budget d'erreur de .018 est mise à disposition de la première tranche. Aucune troncature n'est effectuée avant la troisième tranche, donc le budget restant reste constant. Le budget diminue ensuite de façon monotone, car le budget complet est mis à disposition de chaque tranche rétropropagée jusqu'à épuisement.

Il est notable que cette expérience a donné deux tranches rétropropagées de moins que la première expérience de ce notebook, qui est presque identique. Cela démontre que, pour certains problèmes, répartir le budget uniformément peut être optimal. Pour d'autres problèmes, laisser les tranches dépenser goulûment tout le budget peut donner de meilleures performances.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

Plafonner ensemble le nombre de tranches rétropropagées et l'erreur totale​

Si tu ne veux pas répartir ton budget d'erreur sur tout le circuit mais que tu ne veux pas non plus le dépenser goulûment, tu peux spécifier le nombre de tranches que tu comptes rétropropager (num_slices), ainsi qu'un budget d'erreur total (max_error_total). Cela répartira le budget d'erreur uniformément (selon p_norm) entre les tranches d'entrée.

Ici, nous plafonnerons à 12 le nombre de tranches que nous pourrions rétropropager, et nous conserverons le même budget d'erreur total.

num_slices = 12

truncation_error_budget = setup_budget(
max_error_total=0.018, num_slices=num_slices, p_norm=1
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0014999999999999998], max_error_total=0.018, p_norm=1)

Nous allons maintenant tenter de rétropropager les 12 tranches pour lesquelles nous avons alloué un budget à l'étape précédente. Pour cela, il suffit de passer les 12 dernières tranches de notre circuit. Avec p_norm=1, chacune des 12 tranches dispose d'un budget de 0.018 / num_slices = 0.0015.

bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices[-num_slices:],
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)

Comme nous avons passé un sous-ensemble de nos tranches (dans le code slices[-num_slices:]) à backpropagate, nous devons combiner les tranches restantes après la rétropropagation avec les tranches qui n'ont jamais été envoyées à la rétropropagation (dans le code slices[:-num_slices]).

Une fois que nous avons combiné toutes les tranches restantes, nous pouvons utiliser combine_slices pour générer le QuantumCircuit de profondeur réduite. Ensuite, nous inspectons combien de tranches ont été rétropropagées par rapport à la taille atteinte par notre observable.

# Recombine the slices remaining after backprop with the rest of the original circuit
reduced_circuit = combine_slices(slices[:-num_slices] + remaining_slices)

print(f"Backpropagated {num_slices - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 12 circuit slices.
New observable contains 29 terms and 9 commuting groups.

Les graphiques montrent que nous avons réussi à rétropropager les 12 tranches tout en gardant notre observable sous 10 groupes de Pauli commutants. Nous voyons aussi que l'utilisation de num_slices avec max_error_total produit une répartition des budgets entre les tranches, et que le budget inutilisé est de nouveau transmis à la tranche suivante. C'est particulièrement visible dans le graphique en haut à droite, car le budget est à la fois dépensé et reconstitué tout au long de la rétropropagation.

Il est remarquable que cette méthode de répartition de l'erreur ait donné les meilleurs résultats (plus de tranches rétropropagées), par rapport à la première expérience de ce notebook et à l'exemple précédent. Tous ces exemples allouaient un budget d'erreur de .018, mais la rétropropagation s'est comportée différemment selon la façon dont ce budget était réparti.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

Travailler avec plusieurs observables​

La méthode qiskit_addon_obp.backpropagate permet de passer une séquence d'observables. Cela simplifie le flux de travail lorsqu'on traite plusieurs observables cibles.

Nous le mentionnons explicitement ici pour t'apprendre comment la stratégie de troncature gère un tel cas. Pour cet exemple, nous ajoutons une observable supplémentaire à celle que nous avons utilisée jusqu'ici :

obs = [SparsePauliOp("IIIIIZIIII"), SparsePauliOp("IIIIIXIIII")]

Enfin, nous répétons la première expérience de ce tutoriel, mais avec deux observables dans lesquelles rétropropager le circuit.

Pour cet exemple, cela n'affecte pas le nombre de tranches qui ont pu être rétropropagées. Cependant, nous constatons que les deux observables ont produit des nombres différents de termes de Pauli et de groupes commutants.

truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"The new first observable contains {len(bp_obs[0])} terms and {len(bp_obs[0].group_commuting(qubit_wise=True))} commuting groups."
)
print(
f"The new second observable contains {len(bp_obs[1])} terms and {len(bp_obs[1].group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
The new first observable contains 29 terms and 10 commuting groups.
The new second observable contains 23 terms and 8 commuting groups.

Les graphiques ci-dessous aident à illustrer comment l'algorithme de rétropropagation gère plusieurs observables.

D'abord, les graphiques en haut à gauche, en haut à droite et en bas à gauche montrent que le budget de troncature des termes est défini individuellement pour chaque observable. En d'autres termes, les deux observables ont la possibilité de tronquer des termes en supposant une erreur de 0.001 par tranche rétropropagée. En raison de la nature différente des observables, cela entraîne une consommation différente du budget. Dans cet exemple, elles présentent un fort recouvrement, ce qui n'arrive pas toujours.

Le graphique en bas à droite illustre que max_qwc_groups prend en compte toutes les observables. Cela signifie que les termes de toutes les observables sont regroupés pour arriver à un nombre final de groupes commutant qubit par qubit, qui est comparé à max_qwc_paulis. Il en va de même pour le seuil max_paulis (non abordé dans ce notebook), qui te permet de fixer une limite sur le nombre de termes de Pauli sur l'ensemble des observables.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell