Trasmissione dell'esecutore
I dati forniti alla primitiva Executor possono essere organizzati in diverse configurazioni per garantire flessibilità nel carico di lavoro tramite la trasmissione diffusa. Questa guida spiega come Executor gestisce gli input e gli output degli array utilizzando la semantica del broadcasting. Comprendere questi concetti ti aiuterà a scorrere in modo efficiente i valori dei parametri, a combinare più configurazioni e a interpretare la struttura dei dati restituiti.
Gli esempi riportati in questa sezione non possono essere eseguiti autonomamente. Si presume che abbiate definito i circuiti appropriati, che abbiate utilizzato il gestore dei passaggi di Samplomatic per aggiungere riquadri e annotazioni e che abbiate utilizzato il metodo Samplomatic build per ottenere un circuito modello e un samplex per ciascun blocco di codice, se necessario.
Esempio di avvio rapido
Questo esempio illustra il concetto fondamentale. Crea un circuito parametrico e cinque diverse configurazioni dei parametri. L'esecutore esegue tutte e cinque le configurazioni e restituisce i dati organizzati per configurazione, con un risultato per ogni registro classico in ogni elemento del programma quantistico.
Il resto di questa guida fa riferimento a questo esempio per spiegare come funziona e come creare sweep più complessi, compresa la randomizzazione basata su Samplomatic e gli input.
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"]Assi intrinseci ed estrinseci
La trasmissione si applica solo agli assi estrinseci. Gli assi intrinseci vengono sempre mantenuti come specificato.
-
Assi intrinseci (all'estrema destra): determinati dal tipo di dati. Ad esempio, se il circuito ha tre parametri, i valori dei parametri richiedono tre numeri, il che determina una forma intrinseca di
(3,). -
Assi estrinseci (all'estrema sinistra): le dimensioni di scansione. Queste opzioni definiscono il numero di configurazioni che si desidera eseguire.
Tipo di input | Forma intrinseca | Esempio di forma completa |
|---|---|---|
| Valori dei parametri (n parametri) | (n,) | (5, 3) per cinque configurazioni e tre parametri |
| Ingressi scalari (ad esempio, scala del rumore) | () | (4) per quattro configurazioni |
| Osservabili (se applicabile) | varianze | Dipende dal tipo dell'osservabile |
Esempio
Si consideri un circuito con due parametri che si desidera variare su una griglia di configurazioni 4x3, modificando i valori dei parametri e un fattore di scala del rumore:
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,
},
)Le forme sono le seguenti:
Immissione | Forma completa | Forma estrinseca | Forma intrinseca |
|---|---|---|---|
parameter_values | (4, 1, 2) | (4, 1) | (2,) |
noise_scale | (3,) | (3,) | () |
| Trasmissione | Nessuna | (4, 3) | Nessuna |
Formati degli array di output
Gli array di output seguono lo stesso schema estrinseco/intrinseco:
- Forma estrinseca: corrisponde alla forma di trasmissione di tutti gli input
- Forma intrinseca: determinata dal tipo di output
Il risultato più comune è costituito dai dati delle misurazioni sotto forma di stringa di bit, formattati come un array di valori booleani:
Tipo di output | Forma intrinseca | Descrizione |
|---|---|---|
| Dati relativi al registro classico | (numero_scatti, dimensione_creg) | Dati in formato stringa di bit ricavati dalle misurazioni |
Esempio
Se si specificano input con forme (4, 1) estrinseche e (3,), la forma estrinseca di trasmissione
è (4, 3). Il codice seguente utilizza un circuito con 1024 impulsi e un registro classico a 4 bit (come definito nell'esempio della Guida rapida ):
# 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)Ogni configurazione esegue il numero totale di misurazioni specificato nel programma quantistico. I colpi non sono ripartiti tra le diverse configurazioni. Ad esempio, se si richiedono 1024 scatti e si dispone di 10 configurazioni, ciascuna configurazione esegue 1024 scatti (per un totale di 10.240 scatti eseguiti).
Randomizzazione e il shape parametro
Quando si utilizza un samplex, ogni elemento della forma estrinseca corrisponde all'esecuzione di un circuito indipendente. Il samplex introduce solitamente un elemento di casualità (ad esempio, la rotazione dei gate) in ogni esecuzione, quindi, anche senza richiedere esplicitamente più randomizzazioni, ogni elemento ottiene una realizzazione casuale.
È possibile utilizzare il shape parametro per ampliare la forma estrinseca dell'elemento,
aggiungendo di fatto assi che corrispondono specificatamente alla ripetizione casuale della stessa configurazione più volte. Deve essere
trasmissibile a partire dalla forma implicita nel tuo samplex_arguments. Gli assi in cui shape supera
la forma implicita enumerano ulteriori randomizzazioni indipendenti.
Nessun asse di randomizzazione esplicito
Se lo si omette shape (o lo si imposta in modo che corrisponda alle forme di input), si ottiene un'esecuzione per
ogni configurazione di input. Ogni esecuzione è comunque randomizzata dal samplex, ma con una sola
realizzazione casuale non si beneficia della media ottenuta da più randomizzazioni.
Se sei abituato ad abilitare il twirling con un semplice flag come twirling=True, tieni presente che
Executor richiede di richiedere esplicitamente più randomizzazioni tramite l'argomento shape
per consentire alle tue routine di post-elaborazione di trarre vantaggio dalla media calcolata su più
randomizzazioni. Si applica una singola randomizzazione (l'impostazione predefinita quando shape viene omesso)
che utilizza porte casuali, ma in genere non offre alcun vantaggio rispetto all'esecuzione del circuito di base senza
randomizzazione.
L'esempio seguente illustra il comportamento predefinito:
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)Asse di randomizzazione singolo
Per eseguire più randomizzazioni per ogni configurazione, estendere la figura aggiungendo ulteriori assi. Ad esempio, il codice seguente esegue 20 simulazioni per ciascuna delle 10 configurazioni dei parametri:
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)Assi di randomizzazione multipli
È possibile organizzare le randomizzazioni in una griglia multidimensionale. Ciò risulta utile per l'analisi strutturata, ad esempio per separare le randomizzazioni in base al tipo o raggrupparle ai fini dell'elaborazione statistica.
(2, 14, 10)In questo caso, la forma (10,) estrinseca di input viene trasmessa alla forma richiesta,
con gli assi 0 e 1 popolati da variabili casuali indipendenti.
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)Come shape interagiscono le forme di input
Il shape parametro deve essere trasmissibile dalle forme estrinseche di input. Ciò significa che:
shapeLe forme inserite con dimensioni " size-1 " possono espandersi per adattarsi.- Le forme inserite devono essere allineate a destra con
shape. - Gli assi che superano le dimensioni di
shapeinput generano una serie di randomizzazioni.
Si noti che shape può contenere dimensioni di tipo " size-1 "
che si espandono per adattarsi alle dimensioni dell'input, come illustrato nell'ultima riga della tabella seguente.
Esempi:
Input estrinseco | Forma | Risultato |
|---|---|---|
| (10,) | (10,) | 10 configurazioni, ciascuna con una randomizzazione |
| (10,) | (5, 10) | 10 configurazioni, ciascuna con 5 randomizzazioni |
| (10,) | (2, 3, 10) | 10 configurazioni, 2×3=6 randomizzazioni ciascuna |
| (4, 1) | (4, 5) | 4 configurazioni, ciascuna con 5 randomizzazioni |
| (4, 3) | (2, 4, 3) | 4×3=12 combinazioni, con 2 randomizzazioni per ciascuna |
| (4, 3) | (2, 1, 3) | 4×3=12 combinazioni, con 2 randomizzazioni per ciascuna (il 1 si espande a 4) |
Indice dei risultati
Grazie agli assi di randomizzazione, è possibile accedere a specifiche combinazioni di randomizzazione e parametri:
# 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))Modelli ricorrenti
Analizzare un singolo parametro
Utilizza un codice simile al seguente per variare un parametro mantenendo fissi gli altri:
# 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)Creazione di una scansione a griglia " 2D "
Per creare una griglia basata su tre parametri:
# 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,)Combinazione di più input
Quando si combinano elementi con forme intrinseche diverse, allineare le dimensioni estrinseche utilizzando gli assi dell' 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)Passi successivi
- Consulta la panoramica sulle trasmissioni.
- Comprendere gli input e gli output di Executor.