Міграція на примітиви Qiskit Runtime V2
Оригінальні примітиви (так звані примітиви V1) — V1 Sampler і V1 Estimator — оголошено застарілими у qiskit-ibm-runtime 0.23.
Підтримку їх було припинено 15 серпня 2024 року.
У зв'язку з оголошенням примітивів V1 застарілими, весь код слід перенести на інтерфейси V2. Цей посібник описує, що змінилося у примітивах Qiskit Runtime V2 (доступних починаючи з qiskit-ibm-runtime 0.21.0) і чому, детально описує кожен новий примітив та надає приклади для міграції коду з застарілих примітивів на примітиви V2. Усі приклади в посібнику використовують примітиви Qiskit Runtime, але загалом ті самі зміни стосуються й інших реалізацій примітивів. Функції, унікальні для Qiskit Runtime, — такі як пом'якшення помилок — залишаються ексклюзивними для Qiskit Runtime.
Для отримання інформації про зміни в еталонних примітивах Qiskit (тепер відомих як statevector примітиви) переглянь розділ qiskit.primitives на сторінці змін Qiskit 1.0. Дивись StatevectorSampler і StatevectorEstimator для довідкових реалізацій примітивів V2.
Огляд
Версія 2 примітивів вводиться з новим базовим класом для Sampler і Estimator (BaseSamplerV2 і BaseEstimatorV2), а також новими типами для їхніх вхідних і вихідних даних.
Новий інтерфейс дозволяє задавати один схему та кілька спостережуваних (якщо використовуєш Estimator) і наборів значень параметрів для цієї схеми, що дає змогу ефективно задавати перебір по наборах значень параметрів і спостережуваних. Раніше потрібно було кілька разів задавати одну й ту ж схему, щоб відповідати розміру даних для об'єднання. Крім того, хоча ти й надалі можеш використовувати resilience_level (якщо використовуєш Estimator) як простий перемикач, примітиви V2 дають тобі гнучкість для вмикання або вимикання окремих методів пом'якшення/пригнічення помилок відповідно до потреб.
Щоб скоротити загальний час виконання завдань, примітиви V2 приймають лише схеми та спостережувані, що використовують інструкції, підтримувані цільовим QPU (квантовим процесором). Такі схеми та спостережувані називаються ISA-схемами та ISA-спостережуваними (ISA — instruction set architecture, архітектура набору команд). Примітиви V2 не виконують операції розміщення, маршрутизації та трансляції. Переглянь документацію з транспіляції для отримання інструкцій щодо перетворення схем.
Sampler V2 спрощено з акцентом на його основному завданні — вибірці вихідного регістру з виконання квантових схем. Він повертає зразки, тип яких визначається програмою, без ваг. Вихідні дані також розділені за іменами вихідних регістрів, визначених програмою. Ця зміна уможливлює майбутню підтримку схем із класичним керуванням потоком.
Дивись довідку API EstimatorV2 і довідку API SamplerV2 для отримання повної інформації.
Основні зміни
Імпорт
Для зворотної сумісності потрібно явно імпортувати примітиви V2. Вказувати import <primitive>V2 as <primitive> не обов'язково, але це полегшує перехід коду на V2.
Після того, як підтримку примітивів V1 буде припинено, import <primitive> імпортуватиме версію V2 вказаного примітива.
- Estimator V2
- Estimator (V1)
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import Estimator
- Sampler V2
- Sampler (V1)
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import Sampler
Вхідні та вихідні дані
Вхідні дані
Обидва SamplerV2 і EstimatorV2 приймають один або кілька primitive unified blocs (PUB) як вхідні дані. Кожен PUB — це кортеж, що містить одну схему та дані, що транслюються на цю схему, — кілька спостережуваних і параметрів. Кожен PUB повертає результат.
- Формат PUB для Sampler V2: (
<circuit>,<parameter values>,<shots>), де<parameter values>і<shots>є необов'язковими. - Формат PUB для Estimator V2: (
<circuit>,<observables>,<parameter values>,<precision>), де<parameter values>і<precision>є необов'язковими. При об'єднанні спостережуваних і значень параметрів застосовуються правила broadcasting Numpy.
Крім того, були внесені такі зміни:
- Estimator V2 отримав аргумент
precisionу методіrun(), що задає цільову точність оцінок математичного сподівання. - Sampler V2 має аргумент
shotsу методіrun().
Приклади
Приклад Estimator V2 з використанням precision у run():
# Estimate expectation values for two PUBs, both with 0.05 precision.
estimator.run([(circuit1, obs_array1), (circuit2, obs_array_2)], precision=0.05)
Приклад Sampler V2 з використанням shots у 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),
])
Вихідні дані
Вихідні дані тепер у форматі PubResult. PubResult — це дані та метадані, що є результатом вико нання одного PUB.
-
Estimator V2 продовжує повертати математичні сподівання.
-
Частина
dataу PubResult Estimator V2 містить як математичні сподівання, так і стандартні похибки (stds). V1 повертав дисперсію в метаданих. -
Sampler V2 повертає поshot-вимірювання у вигляді бітових рядків, замість квазійовірнісних розподілів з інтерфейсу V1. Бітові рядки показують результати вимірювань, зберігаючи порядок shots, у якому вони були виміряні.
-
Sampler V2 має зручні методи, такі як
get_counts(), для полегшення міграції. -
Об'єкти результатів Sampler V2 організують дані у термінах імен класичних регістрів вхідних схем, для сумісності з динамічними схемами. За замовчуванням ім'я класичного регістра —
meas, як показано в наступному прикладі. При визначенні схеми, якщо ти створюєш один або кілька класичних регістрів з нестандартним іменем, використовуй це ім'я для отримання результатів. Ім'я класичного регістра можна дізнатись, запустивши<circuit_name>.cregs. Наприклад,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
Приклади Estimator (вхідні та вихідні дані)
- 1 схема, 4 спостережувані
- 1 схема, 4 спостережувані, 2 набори параметрів
- 2 схеми, 2 спостережувані
# 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
# Estimator V1: Execute 1 circuit with 4 observables and 2 parameter sets
job = estimator_v1.run([circuit] * 8, [obs1, obs2, obs3, obs4] * 2, [vals1, vals2] * 4)
evs = job.result().values
# Estimator V2: Execute 1 circuit with 4 observables and 2 parameter sets
job = estimator_v2.run([(circuit, [[obs1], [obs2], [obs3], [obs4]], [[vals1], [vals2]])])
evs = job.result()[0].data.evs
# Estimator V1: Cannot execute 2 circuits with different observables
# Estimator V2: Execute 2 circuits with 2 different observables. There are
# two PUBs because each PUB can have only one circuit.
job = estimator_v2.run([(circuit1, obs1), (circuit2, obs2)])
evs1 = job.result()[0].data.evs # result for pub 1 (circuit 1)
evs2 = job.result()[1].data.evs # result for pub 2 (circuit 2)
Приклади Sampler (вхідні та вихідні дані)
- 1 схема, 3 набори параметрів
- 2 схеми, 1 набір параметрів
- Конвертація виводу V2 у формат V1
# 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()
# Sampler V1: Execute 2 circuits with 1 parameter set
job = sampler_v1.run([circuit1, circuit2], [vals1] * 2)
dists = job.result().quasi_dists
# Sampler V2: Execute 2 circuits with 1 parameter set
job = sampler_v2.run([(circuit1, vals1), (circuit2, vals1)])
counts1 = job.result()[0].data.meas.get_counts() # result for pub 1 (circuit 1)
counts2 = job.result()[1].data.meas.get_counts() # result for pub 2 (circuit 2)
Формат виводу V1 — словник, де ключами є бітові рядки (як int), а значеннями — квазійовірності для кожної схеми. Формат V2 має той же ключ (але у вигляді рядка) і кількість відліків як значення. Щоб конвертувати формат V2 у V1, поділи кількість відліків на кількість shots, де вибір кількості shots описаний у посібнику Sampler options.
v2_result = sampler_v2_job.result()
v1_format = []
for pub_result in v2_result:
counts = pub_result.data.meas.get_counts()
v1_format.append( {int(key, 2): val/shots for key, val in counts.items()} )
Приклад з використанням різних вихідних регістрів
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()}")
Опції
Опції у примітивах V2 задаються інакше, ніж раніше:
SamplerV2іEstimatorV2тепер мають окремі класи опцій. Ти можеш переглядати доступні опції та оновлювати їхні значення під час або після ініціалізації примітива.- Замість методу
set_options(), опції примітивів V2 мають методupdate(), який застосовує зміни до атрибутаoptions. - Якщо ти не вказуєш значення опції, їй присвоюється спеціальне значення
Unset, і використовуються серверні значення за замовчуванням. - Для примітивів V2 атрибут
optionsє типомdataclassу Python. Можна використовувати вбудований методasdict, щоб перетворити його на словник.
Дивись довідку API для переліку доступних опцій.
- Estimator V2
- Estimator (V1)
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 qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
options = Options()
# This uses auto complete.
options.resilience_level = 2
estimator = Estimator(backend=backend, options=options)
# Setting options after primitive initialization.
# This does bulk update.
estimator.set_options(shots=4000)
- Sampler V2
- Sampler (V1)
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))
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
options = Options()
# This uses auto complete.
options.resilience_level = 2
sampler = Sampler(backend=backend, options=options)
# Setting options after primitive initialization.
# This does bulk update.
sampler.set_options(shots=2000)
Пом'якшення та пригнічення помилок
-
Оскільки Sampler V2 повертає зразки без постобробки, він не підтримує рівні стійкості (resilience levels).
-
Sampler V2 не підтримує
optimization_level. -
Estimator V2 припинить підтримку
optimization_levelприблизно 30 вересня 2024 року. -
Estimator V2 не підтримує рівень стійкості 3. Це пов'язано з тим, що рівень 3 у V1 Estimator використовує Probabilistic Error Cancellation (PEC), що, як доведено, дає незміщені результати ціною експоненційного часу обробки. Рівень 3 було прибрано, щоб звернути увагу на цей компроміс. Однак ти все ще можеш використовувати PEC як метод пом'якшення помилок, задавши опцію
pec_mitigation. -
Estimator V2 підтримує
resilience_level0-2, як описано в наступній таблиці. Ці опції є більш просунутими порівняно з їхніми аналогами у V1. Ти також можеш явно вмикати або вимикати окремі методи пом'якшення/пригнічення помилок.Рівень 1 Рівень 2 Measurement twirling Measurement twirling Пом'якшення помилок зчитування Пом'якшення помилок зчитування ZNE
- Estimator V2
- Estimator (V1)
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 Estimator, Options
estimator = Estimator(backend, options=options)
options = Options()
options.resilience_level = 2
- Sampler V2
- Sampler (V1)
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}")
from qiskit_ibm_runtime import Sampler, Options
sampler = Sampler(backend, options=options)
options = Options()
options.resilience_level = 2
Транспіляція
Примітиви V2 підтримують лише схеми, що відповідають архітектурі набору команд (ISA) конкретного бекенду. Оскільки примітиви не виконують операції розміщення, маршрутизації та трансляції, відповідні опції транспіляції з V1 не підтримуються.
Статус завдання
Примітиви V2 мають новий клас RuntimeJobV2, що успадковується від BasePrimitiveJob. Метод status() цього нового класу повертає рядок замість переліку JobStatus з Qiskit. Дивись довідку API RuntimeJobV2 для деталей.
- Примітиви V2
- Примітиви V1
job = estimator.run(...)
# check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() == "RUNNING"}")
from qiskit.providers.jobstatus import JobStatus
job = estimator.run(...)
#check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() is JobStatus.RUNNING}")
Кроки міграції на Estimator V2
-
Заміни
from qiskit_ibm_runtime import Estimatorнаfrom qiskit_ibm_runtime import EstimatorV2 as Estimator. -
Видали всі оператори
from qiskit_ibm_runtime import Options, оскільки класOptionsне використовується примітивами V2. Натомість можна передавати опції у вигляді словника при ініціалізації класуEstimatorV2(наприклад,estimator = Estimator(backend, options={"dynamical_decoupling": {"enable": True}})), або задавати їх після ініціалізації:estimator = Estimator(backend)
estimator.options.dynamical_decoupling.enable = True -
Переглянь всі підтримувані опції та внеси відповідні зміни.
-
Згрупуй кожну схему, яку хочеш запустити, зі спостережуваними та значеннями параметрів, які хочеш застосувати до схеми, у кортеж (PUB). Наприклад, використовуй
(circuit1, observable1, parameter_set1), якщо хочеш запуститиcircuit1зobservable1іparameter_set1. -
Можливо, доведеться перетворити форму масивів спостережуваних або наборів параметрів, якщо хочеш застосувати їхній зовнішній добуток. Наприклад, масив спостережуваних форми (4, 1) і масив наборів параметрів форми (1, 6) дадуть результат (4, 6) математичних сподівань. Дивись правила broadcasting Numpy для деталей.
-
Можна за бажанням вказати точність для конкретного PUB.
-
Оновіть метод
run()Estimator для передачі списку PUB. Наприклад,run([(circuit1, observable1, parameter_set1)]). Можна за бажанням вказатиprecision, яка буде застосована до всіх PUB. -
Результати завдань Estimator V2 згруповані за PUB. Ти можеш переглянути математичне сподівання та стандартну похибку для кожного PUB, звертаючись до нього за індексом. Наприклад:
pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")
Повні приклади Estimator
Запуск одного експерименту
Використовуй Estimator для визначення математичного сподівання однієї пари схема-спостережувана.
- Estimator V2
- Estimator (V1)
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}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
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)
observable = SparsePauliOp("Z" * n_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
estimator = Estimator(backend)
job = estimator.run(isa_circuit, isa_observable)
result = job.result()
print(f" > Observable: {observable.paulis}")
print(f" > Expectation value: {result.values}")
print(f" > Metadata: {result.metadata}")
Запуск кількох експериментів в одному завданні
Використовуй Estimator для визначення математичних сподівань кількох пар схема-спостережувана.
- Estimator V2
- Estimator (V1)
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}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
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]
observables = [
SparsePauliOp("X" * n_qubits),
SparsePauliOp("Y" * n_qubits),
SparsePauliOp("Z" * n_qubits),
]
# Get ISA circuits
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
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, isa_observables)
result = job.result()
print(f" > Expectation values: {result.values}")
Запуск параметризованих схем
Використовуй Estimator для запуску кількох експериментів в одному завданні, використовуючи значення параметрів для підвищення повторного використання схем. У наступному прикладі зверни увагу, що кроки 1 і 2 однакові для V1 і V2.
- Estimator V2
- Estimator (V1)
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}")
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 Estimator
# Step 3: Execute using Qiskit Primitives.
num_ops = len(isa_observables)
batch_circuits = [chsh_isa_circuit] * number_of_phases * num_ops
batch_ops = [op for op in isa_observables for _ in individual_phases]
batch_phases = individual_phases * num_ops
estimator = Estimator(backend, options={"shots": int(1e4)})
job = estimator.run(batch_circuits, batch_ops, batch_phases)
expvals = job.result().values
Використання сесій і розширених опцій
Досліджуй сесії та розширені опції для оптимізації продуктивності схем на QPU.
Наступний блок коду повернеться з помилкою для користувачів плану Open, оскільки він використовує сесії. Завдання на плані Open можуть виконуватися лише в режимі job або режимі batch.
- Estimator V2
- Estimator (V1)
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}")
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, Estimator, Options
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(backend=backend, optimization_level=1)
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)
options = Options()
options.optimization_level = 2
options.resilience_level = 2
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
estimator = Estimator(options=options)
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 values job 1: {result.values}")
# second job
print(f" > Expectation values job 2: {another_result.values}")
Кроки міграції на Sampler V2
- Заміни
from qiskit_ibm_runtime import Samplerнаfrom qiskit_ibm_runtime import SamplerV2 as Sampler. - Видали всі оператори
from qiskit_ibm_runtime import Options, оскільки класOptionsне використовується приміти вами V2. Натомість можна передавати опції у вигляді словника при ініціалізації класуSamplerV2(наприклад,sampler = Sampler(backend, options={"default_shots": 1024})), або задавати їх після ініціалізації:sampler = Sampler(backend)
sampler.options.default_shots = 1024 - Переглянь всі підтримувані опції та внеси відповідні зміни.
- Згрупуй кожну схему, яку хочеш запустити, зі спостережуваними та значеннями параметрів, які хочеш застосувати до схеми, у кортеж (PUB). Наприклад, використовуй
(circuit1, parameter_set1), якщо хочеш запуститиcircuit1зparameter_set1. За бажанням можна вказати кількість shots для конкретного PUB. - Оновіть метод
run()Sampler для передачі списку PUB. Наприклад,run([(circuit1, parameter_set1)]). За бажанням можна вказатиshots, які будуть застосовані до всіх PUB. - Результати завдань Sampler V2 згруповані за PUB. Ти можеш переглянути вихідні дані для кожного PUB, звертаючись до нього за індексом. Хоча Sampler V2 повертає незважені зразки, клас результатів має зручний метод для отримання кількості відліків. Наприклад:
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()}")
Для отримання результатів потрібно знати ім'я класичного регістра. За замовчуванням він називається meas, коли використовуєш measure_all(). При визначенні схеми, якщо ти створюєш один або кілька класичних регістрів з нестандартним іменем, використовуй це ім'я для отримання результатів. Ім'я класичного регістра можна дізнатись, запустивши <circuit_name>.cregs. Наприклад, qc.cregs.
Повні приклади Sampler
Запуск одного експерименту
Використовуй Sampler для визначення кількості відліків або квазійовірнісного розподілу однієї схеми.
- Sampler V2
- Sampler (V1)
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()
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
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()
sampler = Sampler(backend)
job = sampler.run(circuit)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
print(f" > Metadata: {result.metadata}")
Запуск кількох експериментів в одному завданні
Використовуй Sampler для визначення кількості відліків або квазійовірнісних розподілів кількох схем в одному завданні.
- Sampler V2
- Sampler (V1)
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()}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, 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()
sampler = Sampler(backend)
job = sampler.run(circuits)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
Запуск параметризованих схем
Запуск кількох експериментів в одному завданні, використовуючи значення параметрів для підвищення повторного використання схем.
- Sampler V2
- Sampler (V1)
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()}")
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 = 5
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 Sampler
sampler = Sampler(backend)
job = sampler.run([isa_circuit] * 3, parameter_values)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
print(f" > Metadata: {result.metadata}")
Використання сесій і розширених опцій
Досліджуй сесії та розширені опції для оптимізації продуктивності схем на QPU.
Наступний блок коду повернеться з помилкою для користувачів плану Open, оскільки він використовує сесії. Завдання на плані Open можуть виконуватися лише в режимі job або режимі batch.
- Sampler V2
- Sampler (V1)
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()}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Session, Options
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()
options = Options()
options.optimization_level = 2
options.resilience_level = 0
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
sampler = Sampler(options=options)
job = sampler.run(circuit)
another_job = sampler.run(another_circuit)
result = job.result()
another_result = another_job.result()
# first job
print(f" > Quasi-probability distribution job 1: {result.quasi_dists}")
# second job
print(f" > Quasi-probability distribution job 2: {another_result.quasi_dists}")
Наступні кроки
- Дізнайся більше про налаштування опцій у посібниках Sampler options, Estimator options та Executor options.
- Дізнайся більше про вхідні та вихідні дані примітивів.
- Поекспериментуй з туторіалом Нерівність CHSH.