Sari la conținutul principal

Migrează de la Sampler la Executor

Acest ghid descrie cum să muți sarcinile de eșantionare cuantică de la primitiva Sampler IBM Quantum® la primitiva Executor.

Versiune beta

Primitiva Executor face parte din modelul de execuție direcționată. Toate componentele modelului de execuție direcționată sunt în prezent în versiune beta și ar putea să nu fie stabile. Ești invitat să le testezi și să oferi feedback deschizând un issue în depozitele GitHub Samplomatic sau qiskit-ibm-runtime.

Ar trebui să migrezi?

Nu toată lumea ar trebui să migreze de la Sampler la Executor. Există multe diferențe între primitive, dar următoarele îndrumări te pot ajuta să decizi dacă să migrezi:

Migrează la Executor dacă ești un om de știință în informația cuantică care rulează experimente la scară de utilitate și ai nevoie de control fin, reproductibil asupra tehnicilor precum răsucirea Pauli (Pauli twirling), învățarea și injectarea modelului de zgomot, și schimbări de bază — sau dacă ai nevoie de una dintre capacitățile suplimentare oferite de Executor.

Continuă să folosești Sampler dacă vrei o interfață simplă, la nivel înalt, și vrei ca primitiva să gestioneze suprimarea și atenuarea erorilor pentru tine.

Limitări și avertismente

Deoarece Executor și modelul de execuție direcționată sunt în versiune beta, reține următoarele înainte de a decide să migrezi:

  • Fără suport pentru simulator încă: Spre deosebire de Sampler, care are o implementare AerSampler în qiskit-aer pentru simulare locală, în prezent nu există un backend de simulator pentru Executor. Suportul pentru simulator este așteptat să apară în curând. Între timp, poți totuși să inspectezi și să eșantionezi circuitul șablon local pentru a-ți valida fluxul de lucru înainte de a-l trimite către hardware.

  • Acest ghid acoperă doar Sampler, nu și Estimator. Migrarea de la Estimator la Executor este considerabil mai complexă decât migrarea de la Sampler deoarece Estimator calculează valori de așteptare, nu returnează eșantioane brute. Reproducerea comportamentului Estimator cu Executor necesită procesare suplimentară ulterioară. Funcțiile utilitare pentru a ajuta migrarea de la Estimator la Executor sunt încă în dezvoltare, așa că acest ghid descrie intenționat doar fluxul de lucru Sampler.

Diferențe cheie între Executor și Sampler

Sampler și Executor eșantionează amândouă registrele de ieșire ale circuitelor cuantice, dar vizează utilizatori diferiți:

  • Sampler este o abstracție la nivel înalt. Are următoarele caracteristici:

    • Are suprimarea erorilor integrată (decuplare dinamică și răsucire).

    • Ia decizii implicite pentru tine.

    • Este proiectat astfel încât dezvoltatorii de algoritmi să se poată concentra pe inovație, nu pe conversia datelor.

  • Executor face parte din modelul de execuție direcționată. Diferă de Sampler în multe privințe și are următoarele caracteristici:

    • Nu are suprimare sau atenuare a erorilor integrată. În schimb, îți capturezi intenția de proiectare pe partea de client (folosind adnotări de circuit și un samplex), iar generarea costisitoare de variante de circuit este mutată pe partea de server.

    • Nu ia decizii implicite. Urmează exact directivele tale, oferind control și transparență totale.

    • Executor și Samplomatic împreună expun capacități suplimentare pe care Sampler nu le oferă, inclusiv (dar fără a se limita la) următoarele:

      • Mai multe grupuri de răsucire: Samplomatic îți permite să alegi ce grup de răsucire să aplici pentru fiecare box, în loc să fii limitat la strategia unică pe care Sampler o aplică pentru tine. De asemenea, suportă grupuri de răsucire altele decât Pauli, cum ar fi grupul de răsucire "local_c1".
      • Măsurători kerneled și classified împreună: Setarea QuantumProgram.meas_level = "both" (adăugată în qiskit-ibm-runtime v0.48.0) solicită ca atât măsurătorile classified, cât și cele kerneled să fie prezente în rezultate, în loc de a alege un singur tip de măsurare per job.
      • Răsucire pentru circuite cu porți fracționare: Executor poate aplica răsucire circuitelor care conțin porți fracționare.
      • Atenuare a erorilor fină, compozabilă: De exemplu, alegerea stratului de circuit de atenuat și ajustarea ratelor de zgomot injectate în circuit.
      Note
      • Se așteaptă ca noile capacități viitoare să fie lansate pentru Executor mai întâi și ar putea să nu fie portate la Sampler. Dacă te bazezi pe accesul la cele mai recente funcționalități, Executor este alegerea mai sigură pentru viitor.
      • Pachetul de bază Qiskit nu oferă încă o clasă de bază pentru primitiva Executor (o oferă pentru SamplerV2).

Cartografiere conceptuală

Următorul tabel demonstrează cum se mapează conceptele Sampler la Executor.

ConceptSamplerExecutor
Importfrom qiskit_ibm_runtime import SamplerV2from qiskit_ibm_runtime import Executor
IntrareListă de PUB-uri (tuple)Un QuantumProgram de obiecte QuantumProgramItem
Circuit și parametriTuplul (circuit, params, shots)program.append_circuit_item(circuit, circuit_arguments=...)
RăsucireTwirlingOptionsExplicit prin boxuri adnotate și un samplex (append_samplex_item)
Apel de rularesampler.run([pub, ...])executor.run(program)
Tip de rezultatPrimitiveResult de SamplerPubResultQuantumProgramResult (iterabil)
Accesare dateresult[0].data.<register> (BitArray)result[0]["<register>"] (np.ndarray)
Gestionare zgomotOpțiuni integrateTrebuie compus manual (adnotări, samplex, NoiseLearnerV3)

Prezentare generală a pașilor de migrare

  1. Instalează Samplomatic.

  2. Schimbă importurile.

  3. Înlocuiește tuplurile PUB.

  4. Schimbă modul în care sunt exprimate exploatările (shots).

  5. Actualizează alte opțiuni după cum este necesar.

  6. Actualizează comanda run.

  7. Actualizează analiza rezultatelor.

  8. Anulează răsucirea.

Pasul 1. Instalează pachetele necesare

Executor și modelul de execuție direcționată necesită pachetul samplomatic:

pip install qiskit qiskit-ibm-runtime samplomatic

# For visualization support:
# pip install samplomatic[vis]
Note despre versiuni
  • qiskit-ibm-runtime v0.48.0 este recomandat deoarece adaugă opțiunea meas_level = "both" și grupul de răsucire local_c1.
  • qiskit >= 2.3.0 este necesar.
  • samplomatic >= 0.18.0 este necesar.

Pasul 2. Schimbă importurile

Sampler:

from qiskit_ibm_runtime import SamplerV2 as Sampler

Executor:

from qiskit_ibm_runtime import Executor, QuantumProgram

Pasul 3. Înlocuiește tuplurile PUB cu un QuantumProgram

În loc să treci o listă de tuple (PUB-uri), când folosești Executor, construiești un QuantumProgram și adaugi elemente la el.

Un QuantumProgram acceptă elemente de tip circuit și elemente de tip samplex:

  • append_circuit_item: Adaugă un CircuitItem, care este un circuit și (opțional) valorile parametrilor săi. Este executat ca atare, fără nicio randomizare.

    Folosește acest lucru când vrei doar să eșantionezi un circuit, exact așa cum ar face Sampler cu un PUB care nu are răsucire; de exemplu, când trimiți un job de eșantionare simplu, sau când ai inclus deja manual orice variante dorite.

  • append_samplex_item: Adaugă un samplexItem, care este un circuit șablon plus un samplex care generează seturi de parametri randomizate pe partea de server.

    Folosește acest lucru când vrei ca conținutul circuitului să fie randomizat. Cazul principal este cu răsucirea (a porții sau a măsurării) sau injectarea de zgomot. Această capacitate înlocuiește răsucirea integrată a Sampler.

Un singur QuantumProgram poate accepta ambele tipuri de elemente; fiecare element adăugat este executat ca o sarcină independentă și produce propria intrare în rezultate. În general, folosește append_circuit_item când circuitul tău nu trebuie randomizat. Altfel, folosește append_samplex_item.

Secțiunile următoare arată fiecare pe rând: circuite parametrizate care folosesc append_circuit_item, și migrarea răsucirii folosind append_samplex_item.

În exemplele de cod următoare, isa_circuit se referă la circuitul care a fost transpilat pentru a se conforma cu Arhitectura Setului de Instrucțiuni (ISA) a backend-ului țintă. Acest isa_circuit conține doi parametri.

Pasul 3a. Migrează circuite parametrizate

Cu Sampler, valorile parametrilor sunt al doilea element al tuplului PUB. Cu Executor, transmite-le ca circuit_arguments către append_circuit_item.

Sampler:

params = np.random.rand(10, circuit.num_parameters) # 10 parameter sets
pubs = (isa_circuit, params)

Executor

program = QuantumProgram(shots=1024)
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(10, circuit.num_parameters), # 10 sets
)

# CircuitItem result shape: (parameter_sets, shots, register_bits) -> (10, 1024, 2)
result_0 = result[0]["meas"]

Pasul 3b. Migrează răsucirea integrată la adnotări explicite

Aceasta este cea mai semnificativă schimbare. Sampler aplică răsucirea pentru tine folosind opțiuni. Cu Executor, declari acea intenție explicit folosind boxuri adnotate și un samplex (din Samplomatic).

Sampler (răsucire folosind opțiuni):

sampler = Sampler(mode=backend)
sampler.options.twirling.enable_gates = True
sampler.options.twirling.enable_measure = True

Executor (răsucire folosind boxuri și un samplex):

from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager

# 1. Group gates and measurements into annotated boxes with twirling annotations
boxes_pm = generate_boxing_pass_manager(
enable_gates=True, # gate twirling
enable_measures=True, # measurement twirling
)
boxed_circuit = boxes_pm.run(isa_circuit)

# 2. Build the (template circuit, samplex) pair.
# The template circuit's single-qubit gates are replaced by parameterized gates;
# the samplex encodes how to generate the randomized parameters at runtime.
template_circuit, samplex = build(boxed_circuit)

# 3. Append as a samplex item, specifying the number of randomizations
program = QuantumProgram(shots=1024)
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
"parameter_values": np.random.rand(10, 2), # original circuit params
},
shape=(28, 10), # 28 randomizations x 10 parameter sets
)

Deoarece circuitul șablon și samplex-ul sunt construite pe partea de client, poți să le inspectezi și eșantionezi local pentru a verifica rezultatul înainte de a trimite ceva către hardware.

Verificare: Eșantionează circuitul șablon local

Poți extrage randomizări din samplex și le poți lega de circuitul șablon pentru a confirma că samplex-ul produce valorile de parametri așteptate. Valorile parametrilor returnate de samplex.sample sunt direct compatibile cu parametrii circuitului șablon.

# Check which inputs the samplex requires (for the twirling example above,
# this is just the original circuit's parameter values).
print(samplex.inputs())

# Bind the required inputs, then draw a few randomizations locally.
inputs = samplex.inputs().bind(
parameter_values=np.random.rand(2), # one set of the original circuit's params
)
outputs = samplex.sample(inputs, num_randomizations=3)

# Assign one randomization's parameter values to the template circuit and inspect it.
bound_template = template_circuit.assign_parameters(outputs["parameter_values"][0])
bound_template.draw("mpl", idle_wires=False)

Pentru a merge mai departe, poți verifica dacă fiecare randomizare este echivalentă logic cu circuitul original, de exemplu, convertind ambele în obiecte Operator și comparând implementările lor unitare (după ce ai luat în calcul corecțiile outputs["measurement_flips.<register>"] care anulează răsucirea măsurării), sau comparând valori de așteptare dintr-o rulare locală StatevectorSampler sau StatevectorEstimator. Vezi ghidul Samplomatic Intrări și ieșiri samplex pentru un parcurs complet.

Pasul 4. Schimbă modul în care sunt solicitate exploatările (shots)

Mută shots de la PUB la QuantumProgram(shots=...). În Executor, shots se aplică întregului job. Trimite mai multe joburi dacă ai nevoie de numere diferite de shots.

Sampler:

# Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit, None, 25)])

Executor:

# Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)

Pasul 5. Actualizează opțiunile după cum este necesar

Sunt mai puține opțiuni disponibile pentru Executor decât pentru Sampler, deoarece alegerile de atenuare a erorilor trăiesc acum în adnotările și samplex-ul tău, în loc de opțiuni.

Există de asemenea o diferență structurală în locul unde trăiesc setările.

  • Cu Sampler, totul, inclusiv alegerile care afectează post-procesarea rezultatelor, este configurat pe opțiunile primitivei sau în PUB.

  • Cu Executor, alegerile care afectează modul în care sunt formate și post-procesate rezultatele jobului sunt setate pe QuantumProgram, nu pe ExecutorOptions.

Examples:

SamplerExecutor
shotsQuantumProgram(shots=...)
meas_typeQuantumProgram(meas_level=...)

ExecutorOptions conține doar setări de execuție și de mediu la nivel inferior care nu schimbă structura datelor returnate. Are trei grupuri de nivel superior:

De remarcat că opțiunile twirling și dynamical_decoupling există în Sampler, dar nu în Executor. În schimb, valorile acelor opțiuni sunt exprimate prin modelul de execuție dirijată.

Example:

from qiskit_ibm_runtime import Executor, ExecutorOptions

options = ExecutorOptions(
environment={"log_level": "INFO"},
execution={"init_qubits": True},
)
# or mutate after construction:
options = ExecutorOptions()
options.environment.log_level = "INFO"
options.execution.init_qubits = True

executor = Executor(mode=backend, options=options)

Pasul 6. Actualizează comanda run

Intrarea pentru un job Executor este programul, în loc de PUB-uri.

Sampler:

# Submit a job
sampler.run([(isa_circuit, parameter_values)])

Executor:

# Submit a job
executor.run(program)

Pasul 7. Modifică modul în care accesezi rezultatele

În Executor, rezultatele sunt array-uri NumPy, nu obiecte BitArray. Folosește șirul de nume ca index (result[0]["meas"]) și primești înapoi un np.ndarray. Nu trebuie să reții calea de atribut .data.<register>.

Pentru a actualiza de la Sampler la Executor, schimbă result[i].data.<reg> (BitArray) în result[i]["<reg>"] (np.ndarray), apoi rescrie post-procesarea bazată pe get_counts ca operații NumPy.

SarcinăSamplerExecutor
Obține date de registruresult[0].data.measresult[0]["meas"]
Tip de dateBitArraynp.ndarray
Dicționar de countsresult[0].data.meas.get_counts()Post-procesează manual array-ul
Registre multipleresult[0].data.<name> per registruresult[0]["<name>"] per registru
Forma array-ului CircuitItem-(parameter_sets, shots, register_bits)
Forma array-ului SamplexItem-(randomizations, parameter_sets, shots, register_bits)
Anulează twirling-ul de măsurareAutomatresult[i]["measurement_flips.<name>"] + XOR
notă

BitArray din Sampler oferă funcții ajutătoare (get_counts, slice_bits, slice_shots, expectation_values, și măști de post-selecție). Executor returnează array-uri NumPy brute, astfel încât poți efectua această post-procesare cu operații NumPy standard.

Pasul 8. Gestionează rezultatele twirled (corecții bit-flip)

Când aplici twirling-ul măsurătorii printr-un SamplexItem, Executor returnează măsurătorile brute (twirled) plus corecțiile bit-flip necesare pentru a anula twirling-ul. Trebuie să le aplici manual; nimic nu este corectat implicit.

Când folosești Executor, anulează twirling-ul explicit folosind corecțiile measurement_flips.<reg> și un XOR, așa cum se arată în exemplul următor:

# SamplexItem result shape: (randomizations, parameter_sets, shots, register_bits)
result_1 = result[1]["meas"] # example: (28, 10, 1024, 2)

# Bit-flip corrections to undo measurement twirling
flips_1 = result[1]["measurement_flips.meas"] # example: (28, 10, 1, 2)

# Undo the twirling through classical XOR (broadcasts over the shots axis)
unflipped_result_1 = result_1 ^ flips_1

Nu există un pas echivalent în Sampler deoarece acesta anulează twirling-ul pentru tine.

Exemplu complet: Migrarea unui job de sampling de bază

Sampler

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler

# 1. Account + backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# 2. Circuit
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.measure_all()

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

# 4. Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit,)], shots=25)
result = job.result()

# 5. Access results: a BitArray keyed by register name
counts = result[0].data.meas.get_counts()

Executor

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

# 1. Account + backend (unchanged)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# 2. Circuit (unchanged)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.measure_all()

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

# 4. Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)

# 5. Run
executor = Executor(mode=backend)
job = executor.run(program)
result = job.result()

# 6. Access results: a plain np.ndarray keyed by register name
# shape = (shots, register_bits)
meas = result[0]["meas"]

Pașii următori