Annulation probabiliste des erreurs à l'aide de cônes de lumière ombrés
Estimation du temps d'exécution : 10 minutes sur un processeur Heron (REMARQUE : il s'agit uniquement d'une estimation. (Votre temps d'exécution peut varier.)
Acquis d'apprentissage
À l'issue de ce tutoriel, les utilisateurs devraient avoir compris :
- Qu'est-ce que l'annulation probabiliste d'erreur (PEC) et pourquoi sa surcharge d'échantillonnage croît de manière exponentielle avec le bruit total agissant sur le circuit?
- Comment les cônes de lumière ombragés (SLC) limitent la contribution de chaque terme de bruit à l'observable cible, ce qui vous permet d'allouer votre budget d'atténuation là où cela compte vraiment — et comment, au-delà de la réduction de la surcharge sans biais déjà offerte par un cône de lumière purement géométrique, l'ombrage vous permet d'échanger un biais résiduel limité contre une surcharge d'échantillonnage encore plus faible
- Comment apprendre le bruit de couche à l'aide de
NoiseLearnerV3et injecter de l'anti-bruit viasamplomaticet laExecutorprimitive - Comment combiner les méthodes PEC et PEC+SLC avec TREX et la post-sélection pour estimer une valeur attendue sur matériel
Prérequis
Nous recommandons aux utilisateurs de se familiariser avec les sujets suivants avant de suivre ce tutoriel :
- Le flux de travail « Patterns » de Qiskit
- Utilisation de la primitive « Estimator » pour calculer les valeurs attendues d'une variable observable
- Les techniques d'atténuation des erreurs telles que le « Pauli twirling » et TREX, abordées dans la section « Combiner les options d'atténuation des erreurs avec la primitive Estimator »
Arrière-plan
Ce tutoriel explique comment limiter les erreurs à l'aide de l'extension « Shaded Light Cone » (SLC). Cet add-on constitue une évolution de la technique d'annulation probabiliste des erreurs (PEC), dans laquelle l'utilisateur identifie le bruit présent dans chaque couche d'un circuit, puis l'annule en appliquant des portes à un seul qubit et des techniques de post-traitement. Par rapport à d'autres méthodes, la PEC offre des bornes plus solides sur le biais du résultat corrigé, mais tend à entraîner une charge plus importante en termes de temps de calcul sur le QPU. Au cours du PEC, afin de compenser l'atténuation de la valeur attendue due au bruit, le résultat moyen est rééchelonnné par un facteur de , où correspond au taux de bruit d'erreur de Pauli appris au niveau de la couche du circuit. Ce changement d'échelle multiplie la variance par un facteur de et multiplie donc également le nombre d'exécutions de circuits nécessaires sur le QPU par , ce que nous appelons le coût d'échantillonnage ou la surcharge d'échantillonnage. Comme la fonction « » croît de manière exponentielle, le PEC est souvent limité à des circuits peu profonds ou comportant peu de qubits. Pour en savoir plus sur la PEC, consultez l'article « Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors ».
Si nous parvenons à identifier les erreurs qui ne nécessitent pas de mesures correctives, nous pourrons réduire ce coût d'échantillonnage de manière exponentielle. Une première étape dans cette direction consiste à mettre en œuvre une atténuation des erreurs tenant compte du contexte local, qui utilise un « cône de lumière » conventionnel, rapidement calculable, afin de réduire la surcharge liée au PEC en limitant la sensibilité d'une observable aux erreurs à l'échelle de l'ensemble du circuit, ce qui étend la faisabilité du PEC à des échelles plus grandes pour certains problèmes. Les erreurs situées en dehors de ce cône de lumière ne peuvent pas influencer le résultat mesuré et peuvent donc être exclues de la procédure d'annulation des erreurs. Cette exclusion réduit la charge liée à l'échantillonnage, parfois de manière significative, sans introduire de biais supplémentaire. En particulier, pour mesurer une observable locale d’un circuit à profondeur fixe, la surcharge d’échantillonnage nécessaire finit par se stabiliser lorsque l’on augmente le nombre de qubits dans le circuit (voir la figure 2b dans « Locality and error mitigation of quantum circuits »).
Les « lightcones ombragés » (SLC) vont plus loin : ils utilisent des simulations classiques pour mieux maîtriser la sensibilité aux erreurs sur l'ensemble du circuit. Cela permet d'échanger une partie du temps d'exécution du QPU contre du temps d'exécution du CPU et de réduire la surcharge d'échantillonnage nécessaire à la renormalisation du biais. Au lieu d'une limite stricte, on attribue à chaque erreur potentielle du circuit une « nuance » graduelle qui définit la borne supérieure de la sensibilité de la valeur attendue de l'observable à cette erreur. (Pour les limites obtenues par propagation en arrière présentées ci-dessous, cette susceptibilité dépend également de l'état initial du circuit — à savoir l'état « tout à zéro » dans les flux de travail classiques, ainsi que dans ce tutoriel.) Cette caractérisation affinée permet des applications plus efficaces et mieux ciblées de la PEC, avec une variance réduite, tout en offrant à l'utilisateur la possibilité d'ajuster de manière contrôlée le biais dans l'estimation de l'observable. Pour plus de détails, voir « Lightcone shading for classically accelerated quantum error mitigation ».
Notre workflow pour l'extension SLC exploite la samplomatic bibliothèque ainsi que les QuantumProgram classes et Executor ajoutées à Qiskit Runtime dans qiskit-ibm-runtimev0.47.0, ce qui permet aux utilisateurs de bénéficier d'un contrôle plus modulaire des paramètres d'exécution en matière de suppression et d'atténuation des erreurs, tout en conservant une grande facilité d'utilisation. Pour en savoir plus, consultez le guide sur le modèle d'exécution dirigée.
Aperçu du processus de gestion des erreurs SLC
Pour modéliser le bruit du QPU, nous utilisons un modèle de bruit de Pauli-Lindblad clairsemé, avec des taux d'erreur de Pauli à un et deux qubits, générés localement sur chaque qubit et chaque bord du dispositif. Compte tenu de ce choix, le processus de réduction des erreurs SLC présenté dans ce tutoriel se déroule comme suit :
a. CPU — Impact par erreur lié aux erreurs de Pauli à un et deux qubits
- Propagation vers l'avant (effet lié sur l'observable). Propager chaque erreur jusqu'à l'extrémité du circuit et calculer son commutateur avec l'observable.
- Tronquer les termes de l'opérateur au cours de l'évolution afin de garantir la gérabilité des calculs.
- Resserrez encore ces limites à l'aide d'une rétropropagation approximative de l'observable, fondée sur les limites de vitesse quantiques.
- Propagation en arrière (effet lié à l'état initial). Propager chaque erreur jusqu'au début du circuit et calculer son commutateur avec l'état initial.
b. QPU — Découvrez les taux de bruit. Utilisez NoiseLearnerV3 pour estimer les taux du modèle de bruit de Pauli-Lindblad.
c. CPU — Donner la priorité aux mesures d'atténuation
- Mettre à jour les limites fusionnées en fonction des taux de bruit appris. Combiner les limites avant et arrière calculées précédemment et les mettre à jour à l'aide des taux de bruit appris.
- Classez les composantes de bruit à atténuer en utilisant les limites calculées et les taux appris. Classez par ordre de priorité chaque erreur de bruit potentielle en fonction de son impact estimé sur le biais et du coût associé à sa correction.
d. QPU — Insérez l'anti-bruit et lancez l'exécution. Exécutez le circuit de votre choix en spécifiant l'anti-bruit (bruit inverse) à l'aide Box d'annotations.
e. CPU — Estimation observable. Calculer la valeur attendue en appliquant une post-sélection basée sur la mesure afin de réduire l'impact du bruit non markovien.
Présentation de l'apprentissage au bruit
L'apprentissage du bruit est une étape courante dans plusieurs méthodes d'atténuation des erreurs, mise en œuvre par l 'apprenant de bruit; elle apparaît également dans le tutoriel sur l'amplification probabiliste des erreurs. Dans NoiseLearnerV3, vous pouvez identifier spécifiquement les couches de bruit à apprendre en tant CircuitInstruction qu’objets, ce qui vous permet de calculer les limites de bruit SLC souhaitées pour chaque couche, selon la méthode décrite ci-dessus. Le modèle théorique de Pauli-Lindblad fournit des coefficients à utiliser dans la hiérarchisation PEC+SLC. generate_noise_model_paulisLa manière dont les portes sont regroupées en couches peut être déterminée à l'aide des generate_boxing_pass_manager fonctions pratiques et find_unique_box_instructions , puis transmise à la fonction utilitaire SLC, comme décrit à l'étape 2 ci-dessous.
Partie 1 | Deuxième partie | Composant 3 |
|---|---|---|
| Couches de portes à deux qubits de type « Pauli-twirl » | Répéter les paires de couches identiques et apprendre le bruit | Calculer la fidélité (erreur pour chaque canal de bruit) |
![]() | ![]() | ![]() |
Aperçu du post-traitement
Après avoir effectué des calculs sur du matériel quantique à l'aide des samplomatic frameworks et Executor , nous convertissons nos mesures de chaînes de bits en la valeur observable souhaitée. Dans ce tutoriel, nous présentons le déroulement de l'expérience sur un circuit d'Ising symétrique (construit à l'étape 1 ci-dessous), pour lequel la valeur attendue idéale de l'observable mesurée est égale à 1, car la seconde moitié du circuit annule l'effet de la première. Lors du calcul de la valeur observable à l'aide de la executor_expectation_values fonction, nous appliquons quelques techniques de post-traitement qui permettent de réduire l'impact du bruit. Il s'agit notamment de supprimer les images affectées par un bruit non markovien, d'atténuer les erreurs de lecture et de prendre en compte les spécificités de notre implémentation du PEC. Les détails sont abordés à l'étape 4 ci-dessous.
Exigences
Avant de commencer ce tutoriel, assurez-vous d'avoir installé les éléments suivants :
- Qiskit SDK v2.2 ou version ultérieure, avec prise en charge de la visualisation
- Qiskit Runtime v0.47 ou version ultérieure (
pip install qiskit-ibm-runtime) - Modules complémentaires « Shaded lightcones » pour Qiskit v0.1 ou version ultérieure (
pip install qiskit-addon-slc) - Module complémentaire Qiskit « utils » v0.3 ou version ultérieure (
pip install qiskit-addon-utils) - Samplomatic v0.13 ou version ultérieure (
pip install samplomatic)
Configuration
Commencez par importer les paquets et les fonctions nécessaires à l'exécution de ce notebook.
from multiprocessing import set_start_method
# Setting this value prevents itertools.starmap deadlock on UNIX systems
set_start_method("spawn")
# Needed to prevent PySCF from parallelizing internally (SLC only)
%set_env OMP_NUM_THREADS=1Output:
env: OMP_NUM_THREADS=1
import numpy as np
from matplotlib import pyplot as plt
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
QuantumProgram,
Executor,
NoiseLearnerV3,
)
import samplomatic
from samplomatic.utils import find_unique_box_instructions
from samplomatic.transpiler import generate_boxing_pass_manager
from qiskit_addon_utils.exp_vals.measurement_bases import (
get_measurement_bases,
)
from qiskit_addon_utils.exp_vals.expectation_values import (
executor_expectation_values,
)
from qiskit_addon_utils.noise_management import (
gamma_from_noisy_boxes,
trex_factors,
)
from qiskit_addon_utils.noise_management.post_selection import PostSelector
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
AddPostSelectionMeasures,
AddSpectatorMeasures,
)
from qiskit_addon_slc.bounds import (
compute_backward_bounds,
compute_forward_bounds,
compute_local_scales,
merge_bounds,
tighten_with_speed_limit,
)
from qiskit_addon_slc.utils import (
generate_noise_model_paulis,
map_modifier_ref_to_ref,
)
from qiskit_addon_slc.visualization import draw_shaded_lightconeExemple de simulateur à petite échelle
À l’instar d’autres méthodes d’atténuation des erreurs basées sur l’apprentissage, la technique PEC avec cônes de lumière ombrés atténue le bruit physique d’un processeur quantique spécifique; elle dépend donc de services matériels qui n’ont pas d’équivalent significatif sur un simulateur idéal :
NoiseLearnerV3caractérise expérimentalement le canal de bruit de Pauli-Lindblad clairsemé sur chaque couche unique de deux qubits. Sur un simulateur silencieux, il n'y a pas de bruit à supprimer.- Le
Executor« primitive » échantillonne les circuits « twirled » et « anti-noise-injected » générés parsamplomaticsur un backend.
Le calcul de la limite du cône de lumière ombragé est classique, mais il n'a de sens que par rapport aux taux de bruit matériels appris, qui déterminent le budget d'atténuation et la surcharge d'échantillonnage. C'est pourquoi nous ne nous attarderons pas sur l'exemple de simulateur à petite échelle et présenterons le flux de travail complet PEC+SLC directement sur le matériel, en détaillant ci-dessous chaque étape du modèle Qiskit.
Exemple de matériel à grande échelle
Nous exécutons le workflow complet PEC+SLC sur un circuit d'Ising en miroir de 20 qubits, sur le matériel d' IBM Quantum®, en suivant les quatre étapes d'un modèle Qiskit.
Étape 1 : Définir le problème
Notre démonstration se présente comme suit :
- Nous avons choisi d'effectuer l'évolution temporelle dans le cadre d'un hamiltonien de chaîne d' 1D -Ising. La chaîne d'Ising « 1D » offre une structure de circuit bien dense, ce qui est pratique pour illustrer les implémentations du PEC.
- Nous avons choisi de mesurer l' observable, qui comprend une composante et une composante . Cela permet de mettre en évidence la manière dont les limites du SLC traitent différemment les différents types d'erreurs.
- Nous souhaitons que la valeur attendue idéale de cette observable soit exactement égale à 1, afin que la qualité des estimations corrigées soit facile à évaluer. En inversant le circuit, on est déjà presque au but : à chaque porte de la seconde moitié du circuit correspond une porte inverse dans la première moitié, de sorte que le circuit idéal agit comme l'identité.
- Enfin, pour obtenir la valeur d'espérance idéale de 1, il faut que le circuit commence — et donc, grâce à la symétrie de réflexion, qu'il se termine idéalement aussi — dans un état propre +1 de l'observable. Étant donné que l'observable contient une composante non- e et que la
Executorprimitive définit la base de mesure souhaitée à la fin du circuit, nous ajoutons les portes de préparation de base correspondantes au début du circuit miroir à l'aide d'uneprepare_basisfonction. Nous utilisons cetteget_measurement_basesfonction pour déterminer quelles portes sont nécessaires et où les placer, tout en tenant compte des subtilités liées à l'indexation des qubits découlant desboxconventions d'annotation, comme expliqué dans la section consacrée à la préparation des mesures en base canonique.
# Width of the Ising chain (a free parameter of this demonstration)
num_qubits = 20
# The observable to measure: one X component (on qubit 6) and one Z component
# (on qubit 13), as motivated in "Step 1: Map the problem" above
target_obs_sparse = [("XZ", [6, 13], 1.0)]observable = SparsePauliOp.from_sparse_list(
target_obs_sparse, num_qubits=num_qubits
)# Determine the measurement basis each qubit requires for this observable.
# The reverser is used during post-processing in Step 4.
bases_virt, reverser_virt = get_measurement_bases(observable)# Free parameters of the demonstration circuit: the number of Trotter steps
# sets the circuit depth, and rx_angle is the transverse-field rotation angle
# applied in each step (pi/4 makes the dynamics non-Clifford)
num_trotter_steps = 10
rx_angle = np.pi / 4def construct_ising_circuit(
num_qubits: int,
num_trotter_steps: int,
rx_angle: float,
) -> QuantumCircuit:
circuit = QuantumCircuit(num_qubits)
for _ in range(num_trotter_steps):
circuit.rx(rx_angle, range(num_qubits))
for first_qubit in (1, 2):
for idx in range(first_qubit, num_qubits, 2):
# equivalent to Rzz(-pi/2):
circuit.sdg([idx - 1, idx])
circuit.cz(idx - 1, idx)
return circuit
def prepare_basis(
circuit: QuantumCircuit, basis: list[int]
) -> QuantumCircuit:
# basis is a list of integer values from 0 to 3. These map to the basis measurement as:
# 0 = I; 1 = Z; 2 = X; 3 = Y
assert len(basis) == circuit.num_qubits
out_circ = circuit.copy_empty_like()
for qb, bas in enumerate(basis):
if bas == 2:
out_circ.h(qb)
elif bas == 3:
out_circ.rx(-np.pi / 2, qb)
out_circ.barrier()
out_circ.compose(circuit, inplace=True)
return out_circ
def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:
mirror_circ = circuit.copy_empty_like()
mirror_circ.compose(circuit.inverse(), inplace=True)
mirror_circ.barrier()
mirror_circ.compose(circuit, inplace=True)
mirror_circ.measure_active()
return mirror_circ# Instantiate the mirrored circuit and prepend the basis-preparation gates
circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle)
mirrored_circuit = mirror_circuit(circuit)
mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])mirrored_circuit.draw(
"mpl", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False
)Output:
Étape 2 : Optimiser
Nous optimisons les paramètres relatifs au circuit à faire fonctionner, à la grandeur observable à mesurer et aux paramètres d'apprentissage du bruit. Pour commencer, nous choisissons le processeur Heron le moins sollicité et nous l'instancions en activant les portes fractionnaires. Ces portes fractionnaires permettent d'obtenir une plus grande sensibilité dans certains de nos filtres de post-sélection.
# Initialize IBM Quantum Compute Service using your saved credentials
service = QiskitRuntimeService()
# Select the least busy Heron device. This tutorial targets Heron processors:
# the usage estimate and the non-Markovian-noise post-selection discussed in
# Step 4 are specific to this processor family.
backend = service.least_busy(
operational=True,
simulator=False,
filters=lambda b: b.processor_type["family"] == "Heron",
)
# Re-fetch with fractional gates enabled (least_busy does not forward this)
# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.
backend = service.backend(backend.name, use_fractional_gates=True)
print(f"Selected backend: {backend.name}")Output:
Selected backend: ibm_kingston
Tout d'abord, nous allons transcompiler notre circuit en instructions ISA, comme cela est nécessaire pour son exécution sur nos QPU. Le choix des qubits physiques sur lesquels effectuer les opérations a une grande incidence sur la qualité des résultats, car les temps de cohérence et les fidélités des portes varient d'un dispositif à l'autre et évoluent avec le temps. Pour mener une expérience rigoureuse, il peut s’avérer utile de sélectionner manuellement une chaîne de qubits en s’appuyant sur des expériences de benchmarking rapides (voir, par exemple, le tutoriel « Benchmarking en temps réel pour la sélection de qubits »). Dans le cadre de ce tutoriel, nous utilisons le transpileur Qiskit pour sélectionner une configuration en fonction de la carte de couplage du backend et de ses dernières données d'étalonnage.
isa_pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = isa_pm.run(mirrored_circuit)
# The chain of physical qubits selected by the transpiler
layout = isa_circuit.layout.final_index_layout()
print(f"Selected layout: {layout}")
isa_observable = observable.apply_layout(
layout, num_qubits=isa_circuit.num_qubits
)Output:
Selected layout: [71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 36, 21, 22, 23, 16, 3, 2]
wire_order = layout + [
q for q in range(isa_circuit.num_qubits) if q not in layout
]
isa_circuit.draw(
"mpl",
fold=-1,
scale=0.3,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)Output:
Encadrez le circuit
Pour faciliter la mise en œuvre, nous utilisons la generate_boxing_pass_manager phase de transpilation, qui place les instructions du circuit dans des cases annotées. Ces encadrés indiquent clairement où, dans le cas du PEC, il convient d'introduire une contre-perturbation dans le circuit. Pour plus d'informations sur les paramètres, consultez la documentation de Samplomatic.
Il convient de noter que le flux de travail SLC nécessite l'utilisation de inject_noise_strategy="individual_modification" plus tard dans le processus, car cela nous permet d'identifier de manière unique les InjectNoise annotations des BoxOp instructions du circuit. (Les cases ne comportant aucune InjectNoise annotation ne peuvent pas être identifiées de cette manière.)
Les calculs liés au SLC partent actuellement du principe que les InjectNoise annotations sont placées avec site="after" (voir qiskit-addon-slc#33 ). La valeur par défaut de ce paramètre ayant changé dans les versions récentes de samplomatic (voir samplomatic#372 ), nous la définissons inject_noise_site="after" explicitement ci-dessous.
La find_unique_box_instructions fonction parcourt le circuit encapsulé fourni et identifie ceux qui comportent des couches ou des mesures à deux qubits ( 2Q ) uniques, à des fins d'apprentissage du bruit et d'injection de bruit.
# Box circuit with Twirl and InjectNoise annotations
boxes_pm = generate_boxing_pass_manager(
twirling_strategy="active",
inject_noise_strategy="individual_modification",
inject_noise_site="after",
inject_noise_targets="gates",
measure_annotations="all",
)
boxed_circuit = boxes_pm.run(isa_circuit)
# Find the unique instructions (layers) from boxed circuit
unique_2q_instructions = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)boxed_circuit.draw(
"mpl",
fold=-1,
scale=0.3,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)Output:
Préparer les mesures des bases canoniques
Il convient de noter qu'il faut veiller tout particulièrement à respecter l'ordre des qubits. Nous présentons ci-dessous la notion de canonical_qubits afin de mettre à jour correctement l'ordre des qubits lors de leur transmission à Executor, compte tenu de la manière dont cet ordre est pris en compte lors de la conversion des circuits en objets et de la détermination des instructions uniques. Pour plus de détails, consultez la documentation relative à la convention de numérotation des qubits.
# Determine the canonical qubits order
meas_box = boxed_circuit.data[-1]
canonical_qubits = [
idx
for idx, qubit in enumerate(boxed_circuit.qubits)
if qubit in meas_box.qubits
]
# map canonical qubit to physical (isa) qubit
c_2_p = {c: p for c, p in enumerate(canonical_qubits)}
# map physical (isa) qubit to virtual qubit (index in original circuit)
p_2_v = {p: v for v, p in enumerate(layout)}
# compute map between virtual and canonical qubit indices.
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}
assert len(c_2_v) == num_qubits
bases_canon = [
np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)
for base_i in bases_virt
]Flux de travail pour l'ombrage par cône de lumière, l'apprentissage du bruit et l'injection anti-bruit
Dans ce tutoriel, nous effectuons les calculs liés au SLC avant la fin de l'apprentissage du bruit, de sorte que le circuit à optimiser soit exécuté le plus près possible, dans le temps, du modèle de bruit appris. En principe, ce flux de travail peut être encore davantage parallélisé : une tâche d'apprentissage du bruit peut s'exécuter tandis que, en parallèle, les limites du bruit sont estimées. Pour un circuit quantique quelconque, le calcul limité par le bruit peut évoluer selon une dépendance faiblement exponentielle; ainsi, la parallélisation de ce calcul (par exemple, sur plusieurs cœurs de processeur) permet d'obtenir des limites plus strictes pour un temps de calcul donné, et les exécutions sur le QPU ainsi que les calculs limités peuvent eux-mêmes être parallélisés afin d'optimiser l'efficacité du flux de travail.
Prédire les Paulis du modèle de bruit à apprendre
Avant de lancer tout apprentissage au bruit sur le matériel, nous déterminons d'abord quels termes d'erreur de Pauli notre modèle de bruit contiendra. Rappelons que nous modélisons le bruit de chaque couche distincte à l'aide d'un modèle de Pauli-Lindblad clairsemé, avec un taux d'erreur pour chaque opérateur de Pauli de poids un ( , ou ) sur chaque qubit actif, et un autre pour chaque opérateur de Pauli de poids deux sur chaque paire de qubits actifs connectés. La generate_noise_model_paulis fonction parcourt chaque couche « boxed » distincte du circuit fourni et génère précisément cette liste de termes de Pauli, en tenant compte de la connectivité des qubits du circuit.
En d'autres termes, cette étape permet de prédire la structure du modèle de bruit qui NoiseLearnerV3 sera ensuite complétée par les taux appris. C'est le fait de connaître cette structure à l'avance qui nous permet de calculer une borne avant et une borne arrière pour chaque erreur potentielle avant même que l'apprentissage au bruit ne soit exécuté.
noise_model_paulis = generate_noise_model_paulis(
unique_2q_instructions, backend.coupling_map, boxed_circuit
)noise_model_rates = {ref: None for ref in noise_model_paulis}a. Calculer les limites vers l'avant
La compute_forward_bounds fonction évalue les relations de commutation entre les portes de chaque couche et les termes de Pauli générés ci-dessus, en tenant compte de la manière dont les erreurs de propagation vers l'avant affectent l'observable souhaitée . Pour les portes qui commutent avec les termes de Pauli, aucune action n'est effectuée. Quant aux portes de Clifford, elles sont placées au début du circuit. Pour les portes non-Clifford, nous estimons leur influence sur les observables cibles qui seront ensuite prioritaires pour la suppression du bruit (une fois que toutes les bornes auront été fusionnées). On obtient cette borne en appliquant d'abord la norme d' L2 (à savoir la racine carrée de la somme des carrés des coefficients des termes de Pauli concernés). Lorsque le nombre de termes de qubits est trop important, nous recourons à une borne moins stricte qui utilise l'inégalité triangulaire.
Définir les paramètres de calcul des limites
Les paramètres suivants permettent de contrôler les calculs de limites classiques :
slc_atol: la tolérance absolue des calculs. Les termes de l'opérateur dont les coefficients sont inférieurs à ce seuil sont considérés comme négligeables et sont écartés.slc_eigval_max_qubits: le nombre maximal de qubits pour lequel on évalue la borne la plus stricte, basée sur les valeurs propres ( L2-norm ). Les opérateurs pris en charge sur un nombre de qubits supérieur à celui-ci se rabattent sur la borne plus large de l'inégalité triangulaire décrite ci-dessus.slc_evolution_max_terms: le nombre maximal de termes de Pauli conservés lors de la propagation des opérateurs à travers le circuit. Le fait de tronquer les termes les plus petits permet de maintenir le calcul gérable, au prix d'une légère perte de précision des bornes.slc_num_processes: le nombre de processus CPU entre lesquels le calcul lié est parallélisé.slc_timeout: le temps alloué (en secondes) au calcul lié. À toutes les couches non atteintes dans le cadre de ce budget, on attribue la limite triviale de 2.
Dans ce tutoriel, le délai d'expiration est atteint de manière intentionnelle : nous le maintenons slc_timeout délibérément court afin que le notebook s'exécute rapidement, et pour montrer que le fait d'atteindre ce délai d'expiration ne présente aucun risque. Les nuances calculées constituent des limites supérieures à chaque étape; ainsi, le fait d'atteindre le délai d'attente ne fait qu'élargir ces limites (de manière insignifiante) pour les couches qui n'ont pas été atteintes; cela ne conduit jamais à sous-estimer l'effet d'une erreur. Cela apparaît dans les visualisations ci-dessous sous la forme des couches situées tout à gauche, qui sont entièrement remplies par la borne triviale. L'augmentation du délai d'attente (ou du nombre de processus) resserre les limites et réduit encore davantage la surcharge liée à l'échantillonnage.
slc_atol = 1e-8
slc_eigval_max_qubits = 18
slc_evolution_max_terms = 1000
slc_num_processes = 8
slc_timeout = 60forward_bounds = compute_forward_bounds(
boxed_circuit,
noise_model_paulis,
isa_observable,
evolution_max_terms=slc_evolution_max_terms,
eigval_max_qubits=slc_eigval_max_qubits,
atol=slc_atol,
num_processes=slc_num_processes,
timeout=slc_timeout,
)Output:
Bounds computation timed out.
Visualiser le SLC pour une inspection manuelle
Vous pouvez interpréter le comportement des limites ombrées en examinant la manière dont les mesures et les termes de Pauli interagissent avec les erreurs locales. Ces motifs sont caractéristiques de ce problème d'évolution temporelle de l'hamiltonien d'Ising « kicked » et apparaissent également dans l'article intitulé « Lightcone Shading for Classically Accelerated Quantum Error Mitigation », avec plusieurs caractéristiques révélatrices :
- On peut clairement distinguer les deux cônes issus des deux Paulis non identitaires dans l'observable.
- On constate que la mesure X sur le qubit 6 commute avec l'erreur X dans la couche la plus à droite.
- On constate que l'erreur Z de Pauli sur le qubit 13 commute avec l'erreur Z de la couche la plus à droite.
- Lorsque nous atteignons le délai d'attente indiqué ci-dessus, les couches restantes à gauche sont entièrement remplies de limites triviales égales à deux.
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
forward_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)Output:
b. Resserrez les limites supérieures grâce aux limites de vitesse quantiques
Nous allons ensuite resserrer ces limites à l'aide de la tighten_with_speed_limit fonction. L'étendue géométrique du cône de lumière de l'observable est déjà entièrement prise en compte dans les limites vers l'avant calculées ci-dessus. Cette fonction ajoute une étape de cohérence interne qui tire parti de la vitesse limitée à laquelle l'information peut circuler dans le circuit — le cône de lumière ne s'étend que d'un qubit au maximum par couche de portes à deux qubits — afin de resserrer les limites calculées précédemment les unes par rapport aux autres. L'information circule dans les deux sens :
- Des limites du commutateur aux limites de l'observable rétropropagée : si l'observable commute avec une erreur de « » sur un qubit, alors elle ne peut pas comporter de composante « » ou « » significative à cet endroit.
- Des bornes sur l'observable rétropropagée aux bornes du commutateur : si l'observable présente une faible composante d' et d' sur un qubit, alors elle doit presque commuter avec à cet endroit.
forward_bounds_tighter = tighten_with_speed_limit(
forward_bounds, boxed_circuit, noise_model_paulis, isa_observable
)Visualiser le SLC pour une inspection manuelle
En principe, ce resserrement permet une transition plus fluide entre les limites calculées et les limites par défaut attribuées aux couches au-delà du délai d'expiration. Dans cet exemple, l'effet est à peine perceptible, car les cônes de lumière se sont déjà propagés jusqu'aux bords du circuit au moment où le délai d'attente est atteint.
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
forward_bounds_tighter,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)Output:
c. Calculer les limites arrière
Cette partie de la prédiction du bruit évalue dans quelle mesure une erreur sur une couche donnée peut affecter l' de l'état d'entrée. La compute_backward_bounds fonction commence par inverser le circuit et supprimer les portes de mesure, puis procède à une analyse similaire à celle effectuée pour les calculs en sens direct.
backward_bounds = compute_backward_bounds(
boxed_circuit,
noise_model_paulis,
evolution_max_terms=slc_evolution_max_terms,
num_processes=slc_num_processes,
timeout=slc_timeout,
)Visualiser le SLC pour une inspection manuelle
En calculant les limites arrière, on constate que la structure de l'état initial détermine le comportement initial de la propagation de l'erreur :
- On voit clairement comment les erreurs Z commutent initialement avec l'état initial |0⟩.
- Ce n'est que sur le qubit 6, où nous initialisons l'état propre +1 de la base X, qu'une erreur Z ne commute pas, alors qu'une erreur X commute.
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
backward_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)Output:
Aperçu des limites fusionnées sans taux de bruit appris
Cette merged_bounds fonction détermine le point du circuit où le passage des limites arrière aux limites avant minimise le biais total estimé sur l'observable souhaitée. Ce biais est calculé comme la somme des contributions « vers l'arrière » pour tous les emplacements de bruit situés avant ce point, à laquelle s'ajoutent les contributions « vers l'avant » pour tous les emplacements de bruit situés après celui-ci. Actuellement, cette opération est effectuée de manière uniforme pour tous les qubits.
Le moment optimal pour passer des limites arrière aux limites avant dépend des taux de bruit appris. À ce stade, nous n'avons encore déterminé aucun taux : noise_model_rates a été initialisé à None pour chaque couche, ce qui merge_bounds correspond à des taux de bruit uniformes. Les limites fusionnées présentées ici ne constituent donc qu'un aperçu; nous les recalculons à l'aide des taux réels appris à l'étape 3.
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
noise_model_rates,
)Output:
Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
Visualiser le SLC pour une inspection manuelle
Une fois les limites arrière et avant resserrées fusionnées, le comportement des SLC combinés apparaît clairement :
- La fonction ci-dessus indique qu'une partition est choisie à partir de laquelle on passe de limites arrière à des limites avant plus strictes.
- On constate ci-dessous que les SLC contiennent désormais des bornes arrière partielles et des bornes avant partiellement resserrées.
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
merged_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)Output:
Étape 3 : Exécuter
Dans cette section, nous entamons la partie du flux de travail qui fait appel à un dispositif quantique réel. Cette méthode d'atténuation des erreurs basée sur l'apprentissage comporte deux étapes :
- Apprenez à reconnaître ce bruit en utilisant
NoiseLearnerV3. - Mettre en œuvre un circuit d'atténuation des erreurs à l'aide des
samplomaticframeworks etExecutor.
Grâce aux erreurs bornées générées par notre circuit quantique, nous déterminons les taux de bruit associés afin de hiérarchiser notre budget d'erreurs, de calculer la surcharge liée à l'échantillonnage et d'exécuter le programme sur un QPU.
a. Découvrez les indices de bruit
L'algorithme d'apprentissage du bruit caractérise les processus de bruit affectant les portes d'un ou plusieurs circuits d'intérêt, en s'appuyant sur le modèle de bruit de Pauli-Lindblad clairsemé. Cette run() méthode lance une tâche d'apprentissage du bruit pour les couches à deux qubits uniques fournies, en utilisant les options spécifiées dans la configuration du « noise-learner ». Ces options permettent de contrôler la stratégie de « Pauli-twirling », le nombre de randomisations et de tentatives, les profondeurs d'apprentissage et la post-sélection.
Nous choisissons également délibérément le niveau de profondeur de l'apprentissage. Une observation pratique concernant l'atténuation basée sur l'apprentissage samplomatic montre qu'il est très avantageux que la profondeur d'apprentissage maximale corresponde à la profondeur du circuit que l'on souhaite atténuer. Étant donné que les NLv3layer_pair_depths s sont mesurées par paires de couches (une couche et son inverse), nous fixons la valeur la plus profonde à la moitié de la profondeur de la couche à deux qubits du circuit.
post_selection_enabled = True# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. NLv3 ``layer_pair_depths`` are measured in layer pairs (a layer
# plus its inverse), so the deepest value is half the circuit's two-qubit-layer
# depth. Learning to this depth markedly improves the quality of the mitigation.
#
# We measure the two-qubit-layer depth on the pre-boxed ISA circuit: after
# boxing, every two-qubit gate is hidden inside a full-width ``BoxOp``, so a
# ``num_qubits == 2`` filter on ``boxed_circuit`` matches nothing (and
# ``QuantumCircuit.depth`` does not recurse into boxes).
depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2 # dividing by 2 since we want pairs
# Use a fixed schedule of learning depths, but drop any that exceed the circuit's
# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never
# learn deeper than the circuit being mitigated.
candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]
layer_pair_depths = sorted(
{d for d in candidate_depths if d < max_layer_pair_depth}
| {max_layer_pair_depth}
)
noise_learner_options = {
"num_randomizations": 64,
"shots_per_randomization": 128,
"layer_pair_depths": layer_pair_depths,
"post_selection": {
"enable": post_selection_enabled,
"strategy": "edge",
"x_pulse_type": "rx",
},
"environment": {"job_tags": ["TUT_SLC"]},
}
noise_learner = NoiseLearnerV3(backend, noise_learner_options)noise_learner_job = noise_learner.run(unique_2q_instructions)noise_learner_result = noise_learner_job.result()if post_selection_enabled:
print(
"Minimum fraction of shots kept for noise learning experiments: ",
end="",
)
print(
f"{min([min(d.values()) for d in [nlr.metadata['post_selection']['fraction_kept'] for nlr in noise_learner_result[:2]]]):.2f}"
)Output:
Minimum fraction of shots kept for noise learning experiments: 0.71
# Get a dict mapping each InjectNoise.ref to its learned PauliLindbladMap
refs_2_plm = noise_learner_result.to_dict(
unique_2q_instructions, require_refs=False
)b.i. Mettre à jour les limites fusionnées en fonction des taux de bruit réels appris
Maintenant que le modèle de bruit spécifique a été appris, nous pouvons appliquer les taux de bruit appris aux limites de bruit prédites et déterminer ainsi définitivement quelles limites ont le plus d'impact sur la minimisation du biais.
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
refs_2_plm,
)Output:
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
b.ii. Calculer le local_scales pour l'exécution matérielle
compute_local_scales examine chaque erreur de bruit possible dans le circuit et évalue dans quelle mesure cette erreur pourrait fausser la mesure finale, ainsi que le coût que représenterait sa correction. Il classe ensuite les erreurs en fonction de l'intérêt qu'il y a à les corriger et sélectionne le sous-ensemble qui réduit au maximum le biais, tout en restant dans les limites du budget alloué au coût d'échantillonnage (ou en atteignant la précision souhaitée). On obtient ainsi un ensemble de facteurs d'échelle indiquant quelles erreurs seront activement corrigées et lesquelles ne le seront pas (local_scales), ainsi que la surcharge totale prévue liée au coût d'échantillonnage (sampling_costs) et le biais résiduel (residual_bias_bound).
La possibilité de contrôler le biais résiduel souhaité est une caractéristique essentielle de la mise en œuvre du PEC dans le cadre du SLC. Alors que, dans la mise en œuvre initiale, la surcharge d'échantillonnage visait toujours un biais nul, nous pouvons désormais ajuster la surcharge d'échantillonnage requise en acceptant un compromis sur le biais résiduel attendu. Cela permet à l'utilisateur de respecter un budget d'échantillonnage fixe, ce qui peut s'avérer particulièrement utile lors de la création d'un premier prototype de flux de travail.
id_map = map_modifier_ref_to_ref(boxed_circuit)summed_rates = 0.0
for box_id, noise_id in id_map.items():
learned_plm = refs_2_plm[noise_id]
summed_rates += np.sum(learned_plm.rates)
# print(f"{box_id}:\tgamma = {np.exp(2 * summed_rates):1.6e}\tsampling cost = {np.exp(4 * summed_rates):1.6e}")
total_gamma = np.exp(2 * summed_rates)
print(
f"Full PEC gamma={total_gamma}, sampling cost (gamma^2) = {total_gamma**2}"
)Output:
Full PEC gamma=12.750876317157042, sampling cost (gamma^2) = 162.58484685543633
biases = []
costs = []
for bias in [0.0] + np.arange(0.001, 0.102, 0.01).tolist():
_, cost_, bias_ = compute_local_scales(
boxed_circuit,
merged_bounds,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=bias,
)
biases.append(bias_)
costs.append(cost_)Trouver un compromis entre la charge liée à l'échantillonnage et le biais résiduel
xticks = np.arange(0, 11)
fig, ax = plt.subplots()
ax.scatter(
[0], [total_gamma**2], marker="D", c="tab:orange", label="full PEC"
)
ax.plot(
100 * np.array(biases),
np.array(costs),
"o-",
c="tab:blue",
label="PEC+SLC",
)
ax.set_yscale("log")
ax.set_xticks(xticks, [f"{x:.1f}" for x in xticks])
ax.set_xlabel("Remaining bias [%]")
ax.set_ylabel(r"Sampling overhead, $\gamma^2$")
ax.grid()
ax.legend()
fig.suptitle("PEC sampling overhead reduction due to SLC")Output:
Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')
chosen_bias_thres = 0.1local_scales, sampling_cost, residual_bias_bound = compute_local_scales(
boxed_circuit,
merged_bounds,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=chosen_bias_thres,
)
print(
f"PEC+SLC sampling cost (gamma^2) = {sampling_cost} "
f"w/ remaining bias = {100 * residual_bias_bound:.1f}%"
)Output:
PEC+SLC sampling cost (gamma^2) = 48.13646509622307 w/ remaining bias = 10.0%
c. Exécutez le circuit qui vous intéresse avec antinoise
c.i. Préparez le circuit modèle à l'aide de samplex
Le samplex est le résultat de la build méthode de Samplomatic, qui encode toutes les informations nécessaires à la génération de paramètres aléatoires pour template_circuit. Celles-ci servent ensuite à configurer les QuantumProgram objets, qui sont à leur tour exécutés sur un QPU avec la Executor primitive. samplexChacune d'entre elles QuantumProgram peut contenir plusieurs éléments, que l'on peut considérer comme des paires (a, template b).
Pour plus de détails, consultez le tutoriel « Hello » de Samplomatic.
# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)# Set up postselection if it's been enabled
if post_selection_enabled:
# Set up post selection PM (to add PS instructions)
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
final_template_circuit = post_selection_pm.run(template_circuit)
else:
final_template_circuit = template_circuitc.ii. Configurez le QuantumProgram
num_randomizations = 4096
shots_per_randomization = 64
chunk_size = 256# Set up QuantumProgram
program = QuantumProgram(shots=shots_per_randomization, noise_maps=refs_2_plm)
# no EM
# Collect up a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(0) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
# plain PEC
# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
# PEC+SLC
# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
samplex_inputs |= {"local_scales": local_scales}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)c.iii. Exécuter le programme à l'aide de la Executor primitive
executor = Executor(backend)job_exec = executor.run(program)results_exec = job_exec.result()Étape 4 : Post-traitement
Lorsque nous calculons la valeur attendue finale qui nous intéresse à l'aide de executor_expectation_values, nous mettons en œuvre quelques techniques de post-traitement afin de garantir l'obtention de résultats de la meilleure qualité possible. Tout d'abord, nous appliquons notre méthode d'extinction des erreurs de lecture par rotation (TREX), qui tient compte de toutes les erreurs survenant au cours du processus de lecture. Nous corrigeons ensuite les erreurs dues au bruit non markovien sur nos backends Heron à l'aide d'une méthode de post-sélection. Cette méthode consiste à mesurer les qubits actifs et les qubits spectateurs, puis à appliquer une rotation lente à chaque qubit, avant de procéder à une nouvelle mesure. Lorsque les deux mesures ne confirment pas, comme prévu, l'inversion d'un qubit, ces essais sont écartés en appliquant un mask issu du PostSelector. Dans le cadre du calcul du masque, il est possible de définir une stratégie spécifique pour filtrer en fonction des nœuds à un seul qubit ou des arêtes spectatrices voisines, ce qui peut influencer à la fois le nombre de tirs filtrés et la qualité des résultats.
measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_scale_factors = trex_factors(measurement_noise_map, reverser_virt)post_selection_strategy = "node"def post_process_conv(datum, steps=16, gamma=None, ps=False, trex=False):
meas = datum["meas"]
flips = datum["measurement_flips.meas"]
signs = datum.get("pauli_signs", None)
meas_basis_axis = None
avg_axis = 0
mask = None
if ps and post_selection_enabled:
# Post-select the results
post_selector = PostSelector.from_circuit(
circuit=final_template_circuit, coupling_map=backend.coupling_map
)
# Compute the ps mask for filtering results
mask = post_selector.compute_mask(
datum, strategy=post_selection_strategy
)
# Compute fraction of shots kept from post selection
total_num_shots = num_randomizations * shots_per_randomization
ps_ratio = np.sum(mask) * 100 / total_num_shots / len(bases_canon)
print(
f"With {post_selection_strategy}-based post selection ({ps_ratio:.1f}% of shots kept):"
)
results = []
for i in range(steps, num_randomizations + 1, steps):
# Compute mitigated expvals w/out post-selection
res = executor_expectation_values(
meas[:i],
reverser_virt,
meas_basis_axis,
avg_axis=avg_axis,
measurement_flips=flips[:i],
pauli_signs=signs[:i] if signs is not None else None,
postselect_mask=mask[:i] if mask is not None else None,
rescale_factors=trex_scale_factors if trex else None,
gamma_factor=gamma,
)
results.append(res[0])
return resultsgamma_pec = gamma_from_noisy_boxes(refs_2_plm, id_map)
gamma_slc = gamma_from_noisy_boxes(refs_2_plm, id_map, local_scales)steps = 16results = {}
for label, result_idx, gamma, use_ps, use_trex in [
("PEC", 1, gamma_pec, True, True),
("PEC+SLC", 2, gamma_slc, True, True),
("Unmitigated", 0, None, False, False),
]:
res = post_process_conv(
results_exec[result_idx],
steps=steps,
gamma=gamma,
ps=use_ps,
trex=use_trex,
)
results[label] = resOutput:
With node-based post selection (24.1% of shots kept):
With node-based post selection (24.2% of shots kept):
L'analyse des résultats expérimentaux nous permet de comparer directement le comportement des différentes approches : PEC, PEC associée à SLC, et le scénario de référence sans mesures d'atténuation. Quelques détails spécifiques à souligner :
- Le résultat brut se situe bien en dehors de la fourchette de biais de 10 % (voir 0.35 ) et n'est pas influencé par le nombre de randomisations.
- Sur cet appareil, la méthode PEC complète entraîne une surcharge d'échantillonnage de . Après d'importantes oscillations initiales, l'estimation PEC simple récupère le signal et se stabilise autour de 1.1, dépassant légèrement la valeur exacte. Il convient de noter que la marge de biais de 10 % ne s'applique qu'à l'estimateur PEC+SLC : le PEC seul est en principe non biaisé; cet écart résiduel n'est donc pas couvert par une garantie de biais, mais reflète plutôt des fluctuations statistiques et des imperfections du modèle de bruit appris, ce qui est raisonnable compte tenu de l'ampleur du problème.
- La méthode SLC réduit encore davantage la surcharge d' ~3.4-fold (à , pour une limite de biais résiduel d'environ 10 %). La valeur de PEC+SLC fluctue également fortement après quelques randomisations, atteignant même initialement une région non physique, mais son estimation converge progressivement vers la valeur exacte, pour se stabiliser près de 1.0, bien à l'intérieur de la bande. Elle atteint, voire dépasse, la précision de la méthode PEC classique pour un coût d'échantillonnage représentant environ un tiers de celui-ci, ce qui démontre l'intérêt de l'ombrage par cône de lumière.
- Les barres d'erreur des deux estimations corrigées se rétrécissent à mesure que le nombre de randomisations augmente; pour un nombre fixe de randomisations, l'estimateur PEC+SLC présente la variance statistique la plus faible, ce qui correspond à son « » plus faible.
Il convient de noter que les taux de bruit appris (et donc les surcoûts et les estimations corrigées) dépendent du backend et de son étalonnage au moment de l'exécution; ainsi, une nouvelle exécution de ce notebook peut donner lieu à des valeurs quantitativement différentes.
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
ax.axhline(1.0, color="black", label="Exact")
ax.fill_between(
[-50, 4100], -10, 0, color="grey", alpha=0.25, label="Unphysical"
)
ax.fill_between([-50, 4100], 1, 10, color="grey", alpha=0.25)
ax.fill_between(
[-50, 4100], 0.9, 1.1, color="red", alpha=0.25, label="10% bias"
)
for label, res in results.items():
ax.errorbar(
list(range(steps, num_randomizations + 1, steps)),
[r[0] for r in res],
yerr=[r[1] for r in res],
alpha=0.75,
marker="o",
linestyle="",
markerfacecolor="none",
label=label,
)
ax.set_ylabel(r"$\langle X_{6}Z_{13}\rangle$")
ax.set_xlabel("# randomizations")
ax.grid()
ax.legend(ncols=2)
ax.set_ylim([-0.1, 2.0])
ax.set_xlim([-50, 4100])Output:
(-50.0, 4100.0)
Etapes suivantes
Si ce travail vous a paru intéressant, les documents suivants pourraient vous intéresser :
- Combiner les options d'atténuation des erreurs avec la primitive Estimator
- Atténuation des erreurs à grande échelle par amplification probabiliste des erreurs
- La documentation
qiskit-addon-slcetsamplomaticla documentation - Ombrage par cône de lumière pour l'atténuation des erreurs quantiques par accélération classique


