Skip to main content
IBM Quantum Platform

Diffusion par exécuteur

Les données transmises à la primitive Executor peuvent être organisées sous diverses formes afin d'offrir une grande souplesse dans le traitement d'une charge de travail grâce à la diffusion. Ce guide explique comment l'Executor gère les entrées et les sorties de tableaux à l'aide de la sémantique de diffusion. La compréhension de ces concepts vous aidera à parcourir efficacement les valeurs des paramètres, à combiner plusieurs configurations et à interpréter la structure des données renvoyées.

Note

Les exemples présentés dans cette rubrique ne peuvent pas être exécutés séparément. Ils partent du principe que vous avez défini les circuits appropriés, utilisé le gestionnaire de passages Samplomatic pour ajouter des encadrés et des annotations, et utilisé la méthode Samplomatic build pour obtenir un circuit type et un samplex pour chaque bloc de code, si nécessaire.


Exemple de démarrage rapide

Cet exemple illustre le principe de base. Il génère un circuit paramétrique et cinq configurations de paramètres différentes. L'exécuteur exécute les cinq configurations et renvoie les données classées par configuration, avec un résultat par registre classique dans chaque élément du programme quantique.

La suite de ce guide s'appuie sur cet exemple pour expliquer le fonctionnement de cette fonctionnalité et comment créer des balayages plus complexes, notamment en utilisant la randomisation et les entrées basées sur Samplomatic.

import numpy as np
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit.transpiler import generate_preset_pass_manager

# A circuit with 2 parameters
# This circuit is used throughout the rest of this guide.
circuit = QuantumCircuit(4)
circuit.rx(Parameter("a"), 0)
circuit.rx(Parameter("b"), 1)
circuit.h(2)
circuit.cx(2, 3)
circuit.measure_all()

# 5 different parameter configurations (shape: 5 configurations × 2 parameters)
parameter_values = np.linspace(0, np.pi, 10).reshape(5, 2)

# Initialize the service and choose a backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Transpile to ISA circuit
preset_pass_manager = generate_preset_pass_manager(
    backend=backend,
    optimization_level=3,
)
isa_circuit = preset_pass_manager.run(circuit)

# This program is used throughout the rest of this guide.
program = QuantumProgram(shots=1024)
program.append_circuit_item(isa_circuit, circuit_arguments=parameter_values)

# initialize an Executor with default options
executor = Executor(mode=backend)

# Run and get results
result = executor.run(program).result()

# result is a list with one entry per program item
# result[0] is a dict mapping classical register names to data arrays
# Output bool arrays have shape (5, 1024, 4)
#   5 = number of parameter configurations
#   1024 = number of shots
#   4 = bits in the classical register
result[0]["meas"]

Axes intrinsèques et extrinsèques

La diffusion ne s'applique qu'aux axes extrinsèques. Les axes intrinsèques sont toujours conservés, comme indiqué.

  • Axes intrinsèques (à l'extrême droite) : déterminés par le type de données. Par exemple, si votre circuit comporte trois paramètres, les valeurs de ces paramètres nécessitent trois nombres, ce qui donne une forme intrinsèque de (3,).

  • Axes extrinsèques (à l'extrême gauche) : les dimensions de votre balayage. Ces paramètres déterminent le nombre de configurations que vous souhaitez exécuter.

Type d'entrée
Forme intrinsèque
Exemple de forme complète
Valeurs des paramètres (n paramètres)(n,)(5, 3) pour cinq configurations et trois paramètres
Entrées scalaires (par exemple, échelle de bruit)()(4,) pour quatre configurations
Observables (le cas échéant)fluctuationsCela dépend du type de l'observable

Exemple

Considérons un circuit comportant deux paramètres que l'on souhaite balayer sur une grille de configurations d' 4x3, en faisant varier les valeurs des paramètres et un facteur d'échelle de bruit :

import numpy as np

# Parameter values: 4 configurations along axis 0, intrinsic shape (2,)
# Full shape: (4, 1, 2) - the "1" allows broadcasting with noise_scale
parameter_values = np.array([
    [[0.1, 0.2]],
    [[0.3, 0.4]],
    [[0.5, 0.6]],
    [[0.7, 0.8]],
])  # shape (4, 1, 2)

# Noise scale: 3 configurations, intrinsic shape () (scalar)
# Full shape: (3,)
noise_scale = np.array([0.8, 1.0, 1.2])  # shape (3,)

# Extrinsic shapes: (4, 1) and (3,) → broadcast to (4, 3)
# Result: 12 total configurations in a 4×3 grid
program.append_samplex_item(
    template_circuit,
    samplex=samplex,
    samplex_arguments={
        "parameter_values": parameter_values,
        "noise_scales.mod_ref1": noise_scale,
    },
)

Les formes sont les suivantes :

Entrée
Forme complète
Forme extrinsèque
Forme intrinsèque
parameter_values(4, 1, 2)(4, 1)(2,)
noise_scale(3,)(3,)()
Diffusion radiotéléviséeAucun(4, 3)Aucun

Formats des tableaux de sortie

Les tableaux de sortie suivent le même schéma extrinsèque/intrinsèque :

  • Forme extrinsèque : correspond à la forme de diffusion de toutes les entrées
  • Forme intrinsèque : déterminée par le type de sortie

Le format de sortie le plus courant est celui des données de mesure sous forme de chaîne de bits, qui sont présentées sous la forme d'un tableau de valeurs booléennes :

Type de sortie
Forme intrinsèque
Description
Données de registre classiques(nombre_de_tirs, taille_de_la_zone_de_cible)Données sous forme de chaînes binaires issues des mesures

Exemple

Si vous fournissez des entrées avec des formes (4, 1) extrinsèques et (3,), la forme extrinsèque diffusée est (4, 3). Le code suivant utilise un circuit de 1 024 pas et un registre classique de 4 bits (tel que défini dans l'exemple du guide de démarrage rapide ) :

# Input extrinsic shapes: (4, 1) and (3,) → (4, 3)
# Output for classical register "meas":
#   extrinsic: (4, 3)
#   intrinsic: (1024, 4)  - shots × bits
#   full shape: (4, 3, 1024, 4)

result = executor.run(program).result()
meas_data = result[0]["meas"]  # result[0] for first program item
print(meas_data.shape)  # (4, 3, 1024, 4)

# Access a specific configuration
config_2_1 = meas_data[2, 1, :, :]  # shape (1024, 4)
Note

Chaque configuration exécute le nombre total de tirs spécifié dans le programme quantique. Les prises ne sont pas réparties entre les différentes configurations. Par exemple, si vous demandez 1 024 prises et que vous disposez de 10 configurations, chacune d'entre elles exécute 1 024 prises (soit un total de 10 240 prises exécutées).


La randomisation et le shape paramètre

Lorsqu'on utilise un samplex, chaque élément de la forme extrinsèque correspond à une exécution de circuit indépendante. Le samplex introduit généralement un élément aléatoire (par exemple, une rotation de porte) dans chaque exécution; ainsi, même sans demander explicitement plusieurs randomisations, chaque élément bénéficie d'une réalisation aléatoire.

Vous pouvez utiliser le shape paramètre pour enrichir la forme extrinsèque de l'élément, ce qui revient en fait à ajouter des axes qui correspondent précisément à la répétition aléatoire de la même configuration à plusieurs reprises. Il doit être diffusable à partir de la forme implicite dans votre samplex_arguments. Les axes pour lesquels shape dépasse la forme implicite énumèrent des randomisations indépendantes supplémentaires.

Aucun axe de randomisation explicite

Si vous ne le précisez pas shape (ou si vous le configurez pour qu'il corresponde à vos formes d'entrée), vous obtenez une exécution par configuration d'entrée. Chaque exécution est toujours aléatoire grâce au samplex, mais avec une seule réalisation aléatoire, vous ne bénéficiez pas de la moyenne obtenue à partir de plusieurs aléatisations.

Note

Si vous avez l'habitude d'activer le twirling à l'aide d'un simple indicateur tel que twirling=True, notez que l'Executor vous oblige à demander explicitement plusieurs randomisations à l'aide de shape l'argument afin de permettre à vos routines de post-traitement de bénéficier des avantages du calcul de la moyenne sur plusieurs randomisations. Une randomisation simple (la valeur par défaut lorsque shape est omis) applique des portes aléatoires, mais n'offre généralement aucun avantage par rapport à l'exécution du circuit de base sans randomisation.

L'exemple suivant illustre le comportement par défaut :

program.append_samplex_item(
    template_circuit,
    samplex=samplex,
    samplex_arguments={
        "parameter_values": np.random.rand(10, 2),  # extrinsic (10,)
    },
    # shape defaults to (10,) - one randomized execution per config
)
# Output shape for "meas": (10, num_shots, creg_size)

Axe de randomisation unique

Pour effectuer plusieurs randomisations par configuration, étendez la figure en ajoutant des axes supplémentaires. Par exemple, le code suivant effectue 20 simulations aléatoires pour chacune des 10 configurations de paramètres :

program.append_samplex_item(
    template_circuit,
    samplex=samplex,
    samplex_arguments={
        "parameter_values": np.random.rand(10, 2),  # extrinsic (10,)
    },
    shape=(20, 10),  # 20 randomizations × 10 configurations
)
# Output shape for "meas": (20, 10, num_shots, creg_size)

Plusieurs axes de randomisation

Vous pouvez organiser les randomisations sous la forme d'une grille multidimensionnelle. Cela s'avère utile pour une analyse structurée, par exemple pour classer les randomisations par type ou les regrouper en vue d'un traitement statistique.

(2, 14, 10)Ici, la forme (10,) extrinsèque d'entrée est diffusée vers la forme demandée, les axes 0 et 1 étant remplis par des randomisations indépendantes.

program.append_samplex_item(
    template_circuit,
    samplex=samplex,
    samplex_arguments={
        "parameter_values": np.random.rand(10, 2),  # extrinsic (10,)
    },
    # 2×14=28 randomizations per configuration, 10 configurations
    # Or you could set shape=(28, 10) for the same effect
    shape=(2, 14, 10),  
)
# Output shape for "meas": (2, 14, 10, num_shots, creg_size)

Comment shape les formes d'entrée interagissent

Le shape paramètre doit pouvoir être diffusé à partir de vos formes extrinsèques d'entrée. Ce qui signifie :

  • shapeLes formes d'entrée dont les dimensions sont d' size-1 s peuvent s'étendre pour s'adapter.
  • Les formes saisies doivent s'aligner à partir de la droite avec shape.
  • Les shape axes qui dépassent les dimensions d'entrée énumèrent des randomisations.

Notez que shape peut contenir des dimensions d' size-1 qui s'adaptent aux dimensions de l'entrée, comme l'illustre la dernière ligne du tableau ci-dessous.

Exemples :

Entrée extrinsèque
Forme
Résultat
(10,)(10,)10 configurations, chacune avec une randomisation
(10,)(5, 10)10 configurations, avec 5 randomisations chacune
(10,)(2, 3, 10)10 configurations, 2 × 3 = 6 randomisations chacune
(4, 1)(4, 5)4 configurations, avec 5 randomisations chacune
(4, 3)(2, 4, 3)4 × 3 = 12 configurations, avec 2 randomisations pour chacune
(4, 3)(2, 1, 3)4 × 3 = 12 combinaisons, avec 2 randomisations pour chacune (le 1 se décompose en 4)

Accéder aux résultats

Grâce aux axes de randomisation, vous pouvez accéder à des combinaisons spécifiques de randomisation et de paramètres :

# Using shape=(2, 14, 10) with input extrinsic shape (10,), and 
# 1024 shots and 4 classical registers.
result = executor.run(program).result()
meas_data = result[0]["meas"]  # shape (2, 14, 10, 1024, 4)

# Get all shots for randomization (0, 7) and parameter config 3
specific = meas_data[0, 7, 3, :, :]  # shape (1024, 4)

# Average over all randomizations for parameter config 5 on bit 2
averaged = meas_data[:, :, 5, :, 2].mean(axis=(0, 1))

Modèles courants

Balayer un seul paramètre

Utilisez un code similaire à celui-ci pour faire varier un paramètre tout en conservant les autres fixes :

# Circuit has 2 parameters, sweep first one over 20 values
sweep_values = np.linspace(0, 2*np.pi, 20)

parameter_values = np.column_stack([
    sweep_values,
    np.full(20, 0.5),
])  # shape (20, 2)

Création d'un balayage de grille « 2D »

Pour créer une grille à partir de trois paramètres :

# Sweep param 0 over 10 values, param 1 over 8 values, param 2 fixed
p0 = np.linspace(0, np.pi, 10)[:, np.newaxis, np.newaxis]  # (10, 1, 1)
p1 = np.linspace(0, np.pi, 8)[np.newaxis, :, np.newaxis]   # (1, 8, 1)
p2 = np.array([[[0.5]]])                                   # (1, 1, 1)

parameter_values = np.broadcast_arrays(p0, p1, p2)
parameter_values = np.stack(parameter_values, axis=-1).squeeze()  # (10, 8, 3)

# Extrinsic shape: (10, 8), intrinsic shape: (3,)

Combiner plusieurs sources d'entrée

Lorsque vous assemblez des éléments de forme intrinsèque différente, alignez leurs dimensions extrinsèques à l'aide des axes d' size-1 :

# 4 parameter configurations, 3 noise scales → 4×3 = 12 total configurations
parameter_values = np.random.rand(4, 1, 2)  # extrinsic (4, 1), intrinsic (2,)
noise_scale = np.array([0.8, 1.0, 1.2])     # extrinsic (3,), intrinsic ()

# Broadcasted extrinsic shape: (4, 3)

Etapes suivantes

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