Lewati ke konten utama

Broadcasting Executor

Data yang diberikan ke primitif Executor bisa disusun dalam berbagai bentuk untuk memberikan fleksibilitas dalam beban kerja melalui broadcasting. Panduan ini menjelaskan cara Executor menangani input dan output array menggunakan semantik broadcasting. Memahami konsep-konsep ini akan membantumu menyapu nilai parameter secara efisien, menggabungkan beberapa konfigurasi, dan menginterpretasikan bentuk data yang dikembalikan.

catatan

Contoh-contoh dalam topik ini tidak bisa dijalankan sendiri. Mereka mengasumsikan kamu telah mendefinisikan circuit yang sesuai, menggunakan pass manager Samplomatic untuk menambahkan kotak dan anotasi, dan menggunakan metode build Samplomatic untuk mendapatkan template circuit dan samplex untuk setiap blok kode, sesuai kebutuhan.

Contoh quickstart

Contoh ini mendemonstrasikan ide inti. Ini membuat circuit parametrik dan lima konfigurasi parameter yang berbeda. Executor menjalankan semua lima konfigurasi dan mengembalikan data yang diorganisir berdasarkan konfigurasi, dengan satu hasil per classical register dalam setiap item program kuantum.

Sisa panduan ini merujuk kembali ke contoh ini untuk menjelaskan cara kerjanya dan cara membangun sweep yang lebih kompleks, termasuk randomisasi berbasis Samplomatic dan 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"]

Sumbu intrinsik dan ekstrinsik

Broadcasting hanya berlaku untuk sumbu ekstrinsik. Sumbu intrinsik selalu dipertahankan seperti yang ditentukan.

  • Sumbu intrinsik (paling kanan): Ditentukan oleh tipe data. Misalnya, jika circuit-mu memiliki tiga parameter, maka nilai parameter membutuhkan tiga angka, memberikan bentuk intrinsik (3,).

  • Sumbu ekstrinsik (paling kiri): Dimensi sweep-mu. Ini menentukan berapa banyak konfigurasi yang ingin kamu jalankan.

Tipe inputBentuk intrinsikContoh bentuk penuh
Nilai parameter (n parameter)(n,)(5, 3) untuk lima konfigurasi dan tiga parameter
Input skalar (misalnya, faktor skala noise)()(4,) untuk empat konfigurasi
Observable (jika berlaku)bervariasiBergantung pada tipe observable

Contoh

Pertimbangkan circuit dengan dua parameter yang ingin kamu sweep pada grid 4x3 konfigurasi, memvariasikan nilai parameter dan faktor skala noise:

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,
},
)

Bentuk-bentuknya adalah sebagai berikut:

InputBentuk penuhBentuk ekstrinsikBentuk intrinsik
parameter_values(4, 1, 2)(4, 1)(2,)
noise_scale(3,)(3,)()
BroadcastNone(4, 3)None

Bentuk array output

Array output mengikuti pola ekstrinsik/intrinsik yang sama:

  • Bentuk ekstrinsik: Cocok dengan bentuk broadcast dari semua input
  • Bentuk intrinsik: Ditentukan oleh tipe output

Output yang paling umum adalah data bitstring dari pengukuran, yang diformat sebagai array nilai boolean:

Tipe outputBentuk intrinsikDeskripsi
Data classical register(num_shots, creg_size)Data bitstring dari pengukuran

Contoh

Jika kamu menyediakan input dengan bentuk ekstrinsik (4, 1) dan (3,), bentuk ekstrinsik broadcast adalah (4, 3). Kode berikut menggunakan circuit dengan 1024 shot dan classical register 4-bit (seperti yang didefinisikan dalam contoh Quickstart):

# 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)
catatan

Setiap konfigurasi menjalankan jumlah shot penuh yang ditentukan dalam program kuantum. Shot tidak dibagi di antara konfigurasi. Misalnya, jika kamu meminta 1024 shot dan memiliki 10 konfigurasi, setiap konfigurasi menjalankan 1024 shot (total 10.240 shot yang dieksekusi).

Randomisasi dan parameter shape

Saat menggunakan samplex, setiap elemen bentuk ekstrinsik sesuai dengan eksekusi circuit yang independen. Samplex biasanya menyuntikkan keacakan (misalnya, gate twirling) ke dalam setiap eksekusi, sehingga bahkan tanpa secara eksplisit meminta beberapa randomisasi, setiap elemen menerima realisasi acak.

Kamu bisa menggunakan parameter shape untuk menambah bentuk ekstrinsik item, secara efektif menambahkan sumbu yang secara khusus sesuai dengan merandominasi konfigurasi yang sama berkali-kali. Parameter ini harus dapat di-broadcast dari bentuk yang tersirat dalam samplex_arguments-mu. Sumbu di mana shape melebihi bentuk yang tersirat menghitung randomisasi independen tambahan.

Tanpa sumbu randomisasi eksplisit

Jika kamu menghilangkan shape (atau menetapkannya agar cocok dengan bentuk input-mu), kamu mendapatkan satu eksekusi per konfigurasi input. Setiap eksekusi masih dirandomisasi oleh samplex, tetapi dengan hanya satu realisasi acak kamu tidak mendapat manfaat dari averaging atas beberapa randomisasi.

catatan

Jika kamu terbiasa mengaktifkan twirling dengan flag sederhana seperti twirling=True, perhatikan bahwa Executor mengharuskanmu secara eksplisit meminta beberapa randomisasi dengan argumen shape agar rutinitas pasca-pemrosesanmu mendapat manfaat dari averaging atas beberapa randomisasi. Satu randomisasi (default saat shape dihilangkan) menerapkan gate acak tetapi biasanya tidak memberikan keuntungan dibandingkan menjalankan circuit dasar tanpa randomisasi.

Contoh berikut mendemonstrasikan perilaku default:

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)

Sumbu randomisasi tunggal

Untuk menjalankan beberapa randomisasi per konfigurasi, perluas shape dengan sumbu tambahan. Misalnya, kode berikut menjalankan 20 randomisasi untuk masing-masing dari 10 konfigurasi parameter:

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)

Beberapa sumbu randomisasi

Kamu bisa mengorganisir randomisasi ke dalam grid multi-dimensi. Ini berguna untuk analisis terstruktur, misalnya, memisahkan randomisasi berdasarkan tipe atau mengelompokkannya untuk pemrosesan statistik.

Di sini, bentuk ekstrinsik input (10,) di-broadcast ke bentuk yang diminta (2, 14, 10), dengan sumbu 0 dan 1 diisi oleh randomisasi independen.

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)

Cara shape dan bentuk input berinteraksi

Parameter shape harus dapat di-broadcast dari bentuk ekstrinsik input-mu. Artinya:

  • Bentuk input dengan dimensi ukuran-1 dapat diperluas agar cocok dengan shape.
  • Bentuk input harus sejajar dari kanan dengan shape.
  • Sumbu dalam shape yang melebihi dimensi input menghitung randomisasi.

Perhatikan bahwa shape dapat mengandung dimensi ukuran-1 yang diperluas agar cocok dengan dimensi input, seperti yang diilustrasikan di baris terakhir tabel berikut.

Contoh:

Ekstrinsik inputShapeHasil
(10,)(10,)10 konfigurasi, 1 randomisasi masing-masing
(10,)(5, 10)10 konfigurasi, 5 randomisasi masing-masing
(10,)(2, 3, 10)10 konfigurasi, 2×3=6 randomisasi masing-masing
(4, 1)(4, 5)4 konfigurasi, 5 randomisasi masing-masing
(4, 3)(2, 4, 3)4×3=12 konfigurasi, 2 randomisasi masing-masing
(4, 3)(2, 1, 3)4×3=12 konfigurasi, 2 randomisasi masing-masing (1 meluas ke 4)

Indeks ke dalam hasil

Dengan sumbu randomisasi, kamu bisa mengindeks ke dalam kombinasi randomisasi/parameter tertentu:

# 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))

Pola umum

Sweep satu parameter

Gunakan kode seperti berikut untuk menyapu satu parameter sambil mempertahankan yang lain tetap:

# 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)

Membuat sweep grid 2D

Untuk membuat grid pada tiga parameter:

# 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,)

Menggabungkan beberapa input

Saat menggabungkan input dengan bentuk intrinsik yang berbeda, sejajarkan dimensi ekstrinsik menggunakan sumbu ukuran-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)

Langkah selanjutnya

Rekomendasi