ข้ามไปยังเนื้อหาหลัก

ย้ายไปใช้ Qiskit Runtime V2 primitives

คำเตือน

primitives เดิม (เรียกว่า V1 primitives) ได้แก่ V1 Sampler และ V1 Estimator ถูก deprecated ใน qiskit-ibm-runtime 0.23 แล้ว การรองรับถูกลบออกเมื่อวันที่ 15 สิงหาคม 2024

เนื่องจาก V1 primitives ถูก deprecated แล้ว โค้ดทั้งหมดควรได้รับการย้ายไปใช้อินเทอร์เฟซ V2 คู่มือนี้อธิบายว่า Qiskit Runtime V2 primitives (ใช้งานได้กับ qiskit-ibm-runtime 0.21.0) มีอะไรเปลี่ยนไปบ้างและเพราะอะไร อธิบาย primitive ใหม่แต่ละตัวอย่างละเอียด และให้ตัวอย่างเพื่อช่วยให้ย้ายโค้ดจาก primitives เดิมไปเป็น V2 primitives ตัวอย่างในคู่มือทั้งหมดใช้ Qiskit Runtime primitives แต่โดยทั่วไปการเปลี่ยนแปลงเดียวกันนี้ก็ใช้กับ primitive implementations อื่นๆ ด้วย ฟังก์ชันที่เป็นเอกลักษณ์ของ Qiskit Runtime เช่น error mitigation ยังคงเป็นเอกลักษณ์ของ Qiskit Runtime

ดูข้อมูลเกี่ยวกับการเปลี่ยนแปลง Qiskit reference primitives (ปัจจุบันเรียกว่า statevector primitives) ได้ที่ ส่วน qiskit.primitives ในหน้าการเปลี่ยนแปลงฟีเจอร์ Qiskit 1.0 ดู StatevectorSampler และ StatevectorEstimator สำหรับ V2 primitive reference implementations

ภาพรวม

V2 ของ primitives นำเสนอพร้อมกับ base class ใหม่สำหรับทั้ง Sampler และ Estimator (BaseSamplerV2 และ BaseEstimatorV2) รวมถึงประเภทใหม่สำหรับ input และ output

อินเทอร์เฟซใหม่ให้กำหนด Circuit เดียวและ observables หลายตัว (ถ้าใช้ Estimator) รวมถึง parameter value sets สำหรับ Circuit นั้น ทำให้การ sweep ข้าม parameter value sets และ observables สามารถระบุได้อย่างมีประสิทธิภาพ ก่อนหน้านี้ต้องระบุ Circuit เดียวกันหลายครั้งเพื่อให้ตรงกับขนาดของข้อมูลที่จะรวมกัน นอกจากนี้ แม้จะยังใช้ resilience_level (ถ้าใช้ Estimator) เป็น knob แบบง่ายได้ V2 primitives ก็ให้ความยืดหยุ่นในการเปิดหรือปิดวิธี error mitigation / suppression แต่ละวิธีเพื่อปรับแต่งตามความต้องการ

เพื่อลดเวลาการรัน job รวม V2 primitives รับเฉพาะ Circuit และ observables ที่ใช้คำสั่งที่รองรับโดย QPU เป้าหมาย (quantum processing unit) Circuit และ observables ดังกล่าวเรียกว่า instruction set architecture (ISA) circuits และ observables V2 primitives ไม่ทำการ layout, routing, และ translation โปรดดู เอกสาร transpilation สำหรับคำแนะนำในการแปลง Circuit

Sampler V2 ถูกทำให้เรียบง่ายขึ้นเพื่อมุ่งเน้นที่งานหลักในการ sampling output register จากการรัน quantum circuits มันคืนค่า samples ซึ่งประเภทถูกกำหนดโดยโปรแกรม โดยไม่มี weights ข้อมูล output ยังแยกตามชื่อ output register ที่กำหนดโดยโปรแกรม การเปลี่ยนแปลงนี้รองรับ Circuit ที่มี classical control flow ในอนาคต

ดู EstimatorV2 API reference และ SamplerV2 API reference สำหรับรายละเอียดทั้งหมด

การเปลี่ยนแปลงหลัก

Import

เพื่อความเข้ากันได้แบบย้อนหลัง ต้อง import V2 primitives อย่างชัดเจน การระบุ import <primitive>V2 as <primitive> ไม่จำเป็น แต่ทำให้การย้ายโค้ดไป V2 ง่ายขึ้น

คำเตือน

หลังจาก V1 primitives ไม่รองรับแล้ว import <primitive> จะ import เวอร์ชัน V2 ของ primitive ที่ระบุ

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

Input และ output

Input

ทั้ง SamplerV2 และ EstimatorV2 รับ primitive unified blocs (PUBs) หนึ่งตัวหรือมากกว่าเป็น input แต่ละ PUB คือ tuple ที่มี Circuit หนึ่ง ตัวและข้อมูลที่ broadcast ไปยัง Circuit นั้น ซึ่งสามารถมี observables และ parameters หลายตัวได้ แต่ละ PUB คืนผลลัพธ์

  • รูปแบบ Sampler V2 PUB: (<circuit>, <parameter values>, <shots>) โดย <parameter values> และ <shots> เป็นตัวเลือก
  • รูปแบบ Estimator V2 PUB: (<circuit>, <observables>, <parameter values>, <precision>) โดย <parameter values> และ <precision> เป็นตัวเลือก กฎ broadcasting ของ Numpy ถูกใช้เมื่อรวม observables และ parameter values

นอกจากนี้ มีการเปลี่ยนแปลงดังต่อไปนี้:

  • Estimator V2 ได้รับ argument precision ในเมธอด run() ที่ระบุความแม่นยำของการประมาณค่า expectation value
  • Sampler V2 มี argument 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),
])

Output

output ตอนนี้อยู่ในรูปแบบ PubResult PubResult คือข้อมูลและ metadata ที่ได้จากการรัน PUB เดียว

  • Estimator V2 ยังคงคืนค่า expectation values

  • ส่วน data ของ Estimator V2 PubResult มีทั้ง expectation values และ standard errors (stds) V1 คืนค่า variance ใน metadata

  • Sampler V2 คืน per-shot measurements ในรูปแบบ bitstrings แทนการแจกแจงความน่าจะเป็นแบบ quasi-probability จากอินเทอร์เฟซ V1 bitstrings แสดงผลลัพธ์การวัด โดยรักษาลำดับ shot ที่วัด

  • Sampler V2 มีเมธอดที่ช่วยอำนวยความสะดวก เช่น get_counts() เพื่อช่วยในการย้าย

  • ออบเจกต์ผลลัพธ์ของ Sampler V2 จัดระเบียบข้อมูลตาม ชื่อ classical register ของ circuit input เพื่อความเข้ากันได้กับ dynamic circuits ตามค่าเริ่มต้น ชื่อ classical register คือ meas ดังตัวอย่างต่อไปนี้ เมื่อกำหนด circuit ถ้าสร้าง classical register หนึ่งตัวหรือมากกว่าด้วยชื่อที่ไม่ใช่ค่าเริ่มต้น ให้ใช้ชื่อนั้นในการดึงผลลัพธ์ หาชื่อ classical register ได้โดยรัน <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 (input และ output)

# 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

ตัวอย่าง Sampler (input และ output)

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

ตัวอย่างที่ใช้ output registers ต่างกัน

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

Options ถูกระบุแตกต่างกันใน V2 primitives ดังนี้:

  • SamplerV2 และ EstimatorV2 ตอนนี้มี options classes แยกกัน สามารถดู options ที่ใช้งานได้และอัปเดตค่า option ระหว่างหรือหลังการเริ่มต้น primitive
  • แทนที่จะใช้เมธอด set_options() V2 primitive options มีเมธอด update() ที่ใช้การเปลี่ยนแปลงกับ attribute options
  • ถ้าไม่ระบุค่าสำหรับ option จะได้รับค่าพิเศษ Unset และใช้ค่าเริ่มต้นของ server
  • สำหรับ V2 primitives attribute options เป็นประเภท Python dataclass สามารถใช้เมธอด asdict ในตัวเพื่อแปลงเป็น dictionary

ดู API reference สำหรับรายการ options ที่ใช้งานได้

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

การลดและการยับยั้งข้อผิดพลาด

  • เนื่องจาก Sampler V2 คืนค่า samples โดยไม่ผ่านการประมวลผลเพิ่มเติม จึงไม่รองรับ resilience levels

  • Sampler V2 ไม่รองรับ optimization_level

  • Estimator V2 จะยกเลิกการรองรับ optimization_level ในหรือประมาณวันที่ 30 กันยายน 2024

  • Estimator V2 ไม่รองรับ resilience level 3 เพราะ resilience level 3 ใน V1 Estimator ใช้ Probabilistic Error Cancellation (PEC) ซึ่งพิสูจน์แล้วว่าให้ผลลัพธ์ที่ไม่มี bias แต่แลกมาด้วยเวลาประมวลผลแบบ exponential Level 3 ถูกลบออกเพื่อเน้นให้เห็น tradeoff ดังกล่าว อย่างไรก็ตาม คุณยังคงสามารถใช้ PEC เป็นวิธีลดข้อผิดพลาดได้ โดยระบุ option pec_mitigation

  • Estimator V2 รองรับ resilience_level 0–2 ตามตารางด้านล่าง option เหล่านี้มีความสามารถมากกว่า V1 คุณยังสามารถเปิด/ปิดวิธีลดหรือยับยั้งข้อผิดพลาดแต่ละอย่างได้อย่างชัดเจน

    Level 1Level 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}")

การ Transpile

V2 primitives รองรับเฉพาะ Circuit ที่สอดคล้องกับ Instruction Set Architecture (ISA) ของ Backend นั้น ๆ เท่านั้น เนื่องจาก primitives ไม่ทำการ layout, routing, และ translation จึงไม่รองรับ option การ transpile ที่มีใน V1

สถานะของ Job

V2 primitives มีคลาส RuntimeJobV2 ใหม่ที่ inherit จาก BasePrimitiveJob โดยเมธอด status() ของคลาสใหม่นี้คืนค่าเป็น string แทนที่จะเป็น JobStatus enum จาก Qiskit ดูรายละเอียดได้ที่ RuntimeJobV2 API reference

job = estimator.run(...)

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

ขั้นตอนการย้ายไปใช้ Estimator V2

  1. แทนที่ from qiskit_ibm_runtime import Estimator ด้วย from qiskit_ibm_runtime import EstimatorV2 as Estimator

  2. ลบ statement from qiskit_ibm_runtime import Options ออก เนื่องจากคลาส Options ไม่ได้ถูกใช้งานใน V2 primitives คุณสามารถส่ง option เป็น dictionary ตอน initialize คลาส EstimatorV2 แทน (เช่น estimator = Estimator(backend, options={"dynamical_decoupling": {"enable": True}})), หรือกำหนดหลังจาก initialize แล้ว:

    estimator = Estimator(backend)
    estimator.options.dynamical_decoupling.enable = True
  3. ตรวจสอบ option ที่รองรับทั้งหมด และอัปเดตตามความเหมาะสม

  4. จัดกลุ่ม Circuit แต่ละอันที่ต้องการรันพร้อมกับ observable และค่า parameter ที่ต้องการใช้กับ Circuit ในรูปแบบ tuple (PUB) ตัวอย่างเช่น ใช้ (circuit1, observable1, parameter_set1) หากต้องการรัน circuit1 กับ observable1 และ parameter_set1

  5. คุณอาจต้อง reshape array ของ observable หรือ parameter sets หากต้องการใช้ outer product ของมัน ตัวอย่างเช่น array ของ observable ที่มี shape (4, 1) และ array ของ parameter sets ที่มี shape (1, 6) จะให้ผลลัพธ์เป็น expectation values ขนาด (4, 6) ดู Numpy broadcasting rules สำหรับรายละเอียดเพิ่มเติม

  6. คุณสามารถระบุ precision ที่ต้องการสำหรับ PUB นั้น ๆ โดยไม่บังคับ

  7. อัปเดตเมธอด run() ของ estimator ให้รับ list ของ PUBs เช่น run([(circuit1, observable1, parameter_set1)]) คุณสามารถระบุ precision ที่นี่ได้โดยไม่บังคับ ซึ่งจะใช้กับ PUBs ทั้งหมด

  8. ผลลัพธ์ของ Estimator V2 จะถูกจัดกลุ่มตาม PUBs คุณสามารถดู expectation value และ standard error ของแต่ละ PUB ได้โดยอ้างอิงด้วย index ตัวอย่างเช่น:

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

ตัวอย่างเต็มของ Estimator

รันการทดลองเดียว

ใช้ Estimator เพื่อหา expectation value ของคู่ Circuit-observable เดียว

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

รันหลายการทดลองในงานเดียว

ใช้ Estimator เพื่อหา expectation values ของคู่ Circuit-observable หลายคู่

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

รัน Circuit แบบ Parameterized

ใช้ Estimator เพื่อรันการทดลองหลายชุดในงานเดียว โดยอาศัยค่า parameter เพื่อเพิ่มความสามารถนำ Circuit กลับมาใช้ซ้ำ ในตัวอย่างต่อไปนี้ ให้สังเกตว่าขั้นตอนที่ 1 และ 2 เหมือนกันทั้งใน V1 และ 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}")

ใช้ Session และ Options ขั้นสูง

สำรวจ Session และ options ขั้นสูงเพื่อเพิ่มประสิทธิภาพการทำงานของ Circuit บน QPU

ข้อควรระวัง

โค้ดบล็อกต่อไปนี้จะคืนค่าข้อผิดพลาดสำหรับผู้ใช้ที่อยู่ใน Open Plan เนื่องจากใช้ Session งานบน Open Plan สามารถรันได้เฉพาะใน job mode หรือ batch mode เท่านั้น

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

ขั้นตอนการย้ายไปใช้ Sampler V2

  1. แทนที่ from qiskit_ibm_runtime import Sampler ด้วย from qiskit_ibm_runtime import SamplerV2 as Sampler
  2. ลบคำสั่ง from qiskit_ibm_runtime import Options ออกทั้งหมด เนื่องจาก V2 primitives ไม่ใช้คลาส Options แล้ว แต่สามารถส่ง options เป็น dictionary ตอน initialize คลาส SamplerV2 ได้ (เช่น sampler = Sampler(backend, options={"default_shots": 1024})) หรือจะตั้งค่าหลังจาก initialize แล้วก็ได้:
    sampler = Sampler(backend)
    sampler.options.default_shots = 1024
  3. ตรวจสอบ options ที่รองรับ ทั้งหมดและอัปเดตตามความเหมาะสม
  4. จัดกลุ่ม Circuit แต่ละวงกับ observables และค่า parameter ที่ต้องการนำไปใช้ในรูปแบบ tuple (PUB) ตัวอย่างเช่น ใช้ (circuit1, parameter_set1) ถ้าต้องการรัน circuit1 ด้วย parameter_set1 สามารถระบุจำนวน shots สำหรับ PUB นั้นโดยเฉพาะได้ด้วย
  5. อัปเดต method run() ของ Sampler ให้รับ list ของ PUBs เช่น run([(circuit1, parameter_set1)]) สามารถระบุ shots ตรงนี้ได้ด้วย ซึ่งจะใช้กับ PUBs ทุกตัว
  6. ผลลัพธ์ของ Sampler V2 ถูกจัดกลุ่มตาม PUBs สามารถดูข้อมูล output ของแต่ละ PUB ได้โดย index เข้าไป ในขณะที่ Sampler V2 คืนค่า unweighted samples แต่คลาสผลลัพธ์มี convenience method สำหรับดู counts ได้ ตัวอย่างเช่น:
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()}")
หมายเหตุ

ต้องใช้ชื่อ classical register เพื่อดึงผลลัพธ์ โดย default จะตั้งชื่อว่า meas เมื่อใช้ measure_all() ถ้าสร้าง classical register หนึ่งตัวหรือมากกว่าด้วยชื่อที่ไม่ใช่ default ให้ใช้ชื่อนั้นในการดึงผลลัพธ์ สามารถหาชื่อ classical register ได้โดยรัน <circuit_name>.cregs เช่น qc.cregs

ตัวอย่างเต็มรูปแบบของ Sampler

รันการทดลองเดี่ยว

ใช้ Sampler เพื่อดู counts หรือการกระจาย quasi-probability ของ 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()

รันการทดลองหลายรายการในงานเดียว

ใช้ Sampler เพื่อดู counts หรือการกระจาย quasi-probability ของ 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

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()}")

รัน Circuit แบบ parameterized

รันการทดลองหลายรายการในงานเดียว โดยใช้ค่า parameter เพื่อเพิ่มความสามารถในการนำ Circuit กลับมาใช้ซ้ำ

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()}")

ใช้ Sessions และ advanced options

สำรวจ Sessions และ advanced options เพื่อเพิ่มประสิทธิภาพการทำงานของ Circuit บน QPU

ข้อควรระวัง

โค้ดบล็อกต่อไปนี้จะคืนค่า error สำหรับผู้ใช้ที่ใช้ Open Plan เนื่องจากใช้ sessions งานใน Open Plan สามารถรันได้เฉพาะใน job mode หรือ batch mode เท่านั้น

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()}")

ขั้นตอนถัดไป

คำแนะนำ
  • เรียนรู้เพิ่มเติมเกี่ยวกับการตั้งค่า options ในคู่มือ Specify options
  • เรียนรู้รายละเอียดเพิ่มเติมเกี่ยวกับ Primitive inputs and outputs
  • ทดลองกับบทเรียน CHSH Inequality
Source: IBM Quantum docs — updated 27 เม.ย. 2569
English version on doQumentation — updated 7 พ.ค. 2569
This translation based on the English version of 11 มี.ค. 2569