Sari la conținutul principal

Migrează la primitivele Qiskit Runtime V2

avertizare

Primitivele originale (denumite primitive V1), V1 Sampler și V1 Estimator, au fost deprecate în qiskit-ibm-runtime 0.23. Suportul lor a fost eliminat pe 15 august 2024.

Odată cu deprecierea primitivelor V1, tot codul ar trebui migrat pentru a utiliza interfețele V2. Acest ghid descrie ce s-a schimbat în primitivele Qiskit Runtime V2 (disponibile cu qiskit-ibm-runtime 0.21.0) și motivele acestor schimbări, descrie în detaliu fiecare primitivă nouă și oferă exemple care te ajută să migrezi codul de la primitivele moștenite la primitivele V2. Exemplele din ghid utilizează toate primitivele Qiskit Runtime, dar, în general, aceleași modificări se aplică și celorlalte implementări de primitive. Funcțiile unice ale Qiskit Runtime, cum ar fi atenuarea erorilor, rămân specifice Qiskit Runtime.

Pentru informații despre modificările aduse primitivelor de referință Qiskit (acum numite primitive statevector), consultă secțiunea qiskit.primitives din pagina de modificări ale funcționalităților Qiskit 1.0. Consultă StatevectorSampler și StatevectorEstimator pentru implementările de referință ale primitivelor V2.

Prezentare generală

Versiunea 2 a primitivelor este introdusă cu o nouă clasă de bază atât pentru Sampler, cât și pentru Estimator (BaseSamplerV2 și BaseEstimatorV2), împreună cu noi tipuri pentru intrările și ieșirile acestora.

Noua interfață îți permite să specifici un singur Circuit și mai multe observabile (dacă folosești Estimator) și seturi de valori de parametri pentru acel Circuit, astfel încât parcurgerea seturilor de valori de parametri și a observabilelor poate fi specificată eficient. Anterior, trebuia să specifici același Circuit de mai multe ori pentru a corespunde dimensiunii datelor ce urmau să fie combinate. De asemenea, deși poți în continuare să folosești resilience_level (dacă folosești Estimator) ca buton simplu de reglaj, primitivele V2 îți oferă flexibilitatea de a activa sau dezactiva individual metodele de atenuare/suprimare a erorilor, pentru a le personaliza în funcție de nevoile tale.

Pentru a reduce timpul total de execuție a job-ului, primitivele V2 acceptă doar Circuite și observabile care utilizează instrucțiuni suportate de QPU-ul țintă (unitate de procesare cuantică). Astfel de Circuite și observabile sunt denumite Circuite și observabile cu arhitectură set de instrucțiuni (ISA). Primitivele V2 nu efectuează operații de layout, rutare și traducere. Consultă documentația de transpilare pentru instrucțiuni de transformare a Circuitelor.

Sampler V2 este simplificat pentru a se concentra pe sarcina sa de bază de eșantionare a registrului de ieșire din execuția Circuitelor cuantice. Returnează eșantioanele, al căror tip este definit de program, fără ponderi. Datele de ieșire sunt, de asemenea, separate în funcție de numele registrelor de ieșire definite de program. Această modificare permite suportul viitor pentru Circuite cu flux de control clasic.

Consultă referința API EstimatorV2 și referința API SamplerV2 pentru detalii complete.

Modificări majore

Import

Din motive de compatibilitate retroactivă, trebuie să imporți explicit primitivele V2. Specificarea import <primitive>V2 as <primitive> nu este obligatorie, dar face mai ușoară tranziția codului la V2.

avertizare

După ce primitivele V1 nu mai sunt suportate, import <primitive> va importa versiunea V2 a primitivei specificate.

from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as Sampler

Intrare și ieșire

Intrare

Atât SamplerV2, cât și EstimatorV2 primesc unul sau mai multe blocuri unificate primitive (PUB) ca intrare. Fiecare PUB este un tuplu care conține un singur Circuit și datele difuzate către acel Circuit, care pot fi mai multe observabile și parametri. Fiecare PUB returnează un rezultat.

  • Formatul PUB pentru Sampler V2: (<circuit>, <parameter values>, <shots>), unde <parameter values> și <shots> sunt opționale.
  • Formatul PUB pentru Estimator V2: (<circuit>, <observables>, <parameter values>, <precision>), unde <parameter values> și <precision> sunt opționale. Regulile de difuzare Numpy sunt utilizate la combinarea observabilelor și a valorilor de parametri.

În plus, au fost efectuate următoarele modificări:

  • Estimator V2 a câștigat un argument precision în metoda run(), care specifică precizia țintită a estimărilor valorilor de așteptare.
  • Sampler V2 are argumentul shots în metoda sa run().
Exemple

Exemplu Estimator V2 care utilizează precision în run():

# Estimate expectation values for two PUBs, both with 0.05 precision.
estimator.run([(circuit1, obs_array1), (circuit2, obs_array_2)], precision=0.05)

Exemplu Sampler V2 care utilizează shots în run():

# Sample two circuits at 128 shots each.
sampler.run([circuit1, circuit2], shots=128)

# Sample two circuits at different amounts of shots.
# The "None"s are necessary as placeholders
# for the lack of parameter values in this example.
sampler.run([
(circuit1, None, 123),
(circuit2, None, 456),
])

Ieșire

Ieșirea este acum în formatul PubResult. Un PubResult reprezintă datele și metadatele rezultate din execuția unui singur PUB.

  • Estimator V2 continuă să returneze valori de așteptare.

  • Partea data a unui PubResult Estimator V2 conține atât valorile de așteptare, cât și erorile standard (stds). V1 returna varianța în metadate.

  • Sampler V2 returnează măsurători per-shot sub formă de șiruri de biți, în loc de distribuțiile quasi-probabilistice din interfața V1. Șirurile de biți arată rezultatele măsurătorilor, păstrând ordinea shot-urilor în care au fost măsurate.

  • Sampler V2 are metode utilitare precum get_counts() pentru a facilita migrarea.

  • Obiectele de rezultate ale Sampler V2 organizează datele în funcție de numele registrelor clasice ale Circuitelor de intrare, pentru compatibilitate cu Circuitele dinamice. În mod implicit, numele registrului clasic este meas, după cum se arată în exemplul următor. Când îți definești Circuitul, dacă creezi unul sau mai multe registre clasice cu un nume non-implicit, folosește acel nume pentru a obține rezultatele. Poți găsi numele registrului clasic executând <circuit_name>.cregs. De exemplu, qc.cregs.

    # Define a quantum circuit with 2 qubits
    circuit = QuantumCircuit(2)
    circuit.h(0)
    circuit.cx(0, 1)
    circuit.measure_all()
    circuit.draw()
            ┌───┐      ░ ┌─┐
    q_0: ┤ H ├──■───░─┤M├───
    └───┘┌─┴─┐ ░ └╥┘┌─┐
    q_1: ─────┤ X ├─░──╫─┤M├
    └───┘ ░ ║ └╥┘
    meas: 2/══════════════╩══╩═
    0 1

Exemple Estimator (intrare și ieșire)

# Estimator V1: Execute 1 circuit with 4 observables
job = estimator_v1.run([circuit] * 4, [obs1, obs2, obs3, obs4])
evs = job.result().values

# Estimator V2: Execute 1 circuit with 4 observables
job = estimator_v2.run([(circuit, [obs1, obs2, obs3, obs4])])
evs = job.result()[0].data.evs

Exemple Sampler (intrare și ieșire)

  # Sampler V1: Execute 1 circuit with 3 parameter sets
job = sampler_v1.run([circuit] * 3, [vals1, vals2, vals3])
dists = job.result().quasi_dists

# Sampler V2: Executing 1 circuit with 3 parameter sets
job = sampler_v2.run([(circuit, [vals1, vals2, vals3])])
counts = job.result()[0].data.meas.get_counts()

Exemplu care utilizează registre de ieșire diferite

from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler

alpha = ClassicalRegister(5, "alpha")
beta = ClassicalRegister(7, "beta")
qreg = QuantumRegister(12)

circuit = QuantumCircuit(qreg, alpha, beta)
circuit.h(0)
circuit.measure(qreg[:5], alpha)
circuit.measure(qreg[5:], beta)

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=12)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)

sampler = Sampler(backend)
job = sampler.run([isa_circuit])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
print(f" >> Counts for the alpha output register: {pub_result.data.alpha.get_counts()}")
print(f" >> Counts for the beta output register: {pub_result.data.beta.get_counts()}")

Options

Opțiunile sunt specificate diferit în primitivele V2, în următoarele moduri:

  • SamplerV2 și EstimatorV2 au acum clase de opțiuni separate. Poți vedea opțiunile disponibile și poți actualiza valorile opțiunilor în timpul sau după inițializarea primitivei.
  • În loc de metoda set_options(), opțiunile primitivelor V2 dispun de metoda update(), care aplică modificările atributului options.
  • Dacă nu specifici o valoare pentru o opțiune, aceasta primește valoarea specială Unset și se folosesc valorile implicite ale serverului.
  • Pentru primitivele V2, atributul options este de tipul Python dataclass. Poți folosi metoda built-in asdict pentru a-l converti într-un dicționar.

Consultă referința API pentru lista opțiunilor disponibile.

from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Setting options during primitive initialization
estimator = Estimator(backend, options={"resilience_level": 2})

# Setting options after primitive initialization
# This uses auto complete.
estimator.options.default_shots = 4000
# This does bulk update.
estimator.options.update(default_shots=4000, resilience_level=2)

# Print the dictionary format.
# Server defaults are used for unset options.
print(asdict(estimator.options))
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Setting options during primitive initialization
sampler = Sampler(backend, options={"default_shots": 4096})

# Setting options after primitive initialization
# This uses auto complete.
sampler.options.dynamical_decoupling.enable = True
# Turn on gate twirling. Requires qiskit_ibm_runtime 0.23.0 or later.
sampler.options.twirling.enable_gates = True

# This does bulk update. The value for default_shots is overridden if you specify shots with run() or in the PUB.
sampler.options.update(default_shots=1024, dynamical_decoupling={"sequence_type": "XpXm"})

# Print the dictionary format.
# Server defaults are used for unset options.
print(asdict(sampler.options))

Atenuarea și suprimarea erorilor

  • Deoarece Sampler V2 returnează eșantioane fără postprocesare, nu acceptă niveluri de reziliență.

  • Sampler V2 nu acceptă optimization_level.

  • Estimator V2 va renunța la suportul pentru optimization_level în jurul datei de 30 septembrie 2024.

  • Estimator V2 nu acceptă nivelul de reziliență 3. Acest lucru se datorează faptului că nivelul 3 în Estimator V1 folosește Probabilistic Error Cancellation (PEC), despre care s-a demonstrat că oferă rezultate nebiasate cu costul unui timp de procesare exponențial. Nivelul 3 a fost eliminat pentru a atrage atenția asupra acestui compromis. Poți folosi în continuare PEC ca metodă de atenuare a erorilor, specificând opțiunea pec_mitigation.

  • Estimator V2 acceptă resilience_level 0-2, conform tabelului următor. Aceste opțiuni sunt mai avansate decât echivalentele lor din V1. De asemenea, poți activa/dezactiva explicit metode individuale de atenuare/suprimare a erorilor.

    Nivel 1Nivel 2
    Measurement twirlingMeasurement twirling
    Readout error mitigationReadout error mitigation
    ZNE
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Setting options during primitive initialization
estimator = Estimator(backend)

# Set resilience_level to 0
estimator.options.resilience_level = 0

# Turn on measurement error mitigation
estimator.options.resilience.measure_mitigation = True
from qiskit_ibm_runtime import SamplerV2 as Sampler

sampler = Sampler(backend)
# Turn on dynamical decoupling with sequence XpXm.
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XpXm"

print(f">> dynamical decoupling sequence to use: {sampler.options.dynamical_decoupling.sequence_type}")

Transpilare

Primitivele V2 acceptă doar circuite care respectă Instruction Set Architecture (ISA) a unui backend specific. Deoarece primitivele nu efectuează operații de layout, rutare și traducere, opțiunile de transpilare corespunzătoare din V1 nu sunt acceptate.

Starea job-ului

Primitivele V2 au o nouă clasă RuntimeJobV2, care moștenește din BasePrimitiveJob. Metoda status() a acestei noi clase returnează un șir de caractere în loc de o enumerare JobStatus din Qiskit. Consultă referința API RuntimeJobV2 pentru detalii.

job = estimator.run(...)

# check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() == "RUNNING"}")

Pași pentru migrarea la Estimator V2

  1. Înlocuiește from qiskit_ibm_runtime import Estimator cu from qiskit_ibm_runtime import EstimatorV2 as Estimator.

  2. Elimină toate instrucțiunile from qiskit_ibm_runtime import Options, deoarece clasa Options nu este folosită de primitivele V2. În schimb, poți transmite opțiunile ca un dicționar la inițializarea clasei EstimatorV2 (de exemplu, estimator = Estimator(backend, options={"dynamical_decoupling": {"enable": True}})), sau le poți seta după inițializare:

    estimator = Estimator(backend)
    estimator.options.dynamical_decoupling.enable = True
  3. Revizuiește toate opțiunile acceptate și efectuează actualizările necesare.

  4. Grupează fiecare circuit pe care vrei să-l rulezi împreună cu observabilele și valorile parametrilor pe care vrei să le aplici circuitului, într-un tuplu (un PUB). De exemplu, folosește (circuit1, observable1, parameter_set1) dacă vrei să rulezi circuit1 cu observable1 și parameter_set1.

  5. S-ar putea să fie nevoie să remodelezi tablourile de observabile sau de seturi de parametri dacă vrei să aplici produsul lor exterior. De exemplu, un tablou de observabile de forma (4, 1) și un tablou de seturi de parametri de forma (1, 6) îți vor oferi un rezultat de (4, 6) valori așteptate. Consultă regulile de broadcasting Numpy pentru mai multe detalii.

  6. Poți specifica opțional precizia dorită pentru acel PUB specific.

  7. Actualizează metoda run() a estimatorului pentru a transmite lista de PUB-uri. De exemplu, run([(circuit1, observable1, parameter_set1)]). Poți specifica opțional o valoare precision aici, care s-ar aplica tuturor PUB-urilor.

  8. Rezultatele job-urilor Estimator V2 sunt grupate pe PUB-uri. Poți vedea valoarea așteptată și eroarea standard pentru fiecare PUB indexând la acesta. De exemplu:

pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")

Exemple complete cu Estimator

Rulează un singur experiment

Folosește Estimator pentru a determina valoarea de așteptare a unei singure perechi circuit-observabil.

import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import EstimatorV2 as Estimator, QiskitRuntimeService

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
estimator = Estimator(backend)

n_qubits = 127

mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)

pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)

job = estimator.run([(isa_circuit, isa_observable)])
result = job.result()

print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")

Rulează mai multe experimente într-un singur job

Folosește Estimator pentru a determina valorile de așteptare ale mai multor perechi circuit-observabil.

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)

n_qubits = 3
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
observables = [
SparsePauliOp("X" * n_qubits),
SparsePauliOp("Y" * n_qubits),
SparsePauliOp("Z" * n_qubits),
]

isa_circuits = pm.run(circuits)
isa_observables = [ob.apply_layout(isa_circuits[0].layout) for ob in observables]

estimator = Estimator(backend)
job = estimator.run([(isa_circuits[0], isa_observables[0]),(isa_circuits[1], isa_observables[1]),(isa_circuits[2], isa_observables[2])])
job_result = job.result()
for idx in range(len(job_result)):
pub_result = job_result[idx]
print(f">>> Expectation values for PUB {idx}: {pub_result.data.evs}")
print(f">>> Standard errors for PUB {idx}: {pub_result.data.stds}")

Rularea circuitelor parametrizate

Folosește Estimator pentru a rula mai multe experimente într-un singur job, valorificând valorile parametrilor pentru a crește reutilizabilitatea Circuit-ului. În exemplul următor, observă că pașii 1 și 2 sunt identici pentru V1 și V2.

import numpy as np

from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService

# Step 1: Map classical inputs to a quantum problem

theta = Parameter("θ")

chsh_circuit = QuantumCircuit(2)
chsh_circuit.h(0)
chsh_circuit.cx(0, 1)
chsh_circuit.ry(theta, 0)

number_of_phases = 21
phases = np.linspace(0, 2 * np.pi, number_of_phases)
individual_phases = [[ph] for ph in phases]

ZZ = SparsePauliOp.from_list([("ZZ", 1)])
ZX = SparsePauliOp.from_list([("ZX", 1)])
XZ = SparsePauliOp.from_list([("XZ", 1)])
XX = SparsePauliOp.from_list([("XX", 1)])
ops = [ZZ, ZX, XZ, XX]

# Step 2: Optimize problem for quantum execution.

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
chsh_isa_circuit = pm.run(chsh_circuit)
isa_observables = [operator.apply_layout(chsh_isa_circuit.layout) for operator in ops]

from qiskit_ibm_runtime import EstimatorV2 as Estimator

# Step 3: Execute using Qiskit primitives.

# Reshape observable array for broadcasting
reshaped_ops = np.fromiter(isa_observables, dtype=object)
reshaped_ops = reshaped_ops.reshape((4, 1))

estimator = Estimator(backend, options={"default_shots": int(1e4)})
job = estimator.run([(chsh_isa_circuit, reshaped_ops, individual_phases)])
# Get results for the first (and only) PUB
pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")
print(f">>> Metadata: {pub_result.metadata}")

Folosește sesiuni și opțiuni avansate

Explorează sesiunile și opțiunile avansate pentru a optimiza performanța circuitelor pe QPU-uri.

atenție

Blocul de cod următor va returna o eroare pentru utilizatorii cu planul Open, deoarece folosește sesiuni. Sarcinile de lucru pe planul Open pot rula doar în modul job sau în modul batch.

import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Session, EstimatorV2 as Estimator

n_qubits = 127

rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
observable = SparsePauliOp("X" * n_qubits)
another_observable = SparsePauliOp("Y" * n_qubits)

pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
another_isa_observable = another_observable.apply_layout(another_isa_circuit.layout)

service = QiskitRuntimeService()

backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)

with Session(backend=backend) as session:
estimator = Estimator()

estimator.options.resilience_level = 1

job = estimator.run([(isa_circuit, isa_observable)])
another_job = estimator.run([(another_isa_circuit, another_isa_observable)])
result = job.result()
another_result = another_job.result()

# first job
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")

# second job
print(f" > Another Expectation value: {another_result[0].data.evs}")
print(f" > More Metadata: {another_result[0].metadata}")

Pași pentru migrarea la Sampler V2

  1. Înlocuiește from qiskit_ibm_runtime import Sampler cu from qiskit_ibm_runtime import SamplerV2 as Sampler.
  2. Elimină orice instrucțiune from qiskit_ibm_runtime import Options, deoarece clasa Options nu este utilizată de primitivele V2. În schimb, poți transmite opțiunile sub formă de dicționar la inițializarea clasei SamplerV2 (de exemplu, sampler = Sampler(backend, options={"default_shots": 1024})), sau le poți seta după inițializare:
    sampler = Sampler(backend)
    sampler.options.default_shots = 1024
  3. Consultă toate opțiunile acceptate și efectuează actualizările necesare.
  4. Grupează fiecare Circuit pe care vrei să-l rulezi împreună cu observabilele și valorile parametrilor pe care dorești să le aplici circuitului, într-un tuplu (un PUB). De exemplu, folosește (circuit1, parameter_set1) dacă vrei să rulezi circuit1 cu parameter_set1. Poți specifica opțional numărul de shot-uri dorite pentru acel PUB specific.
  5. Actualizează metoda run() a Sampler pentru a transmite lista de PUB-uri. De exemplu, run([(circuit1, parameter_set1)]). Poți specifica opțional shots aici, ceea ce s-ar aplica tuturor PUB-urilor.
  6. Rezultatele joburilor din Sampler V2 sunt grupate pe PUB-uri. Poți vedea datele de ieșire pentru fiecare PUB indexând până la acesta. În timp ce Sampler V2 returnează eșantioane neponderate, clasa de rezultate are o metodă de conveniență pentru a obține counts în schimb. De exemplu:
pub_result = job.result()[0]
print(f">>> Counts: {pub_result.data.meas.get_counts()}")
print(f">>> Per-shot measurement: {pub_result.data.meas.get_counts()}")
notă

Ai nevoie de numele registrului clasic pentru a obține rezultatele. În mod implicit, acesta se numește meas când folosești measure_all(). Când îți definești Circuit-ul, dacă creezi unul sau mai multe registre clasice cu un nume diferit de cel implicit, folosește acel nume pentru a obține rezultatele. Poți găsi numele registrului clasic rulând <circuit_name>.cregs. De exemplu, qc.cregs.

Exemple complete cu Sampler

Rularea unui singur experiment

Folosește Sampler pentru a determina counts sau distribuția de quasi-probabilitate a unui singur Circuit.

import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

service = QiskitRuntimeService()

backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)

n_qubits = 127

mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)

sampler = Sampler(backend)
job = sampler.run([isa_circuit])
result = job.result()

Rulează mai multe experimente într-un singur job

Folosește Sampler pentru a determina numărul de apariții sau distribuțiile de cvasi-probabilitate ale mai multor Circuit-uri într-un singur job.

import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler

service = QiskitRuntimeService()

backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)

n_qubits = 127

rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
for circuit in circuits:
circuit.measure_all()

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)

sampler = Sampler(backend)
job = sampler.run(isa_circuits)
result = job.result()

for idx, pub_result in enumerate(result):
print(f" > Counts for pub {idx}: {pub_result.data.meas.get_counts()}")

Rulează circuite parametrizate

Rulează mai multe experimente într-un singur job, folosind valori de parametri pentru a crește reutilizabilitatea circuitului.

import numpy as np
from qiskit.circuit.library import RealAmplitudes
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService

# Step 1: Map classical inputs to a quantum problem
num_qubits = 127
circuit = RealAmplitudes(num_qubits=num_qubits, reps=2)
circuit.measure_all()

# Define three sets of parameters for the circuit
rng = np.random.default_rng(1234)
parameter_values = [
rng.uniform(-np.pi, np.pi, size=circuit.num_parameters) for _ in range(3)
]

# Step 2: Optimize problem for quantum execution.

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=num_qubits)

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)

# Step 3: Execute using Qiskit primitives.

from qiskit_ibm_runtime import SamplerV2 as Sampler

sampler = Sampler(backend)
job = sampler.run([(isa_circuit, parameter_values)])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
# Get counts from the classical register "meas".
print(f" >> Counts for the meas output register: {pub_result.data.meas.get_counts()}")

Folosește sesiuni și opțiuni avansate

Explorează sesiunile și opțiunile avansate pentru a optimiza performanța Circuit-urilor pe QPU-uri.

atenție

Blocul de cod următor va returna o eroare pentru utilizatorii din planul Open, deoarece folosește sessions. Lucrările din planul Open pot rula doar în modul job sau în modul batch.

import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler, Session

n_qubits = 127

rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
circuit.measure_all()
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
another_circuit.measure_all()

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)

service = QiskitRuntimeService()

# Turn on dynamical decoupling with sequence XpXm.
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XpXm"

backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)

with Session(backend=backend) as session:
sampler = Sampler()
job = sampler.run([isa_circuit])
another_job = sampler.run([another_isa_circuit])
result = job.result()
another_result = another_job.result()

# first job
print(f" > Counts for job 1: {result[0].data.meas.get_counts()}")

# second job
print(f" > Counts for job 2: {another_result[0].data.meas.get_counts()}")

Next steps

Recommendations