Transmissão do executor
Os dados fornecidos à primitiva Executor podem ser organizados em diversos formatos para oferecer flexibilidade a uma carga de trabalho por meio da difusão. Este guia explica como o Executor lida com entradas e saídas de matrizes utilizando a semântica de difusão. Compreender esses conceitos ajudará você a percorrer com eficiência os valores dos parâmetros, combinar várias configurações e interpretar a forma dos dados retornados.
Os exemplos deste tópico não podem ser executados isoladamente. Eles presumem que você já definiu os circuitos adequados, utilizou o gerenciador de passagens do Samplomatic para adicionar caixas e anotações e utilizou o método do Samplomatic build para obter um circuito modelo e um samplex para cada bloco de código, conforme necessário.
Exemplo de introdução rápida
Este exemplo ilustra a ideia central. Ele cria um circuito paramétrico e cinco configurações de parâmetros diferentes. O executor executa todas as cinco configurações e retorna os dados organizados por configuração, com um resultado por registro clássico em cada item do programa quântico.
O restante deste guia se baseia neste exemplo para explicar como isso funciona e como criar varreduras mais complexas, incluindo randomização e entradas baseadas no 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"]Eixos intrínsecos e extrínsecos
A transmissão se aplica apenas aos eixos extrínsecos. Os eixos intrínsecos são sempre preservados conforme especificado.
-
Eixos intrínsecos (à direita): determinados pelo tipo de dados. Por exemplo, se o seu circuito tiver três parâmetros, os valores desses parâmetros exigirão três números, resultando em uma forma intrínseca de
(3,). -
Eixos extrínsecos (mais à esquerda): suas dimensões de varredura. Isso define quantas configurações você deseja executar.
Tipo de entrada | Forma intrínseca | Exemplo de forma completa |
|---|---|---|
| Valores dos parâmetros (n parâmetros) | (n,) | (5, 3) para cinco configurações e três parâmetros |
| Entradas escalares (por exemplo, escala de ruído) | () | (4,) para quatro configurações |
| Observáveis (se aplicável) | varia | Depende do tipo do observável |
Exemplo
Considere um circuito com dois parâmetros que você deseja varrer por uma grade de configurações de tipo “ 4x3 ”, variando os valores dos parâmetros e um fator de escala de ruído:
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,
},
)As formas são as seguintes:
Entrada | Em plena forma | Forma extrínseca | Forma intrínseca |
|---|---|---|---|
parameter_values | (4, 1, 2) | (4, 1) | (2,) |
noise_scale | (3,) | (3,) | () |
| Transmissão | Nenhum | (4, 3) | Nenhum |
Formatos das matrizes de saída
As matrizes de saída seguem o mesmo padrão extrínseco/intrínseco:
- Forma extrínseca: corresponde à forma de transmissão de todas as entradas
- Formato intrínseco: determinado pelo tipo de saída
O resultado mais comum são dados em forma de sequência de bits provenientes das medições, que são formatados como uma matriz de valores booleanos:
Tipo de saída | Forma intrínseca | Descrição |
|---|---|---|
| Dados do registro clássico | (número_de_tiros, tamanho_do_círculo) | Dados em cadeia de bits provenientes de medições |
Exemplo
Se você fornecer entradas com formas (4, 1) extrínsecas e (3,), a forma extrínseca de difusão
é (4, 3). O código a seguir utiliza um circuito com 1024 disparos e um registro clássico de 4 bits (conforme definido no exemplo do Guia Rápido ):
# 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)Cada configuração executa o número total de disparos especificado no programa quântico. As doses não são distribuídas entre as configurações. Por exemplo, se você solicitar 1.024 testes e tiver 10 configurações, cada configuração executará 1.024 testes (total de 10.240 testes executados).
Randomização e o shape parâmetro
Ao utilizar um samplex, cada elemento da forma extrínseca corresponde a uma execução de circuito independente. O samplex normalmente introduz aleatoriedade (por exemplo, rotação de portas) em cada execução; assim, mesmo sem solicitar explicitamente múltiplas aleatorizações, cada elemento recebe uma realização aleatória.
Você pode usar o shape parâmetro para ampliar a forma extrínseca do item,
efetivamente adicionando eixos que correspondem especificamente à repetição aleatória da mesma configuração várias vezes. Deve ser
transmitível a partir do formato implícito no seu samplex_arguments. Os eixos em que shape excede
a forma implícita enumeram aleatorizações independentes adicionais.
Não há eixos de randomização explícitos
Se você omitir shape (ou definir de modo a corresponder às suas formas de entrada), terá uma execução por
configuração de entrada. Cada execução ainda é aleatorizada pelo samplex, mas com apenas uma
única realização aleatória, você não se beneficia da média obtida a partir de várias aleatorizações.
Se você está acostumado a ativar o twirling com um simples sinalizador como twirling=True, observe que
o Executor exige que você solicite explicitamente múltiplas randomizações com o shape argumento
para permitir que suas rotinas de pós-processamento obtenham os benefícios da média calculada sobre múltiplas
randomizações. Uma única randomização (a configuração padrão quando shape é omitida) aplica
portas aleatórias, mas normalmente não oferece nenhuma vantagem em relação à execução do circuito básico sem
randomização.
O exemplo a seguir ilustra o comportamento padrão:
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)Eixo único de randomização
Para executar várias randomizações por configuração, amplie a figura com eixos adicionais. Por exemplo, o código a seguir executa 20 randomizações para cada uma das 10 configurações de parâmetros:
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)Vários eixos de randomização
Você pode organizar as randomizações em uma grade multidimensional. Isso é útil para análises estruturadas, por exemplo, para separar as randomizações por tipo ou agrupá-las para processamento estatístico.
(2, 14, 10)Aqui, a forma (10,) extrínseca de entrada é transmitida para a forma solicitada,
com os eixos 0 e 1 preenchidos por aleatorizações independentes.
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)Como shape as formas de entrada interagem
O shape parâmetro deve poder ser transmitido a partir das suas formas extrínsecas de entrada. Ou seja:
- As formas inseridas com dimensões d size-1 e podem se expandir para se adequarem a
shape. - As formas inseridas devem ser alinhadas à direita com
shape. - Os
shapeeixos que excedem as dimensões de entrada enumeram as randomizações.
Observe que shape pode conter dimensões do tipo “ size-1 ”
que se expandem para se adequarem às dimensões de entrada, conforme ilustrado na última linha da tabela a seguir.
Exemplos:
Entrada extrínseca | Forma | Resultado |
|---|---|---|
| (10,) | (10,) | 10 configurações, com 1 randomização para cada uma |
| (10,) | (5, 10) | 10 configurações, com 5 randomizações cada |
| (10,) | (2, 3, 10) | 10 configurações, 2×3=6 randomizações cada |
| (4, 1) | (4, 5) | 4 configurações, com 5 randomizações cada |
| (4, 3) | (2, 4, 3) | 4×3=12 configurações, com 2 randomizações cada |
| (4, 3) | (2, 1, 3) | 4×3=12 configurações, com 2 randomizações cada (o 1 se expande para 4) |
Índice dos resultados
Com os eixos de aleatorização, é possível acessar combinações específicas de aleatorização/parâmetros:
# 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))Padrões comuns
Variação de um único parâmetro
Use um código como o seguinte para variar um parâmetro enquanto mantém os demais fixos:
# 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)Criação de uma varredura de grade do tipo “ 2D ”
Para criar uma grade com base em três parâmetros:
# 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,)Combinação de várias entradas
Ao combinar entradas com formas intrínsecas diferentes, alinhe as dimensões extrínsecas utilizando os eixos 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)Próximas etapas
- Consulte a visão geral da transmissão.
- Compreender as entradas e saídas do Executor.