Langkau ke kandungan utama
Projek ini dalam fasa beta. Jumpa pepijat? Buat isu GitHub. Ada soalan atau idea? Tanya dalam Discussions โ€” kami suka dengar maklum balas anda!
Bagaimana kualiti terjemahan pada halaman ini?

Jalankan beban kerja kuantum dengan QRMI

Anggaran penggunaan: kurang daripada satu minit pada perkakasan IBM Quantumยฎ untuk bahagian SQD. Anggaran ini tidak termasuk masa beratur dan pemprosesan klasik; masa jalan boleh berbeza.

Hasil pembelajaranโ€‹

  1. Peranan yang dimainkan oleh QRMI sebagai middleware antara penjadual HPC dan perkakasan IBM Quantum

  2. Cara menggunakan kitaran hayat teras QRMI (acquire โ†’ task_start โ†’ task_status โ†’ task_result โ†’ release) terhadap backend IBMยฎ sebenar

  3. Cara menggunakan pembalut peringkat lebih tinggi Qiskit SamplerV2 dan QRMIService di atas QRMI

  4. Cara penjadual HPC (Slurm) menyuntik sumber kuantum melalui pemboleh ubah persekitaran dan cara aplikasi menggunakannya

  5. Cara menjalankan aliran kerja kimia SQD (Sample-based Quantum Diagonalization) yang lengkap pada N2_2 dengan menggunakan perkakasan IBM melalui QRMI

Prasyaratโ€‹

Latar belakangโ€‹

Cabaran integrasi kuantum-HPCโ€‹

Aliran kerja pengkomputeran berprestasi tinggi (HPC) selalunya memerlukan penyelarasan yang lancar antara kelompok pengkomputeran klasik dan unit pemprosesan kuantum (QPU). Backend dan perkhidmatan perkakasan kuantum yang berbeza mendedahkan mekanisme pengesahan, format wayar, dan API kitaran hayat kerja yang berbeza. Mengintegrasikan sistem IBM Quantum ke dalam pengurus beban kerja HPC (seperti Slurm) memerlukan antara muka standard yang bersih untuk perolehan sumber, pelaksanaan kerja, dan pengurusan Session.

Apa itu QRMIโ€‹

Quantum Resource Management Interface (QRMI) ialah pustaka middleware yang ditulis dalam Rust yang menyeragamkan akses kepada perkakasan kuantum daripada penjadual HPC dan aplikasi klasik. Ia mendedahkan satu API kitaran hayat yang bersatu:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ HPC Application Layer โ”‚
โ”‚ (Slurm job script / Python workflow / CUDA-Q) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚ QRMI API
โ”‚ acquire() / task_start() / task_result() / release()
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ QRMI Core (Rust) โ”‚
โ”‚ Python bindings ยท C bindings ยท Lua bindings โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
IBM Quantum Compute Service / IBM Quantum System

QRMI diterbitkan sebagai projek sumber terbuka di github.com/qiskit-community/qrmi dan diterangkan dalam kertas gambaran keseluruhan arXiv:2506.10052.

Pilihan reka bentuk utamaโ€‹

Kitaran hayat sumber, bukan kompilasi Circuit. QRMI mengendalikan kitaran hayat acquire/submit/poll/release dan tiada apa-apa lagi. Kompilasi Circuit, pengoptimuman, dan transpilation kekal di lapisan aplikasi (contohnya, Qiskit). Ini memastikan antara muka kekal minimum dan boleh digabungkan dengan komponen lain.

Model kebolehalihan vendor. Walaupun QRMI menyediakan panggilan pengurusan kerja yang sama (acquire, task_start, task_status, task_result, release) merentasi Backend perkakasan yang disokong, menukar vendor juga memerlukan laluan kompilasi yang berbeza, pembinaan payload khusus vendor, dan penyahkodan hasil di lapisan aplikasi.

Format payload IBM asli. Untuk Backend IBM Quantum, QRMI menggunakan payload JSON OpenQASM 3 (QiskitPrimitive) yang mematuhi skema Qiskit Runtime.

Konfigurasi melalui pemboleh ubah persekitaran. Kelayakan dan URL titik akhir dibaca daripada pemboleh ubah persekitaran semasa runtime. Dalam kelompok HPC, plugin SPANK QRMI Slurm menetapkan ini secara automatik apabila kerja dihantar. Dalam notebook atau sesi interaktif, anda memuatkannya daripada fail .env. Kod aplikasi tidak pernah mengandungi kelayakan atau URL titik akhir yang dikodkan keras.

Integrasi penjadual HPC melalui GRES. Apabila kerja Slurm meminta sumber kuantum menggunakan antara muka plugin SPANK QRMI (#SBATCH --gres=qpu:1 dan #SBATCH --qpu=ibm_kingston), plugin tersebut menyuntik QRMI_JOB_QPU_RESOURCES dan QRMI_JOB_QPU_TYPES ke dalam persekitaran kerja. Aplikasi memanggil get_job_qpu_resources_and_types() untuk mengetahui sumber yang telah diperuntukkan โ€” tiada nama Backend yang dikodkan keras diperlukan. QRMIService membungkus corak ini untuk pengguna Qiskit.

Panggilan API terasโ€‹

PanggilanTujuan
qrmi.acquire()Memperoleh akses kepada sumber (contohnya, membuka sesi khusus); mengembalikan token kunci
qrmi.target()Mendapatkan keupayaan Backend (Qubit, Gate, peta gandingan) sebagai JSON
qrmi.task_start(payload)Menghantar kerja kuantum; mengembalikan ID kerja
qrmi.task_status(job_id)Menyemak status kerja (Queued, Running, Completed, Failed)
qrmi.task_result(job_id)Mendapatkan hasil kerja yang telah selesai sebagai rentetan JSON mentah
qrmi.task_stop(job_id)Membatalkan atau membersihkan kerja
qrmi.release(lock)Melepaskan kunci sumber (contohnya, menutup sesi)

Apa yang diliputi oleh tutorial iniโ€‹

Tutorial ini disusun kepada dua bahagian:

Langkah 1โ€“3 (contoh skala kecil): Memperkenalkan API QRMI dengan demonstrasi Circuit keadaan Bell yang mudah pada perkakasan IBM Quantum, merangkumi penggunaan primitif peringkat rendah secara langsung serta integrasi peringkat tinggi QRMIService dan SamplerV2.

Contoh perkakasan berskala besar: Aliran kerja SQD lengkap untuk molekul N2_2 pada jarak ikatan 1.0 Aหš\AA (ruang aktif asas cc-pVDZ, 26 orbital spatial / 52 Qubit), dilaksanakan pada perkakasan IBM Quantum melalui QRMI. SQD menggabungkan pensampelan kuantum ansatz LUCJ yang dibina dengan ffsim dan pemulihan konfigurasi swakonsisten dengan qiskit-addon-sqd.

# Added by doQumentation โ€” installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"dotenv": "python-dotenv", "ffsim": "ffsim", "matplotlib": "matplotlib", "numpy": "numpy", "pyscf": "pyscf", "qiskit": "qiskit", "qiskit_addon_sqd": "qiskit-addon-sqd", "qrmi": "qrmi"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")

Keperluanโ€‹

Sebelum memulakan tutorial ini, pastikan anda telah memasang perkara berikut.

Persediaan persekitaran Pythonโ€‹

Wheel binari yang telah dibina tersedia untuk Linux di PyPI, jadi pip install standard berfungsi terus pada sistem Linux/HPC.

python3 -m venv ~/.venvs/qrmi-ibm
source ~/.venvs/qrmi-ibm/bin/activate
python -m pip install "qrmi[ibm]" python-dotenv pyscf ffsim qiskit-addon-sqd matplotlib ipykernel
python -m ipykernel install --user --name qrmi-ibm --display-name "QRMI IBM"
Platform tanpa wheel yang telah dibina

Jika pip membina QRMI daripada sumber, pastikan anda mempunyai rangkaian alat Rust moden (Rust โ‰ฅ 1.91.1 dipasang melalui rustup daripada rustup.rs).

Pilih kernel QRMI IBM dalam Jupyter, kemudian mulakan semula dan jalankan sel notebook mengikut urutan. Output yang disimpan adalah daripada larian perkakasan penyumbang; arahan pemasangan tidak menyatakan versi tepat yang digunakan untuk larian tersebut.

Kelayakan diperlukanโ€‹

Untuk pelaksanaan secara kendiri, cipta fail .env bersebelahan dengan notebook ini dengan nilai berikut, menggantikan placeholder kelayakan. Rahsiakan fail ini. Jika anda memilih Backend yang berbeza, kemas kini kedua-dua namanya dan awalan pemboleh ubah persekitaran.

ibm_kingston_QRMI_IBM_QCS_ENDPOINT=https://quantum.cloud.ibm.com/api/v1
ibm_kingston_QRMI_IBM_QCS_IAM_ENDPOINT=https://iam.cloud.ibm.com
ibm_kingston_QRMI_IBM_QCS_IAM_APIKEY=<your-iam-api-key>
ibm_kingston_QRMI_IBM_QCS_SERVICE_CRN=<your-crn-starting-with-crn:v1:>
ibm_kingston_QRMI_IBM_QCS_SESSION_MODE=dedicated
ibm_kingston_QRMI_IBM_QCS_SESSION_MAX_TTL=28800
QRMI_JOB_QPU_RESOURCES=ibm_kingston
QRMI_JOB_QPU_TYPES=ibm-quantum-compute-service

Untuk peruntukan Slurm, gunakan tetapan sumber dan kelayakan yang dibekalkan oleh kelompok. Notebook ini mengekalkan nilai persekitaran yang sedia ada.

Persediaanโ€‹

Import kebergantungan dan muatkan konfigurasi sumber.

import os
import time
import json
import numpy as np
from dotenv import load_dotenv

from qrmi import (
QuantumResource,
ResourceType,
Payload,
TaskStatus,
get_job_qpu_resources_and_types,
)
from qrmi.primitives import QRMIService
from qrmi.primitives.ibm import SamplerV2, get_target

from qiskit import QuantumCircuit, qasm3
from qiskit.circuit.library import efficient_su2
from qiskit.primitives.containers.sampler_pub import SamplerPub
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

# Load credentials from .env without overriding already-set scheduler environment variables
load_dotenv(override=False)

# Preserve resources if injected by Slurm SPANK plugin; fallback to default for interactive run
BACKEND_NAME = os.environ.get("QRMI_JOB_QPU_RESOURCES", "ibm_kingston")
os.environ.setdefault("QRMI_JOB_QPU_RESOURCES", BACKEND_NAME)
os.environ.setdefault("QRMI_JOB_QPU_TYPES", "ibm-quantum-compute-service")

print(f"Backend: {BACKEND_NAME}")
print("Environment ready.")
Backend: ibm_kingston
Environment ready.

Contoh skala kecilโ€‹

Langkah 1โ€“3 memperkenalkan API QRMI dengan menggunakan Circuit yang mudah. Setiap langkah memetakan kepada fasa teras kitaran hayat QRMI terhadap perkakasan IBM Quantum.

Payload untuk langkah-langkah awal ini ialah Circuit keadaan Bell kecil yang dipilih supaya pantas dan murah untuk dijalankan.

Contoh-contoh ini menggunakan perkakasan kerana ia menunjukkan peruntukan sumber jauh dan pengurusan kerja. Simulator Circuit tempatan tidak mengesahkan integrasi perkhidmatan QRMI dan penjadual. Menjalankan notebook ini menghantar kerja IBM Quantum dan memerlukan akses kepada Backend yang dikonfigurasikan.

Langkah 1: Petakan masalah klasik kepada sumber kuantumโ€‹

Langkah pertama dalam mana-mana aliran kerja QRMI ialah mencipta objek QuantumResource dan mengesahkan bahawa ia boleh diakses.

get_target() mendapatkan penerangan perkakasan Backend (bilangan Qubit, Gate asas, peta gandingan) dan membungkusnya sebagai objek Target Qiskit, yang digunakan oleh Transpiler dalam Langkah 2.

Langkah 2: Optimumkan masalah untuk pelaksanaan perkakasan kuantumโ€‹

Sebelum penghantaran, gunakan Qiskit untuk melakukan transpile Circuit kepada seni bina set arahan (ISA) Backend, menggunakan objek Target yang diperoleh dalam Langkah 1.

Contoh ini kemudian membina Payload.QiskitPrimitive, yang membungkus rentetan Circuit OpenQASM 3 dan metadata kerja ke dalam skema primitif IBM.

Langkah 3: Laksanakan menggunakan primitif QRMIโ€‹

Setelah payload dibina, contoh ini menghantar kerja dan menyemak status sehingga selesai. task_start() mengembalikan ID kerja dengan serta-merta; task_status() disemak sehingga status tidak lagi Queued/Running. Hasil diperoleh sebagai rentetan JSON mentah dan dihuraikan untuk mengekstrak sampel pengukuran.

Sel berikut mengekalkan perolehan, pelaksanaan, dan pembersihan bersama supaya kegagalan selepas perolehan tetap melepaskan sesi yang dimiliki oleh notebook.

# โ”€โ”€ IBM Quantum โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
qrmi = QuantumResource(BACKEND_NAME, ResourceType.IBMQuantumComputeService)
# ResourceType.IBMQuantumSystem is the alternative for directly provisioned systems

print(f"Resource id: {qrmi.resource_id()}")
print(f"Resource type: {qrmi.resource_type()}")
print(f"Accessible: {qrmi.is_accessible()}")

# Acquire exclusive access โ€” open try/finally immediately so every
# subsequent failure (target retrieval, transpilation, submission) is covered.
# Release is skipped when running under Slurm: the SPANK plugin owns the
# session lifecycle and will release it when the job finishes.
lock = qrmi.acquire()
print(f"Lock token: {lock}")
try:
# Retrieve backend capabilities
transpiler_target = get_target(
qrmi
) # calls qrmi.target() and parses the JSON
target_json = json.loads(qrmi.target().value)
config = target_json.get("configuration", {})
print(f"\nBackend: {config.get('backend_name', 'unknown')}")
print(f"Qubits: {config.get('n_qubits', 'unknown')}")
print(f"Gates: {config.get('basis_gates', [])}")

# โ”€โ”€ IBM Quantum โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

# Build a Bell state circuit
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
print(qc.draw("text"))

# Transpile to ISA using the target retrieved in Step 1
pm = generate_preset_pass_manager(
optimization_level=1, target=transpiler_target
)
isa_circuit = pm.run(qc)
print(f"\nTranspiled gate counts: {isa_circuit.count_ops()}")

# Build the QRMI payload
# Payload.QiskitPrimitive wraps the IBM SamplerV2 input schema:
# pubs: list of [qasm3_string, parameter_values] (shots goes at top level)
# program_id: "sampler" or "estimator"
shots = 1024
pub = SamplerPub.coerce((isa_circuit,), shots)
qasm3_str = qasm3.dumps(
pub.circuit,
disable_constants=True,
allow_aliasing=True,
experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1,
)
# Parameter values as a flat list (empty for non-parametric circuits)
param_array = pub.parameter_values.as_array(
pub.circuit.parameters
).tolist()

input_json = {
"pubs": [
[qasm3_str, param_array]
], # list-of-lists; shots at top level
"version": 2,
"support_qiskit": False, # True returns binary-encoded Qiskit result
"shots": shots,
}
payload = Payload.QiskitPrimitive(
input=json.dumps(input_json), program_id="sampler"
)
print("Payload ready")

# โ”€โ”€ IBM Quantum โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

# Submit the job
job_id = qrmi.task_start(payload)
print(f"Job submitted: {job_id}")

# Poll until complete
while True:
status = qrmi.task_status(job_id)
print(f" Status: {status}")
if status not in [TaskStatus.Running, TaskStatus.Queued]:
break
time.sleep(5)

print(f"\nFinal status: {status}")

# Retrieve results
# support_qiskit=False โ†’ plain JSON; parse directly without ResultDecoder
if status == TaskStatus.Completed:
raw = qrmi.task_result(job_id).value
result = json.loads(raw)
# IBM QCS plain-JSON result shape: {"results": [{"data": {"meas": {"samples": [...]}}}]}
# samples is a list of hex-encoded integers; decode to zero-padded bitstrings
samples = result["results"][0]["data"]["meas"]["samples"]
num_bits = sum(reg.size for reg in isa_circuit.cregs)
from collections import Counter

counts = Counter(format(int(s, 16), f"0{num_bits}b") for s in samples)
print(f"\nMeasurement counts: {dict(counts.most_common(8))}")
qrmi.task_stop(job_id)
else:
print(f"Job did not complete. Logs:\n{qrmi.task_logs(job_id)}")

finally:
# Release only in interactive sessions; under Slurm the SPANK plugin
# manages the session lifecycle and calling release() here would
# prematurely close a session it does not own.
if not os.environ.get("SLURM_JOB_ID"):
qrmi.release(lock)
print("\nSession released.")
Resource id: ibm_kingston
Resource type: ResourceType.IBMQuantumComputeService
Accessible: True
Lock token: 2ff43011-aed1-4436-a4df-40f37ec588b7

Backend: ibm_kingston
Qubits: 156
Gates: ['cz', 'id', 'rx', 'rz', 'rzz', 'sx', 'x', 'xslow']
โ”Œโ”€โ”€โ”€โ” โ–‘ โ”Œโ”€โ”
q_0: โ”ค H โ”œโ”€โ”€โ– โ”€โ”€โ”€โ–‘โ”€โ”คMโ”œโ”€โ”€โ”€
โ””โ”€โ”€โ”€โ”˜โ”Œโ”€โ”ดโ”€โ” โ–‘ โ””โ•ฅโ”˜โ”Œโ”€โ”
q_1: โ”€โ”€โ”€โ”€โ”€โ”ค X โ”œโ”€โ–‘โ”€โ”€โ•ซโ”€โ”คMโ”œ
โ””โ”€โ”€โ”€โ”˜ โ–‘ โ•‘ โ””โ•ฅโ”˜
meas: 2/โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•ฉโ•
0 1

Transpiled gate counts: OrderedDict([('rz', 6), ('sx', 3), ('measure', 2), ('cz', 1), ('barrier', 1)])
Payload ready
Job submitted: dai43g8mhr3c73e7a7o0
Status: TaskStatus.Queued
Status: TaskStatus.Running
Status: TaskStatus.Completed

Final status: TaskStatus.Completed

Measurement counts: {'11': 487, '00': 254, '01': 177, '10': 106}

Session released.

Antara muka Qiskit peringkat tinggi: QRMIService dan SamplerV2โ€‹

Kitaran hayat mentah di atas menyediakan kawalan eksplisit ke atas setiap panggilan. Untuk aliran kerja Qiskit standard, QRMI menyediakan primitif SamplerV2 yang melaksanakan BaseSamplerV2.

Pengurusan kitaran hayat

SamplerV2 mengendalikan pensirian payload, penghantaran (task_start), penyemakan status, dan penyahkodan hasil. Dalam persekitaran kelompok HPC (contohnya, dengan Slurm), peruntukan dan pelepasan diurus oleh penjadual dan plugin SPANK. Dalam sesi Python interaktif yang menggunakan objek API peringkat rendah secara langsung, acquire() dan release() boleh digunakan untuk mengurus sesi khusus secara eksplisit.

# QRMIService reads QRMI_JOB_QPU_RESOURCES / QRMI_JOB_QPU_TYPES set in Setup or Slurm
service = QRMIService()
qrmi_svc = service.resources()[0]
print(f"Using: {qrmi_svc.resource_id()} ({qrmi_svc.resource_type()})")

# Build an EfficientSU2 circuit
circuit = efficient_su2(5, entanglement="linear")
circuit.measure_all()
param_values = np.random.rand(circuit.num_parameters)

pm = generate_preset_pass_manager(
optimization_level=1, target=get_target(qrmi_svc)
)
isa_circuit = pm.run(circuit)

# SamplerV2 executes jobs against the QRMI resource and decodes results into primitive containers
sampler = SamplerV2(qrmi_svc, options={"default_shots": 1024})
job = sampler.run([(isa_circuit, param_values)])
print(f"Job ID: {job.job_id()} | Status: {job.status()}")

# Poll with retry โ€” re-raise immediately on permanent failures;
# only retry on transient network/timeout errors (connection resets, 503s).
_TRANSIENT = (
"503",
"Service Unavailable",
"ConnectionError",
"TimeoutError",
"timed out",
"Connection reset",
)
result = None
for attempt in range(60):
try:
result = job.result() # blocks until complete
break
except Exception as e:
if not any(tok in str(e) for tok in _TRANSIENT):
raise
print(f" Transient error on attempt {attempt + 1}: {e}")
time.sleep(10)

if result is not None:
counts = result[0].data.meas.get_counts()
print(f"Counts (first 5): {dict(list(counts.items())[:5])}")
else:
print("Job did not complete after retries.")

if job.errored():
print(f"Logs:\n{job.logs()}")
Using: ibm_kingston (ResourceType.IBMQuantumComputeService)
Job ID: dai43jj9k43c73afhrhg | Status: JobStatus.QUEUED
Counts (first 5): {'00010': 66, '00100': 28, '11000': 71, '00110': 23, '10100': 14}

Konteks HPC: Suntikan sumber Slurmโ€‹

Dalam kelompok HPC, pengguna meminta sumber kuantum menggunakan sintaks GRES Slurm bersama-sama dengan pilihan plugin SPANK QRMI. Plugin tersebut mengendalikan suntikan kelayakan dan sumber secara automatik:

#SBATCH --gres=qpu:1
#SBATCH --qpu=ibm_kingston
python my_workflow.py # QRMI_JOB_QPU_RESOURCES and QRMI_JOB_QPU_TYPES are already set

Kod aplikasi mengetahui sumber yang telah diperuntukkan kepadanya semasa runtime โ€” tiada nama Backend yang dikodkan keras:

# get_job_qpu_resources_and_types() reads QRMI_JOB_QPU_RESOURCES / QRMI_JOB_QPU_TYPES
# set by the Slurm SPANK plugin (or manually above in Setup)
qpus, qpu_types = get_job_qpu_resources_and_types()
print("Resources allocated by scheduler:")
for qpu, qpu_type in zip(qpus, qpu_types):
print(f" {qpu} ({qpu_type})")

# QRMIService wraps this into a list of ready QuantumResource objects
for r in QRMIService().resources():
print(
f"\nQRMIService found: {r.resource_id()} accessible={r.is_accessible()}"
)
Resources allocated by scheduler:
ibm_kingston (ibm-quantum-compute-service)

QRMIService found: ibm_kingston accessible=True

Contoh perkakasan berskala besar: SQD pada N2_2โ€‹

Di sini kami menggabungkan semua komponen ke dalam aliran kerja kimia kuantum yang lengkap pada skala yang lebih besar, dilaksanakan pada perkakasan IBM Quantum sebenar melalui QRMI.

SQD menggabungkan yang berikut:

  1. Pensampelan kuantum ansatz Local Unitary Cluster Jastrow (LUCJ) yang dibina menggunakan ffsim dan dimulakan daripada amplitud CCSD
  2. Transpilation sedar-perkakasan yang sepadan dengan topologi kekisi heavy-hex melalui generate_lucj_pass_manager
  3. Pelaksanaan pensampelan pada perkakasan IBM Quantum yang diuruskan melalui QRMIService dan QRMI SamplerV2
  4. Pasca-pemprosesan klasik: pemulihan konfigurasi swakonsisten dan pendiagonalan subruang berulang menggunakan qiskit-addon-sqd

Kami menggunakan SQD pada N2_2 pada jarak ikatan 1.0 Aหš\AA dengan ruang aktif yang diperoleh daripada set asas cc-pVDZ (26 orbital spatial, bersamaan dengan 52 spin-orbital/Qubit).

Tenaga rujukan untuk ruang aktif N2_2 /cc-pVDZ (jarak ikatan 1.0 Aหš\AA):

  • Tenaga rujukan (pengiraan SCI berasingan): โˆ’109.22802922 Ha
Ketepatan larian yang disimpan

Larian SQD di bawah menunjukkan pelaksanaan QRMI hujung-ke-hujung yang berjaya pada perkakasan IBM Quantum. Dengan satu ulangan LUCJ dan 100,000 shot, hasilnya berakhir kira-kira 23.7 kcal/mol di atas tenaga rujukan dan tidak mencapai ketepatan kimia (โ‰ค 1 kcal/mol). Menukar n_reps, bilangan shot, atau bilangan iterasi SQD mungkin dapat meningkatkan ketepatan, tetapi memerlukan ujian lanjut.

Dalam larian yang disimpan, pengurus laluan ffsim mengalih keluar interaksi spin bertentangan (24, 24) dan (20, 20) kerana Backend tidak dapat menampungnya. Hasil yang dilaporkan menggunakan Circuit yang telah diselaraskan ini.

from qrmi.primitives.ibm import get_backend
import math
import os
import time
from functools import partial
from dotenv import load_dotenv
import numpy as np
import matplotlib.pyplot as plt

import pyscf
import pyscf.gto
import pyscf.scf
import pyscf.cc
import pyscf.mcscf
import pyscf.ao2mo

import ffsim
import ffsim.qiskit
from qiskit import QuantumCircuit, QuantumRegister
from qiskit_addon_sqd.fermion import (
SCIResult,
diagonalize_fermionic_hamiltonian,
solve_sci_batch,
)
from qrmi.primitives import QRMIService
from qrmi.primitives.ibm import SamplerV2, get_target

load_dotenv(override=False)
os.environ.setdefault("QRMI_JOB_QPU_RESOURCES", "ibm_kingston")
os.environ.setdefault("QRMI_JOB_QPU_TYPES", "ibm-quantum-compute-service")

# โ”€โ”€ Step 1: Map classical inputs to a quantum problem โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

# Build N2 molecule at 1.0 ร… bond distance
mol = pyscf.gto.Mole()
mol.build(
atom=[["N", (0, 0, 0)], ["N", (1.0, 0, 0)]],
basis="cc-pvdz",
symmetry="Dooh",
)

# Define active space: freeze 2 core orbitals
n_frozen = 2
active_space = range(n_frozen, mol.nao_nr())

# Get molecular integrals
scf = pyscf.scf.RHF(mol).run()
norb = len(active_space)
n_electrons = int(sum(scf.mo_occ[active_space]))
n_alpha = (n_electrons + mol.spin) // 2
n_beta = (n_electrons - mol.spin) // 2
nelec = (n_alpha, n_beta)

cas = pyscf.mcscf.CASCI(scf, norb, nelec)
mo = cas.sort_mo(active_space, base=0)
hcore, nuclear_repulsion_energy = cas.get_h1cas(mo)
eri = pyscf.ao2mo.restore(1, cas.get_h2cas(mo), norb)

# Reference energy from external SCI calculation
reference_energy = -109.22802921665716

print(
f"Nโ‚‚/cc-pVDZ active space: {norb} orbitals ({2 * norb} qubits), {nelec} electrons"
)
print(f"SCF energy: {scf.e_tot:.8f} Ha")
print(f"Reference energy: {reference_energy:.8f} Ha")

# Get CCSD amplitudes for initializing the LUCJ ansatz
ccsd = pyscf.cc.CCSD(
scf, frozen=[i for i in range(mol.nao_nr()) if i not in active_space]
).run()
t1 = ccsd.t1
t2 = ccsd.t2
print(f"CCSD energy: {ccsd.e_tot:.8f} Ha")

# Discover backend via QRMIService (QRMI_JOB_QPU_RESOURCES set in Setup)
service = QRMIService()
qrmi_sqd = service.resources()[0]
print(f"Using QRMI resource: {qrmi_sqd.resource_id()}")

# get_backend() wraps the QRMI resource as a Qiskit backend for layout synthesis

backend = get_backend(qrmi_sqd)

# Set ansatz properties
n_reps = 1
pairs_aa = [(p, p + 1) for p in range(norb - 1)]
pairs_ab = None

# Create pass manager adapted to hardware heavy-hex topology
pass_manager, pairs_ab = ffsim.qiskit.generate_lucj_pass_manager(
backend=backend,
norb=norb,
connectivity="heavy-hex",
interaction_pairs=(pairs_aa, pairs_ab),
optimization_level=3,
)

# Create the compressed LUCJ ansatz operator
ucj_op = ffsim.UCJOpSpinBalanced.from_t_amplitudes(
t2=t2,
t1=t1,
n_reps=n_reps,
interaction_pairs=(pairs_aa, pairs_ab),
optimize=True,
options=dict(maxiter=1000),
)

# Assemble the circuit
qubits = QuantumRegister(2 * norb, name="q")
circuit = QuantumCircuit(qubits)
circuit.append(ffsim.qiskit.PrepareHartreeFockJW(norb, nelec), qubits)
circuit.append(ffsim.qiskit.UCJOpSpinBalancedJW(ucj_op), qubits)
circuit.measure_all()
print(f"LUCJ circuit: {circuit.num_qubits} qubits, depth {circuit.depth()}")

# โ”€โ”€ Step 2: Optimize for quantum hardware execution โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

isa_circuit = pass_manager.run(circuit)
print(f"Transpiled gate counts: {isa_circuit.count_ops()}")

# โ”€โ”€ Step 3: Execute using Qiskit primitives (QRMI SamplerV2) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

sampler = SamplerV2(qrmi_sqd, options={"default_shots": 100_000})
# sampler.options.environment.job_tags = ["TUT_SQD"]
job = sampler.run([(isa_circuit,)])
print(f"Job submitted via QRMI: {job.job_id()} | Status: {job.status()}")
print("Waiting for results from hardware...")

_TRANSIENT = (
"503",
"Service Unavailable",
"ConnectionError",
"TimeoutError",
"timed out",
"Connection reset",
)
primitive_result = None
for attempt in range(120):
try:
primitive_result = job.result()
break
except Exception as e:
if not any(tok in str(e) for tok in _TRANSIENT):
raise
print(f" Transient error on attempt {attempt + 1}: {e}")
time.sleep(10)

if primitive_result is None:
raise RuntimeError("Job did not complete after retries")

pub_result = primitive_result[0]
bit_array = pub_result.data.meas
print(f"Total shots collected: {bit_array.num_shots}")

# โ”€โ”€ Step 4: Post-process and return result in classical format โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

def is_valid_bitstring(
bitstring: str, norb: int, nelec: tuple[int, int]
) -> bool:
n_a, n_b = nelec
return (
len(bitstring) == 2 * norb
and bitstring[norb:].count("1") == n_a
and bitstring[:norb].count("1") == n_b
)

num_valid = sum(
is_valid_bitstring(b, norb, nelec) for b in bit_array.get_bitstrings()
)
valid_fraction = num_valid / bit_array.num_shots
expected_random = (
math.comb(norb, n_alpha) * math.comb(norb, n_beta) / (2 ** (2 * norb))
)
print(f"Fraction of valid configurations sampled: {valid_fraction:.5f}")
print(f"Expected fraction from uniform random: {expected_random:.4e}")

# Configure SQD eigensolver
energy_tol = 1e-3
occupancies_tol = 1e-3
max_iterations = 5
num_batches = 3
samples_per_batch = 300
symmetrize_spin = True
carryover_threshold = 1e-4
max_cycle = 200

# Hartree-Fock initial occupancy guess
initial_occupancies = (
np.array([1] * n_alpha + [0] * (norb - n_alpha)),
np.array([1] * n_beta + [0] * (norb - n_beta)),
)

sci_solver = partial(solve_sci_batch, spin_sq=0.0, max_cycle=max_cycle)
result_history = []

def callback(results: list[SCIResult]):
result_history.append(results)
iteration = len(result_history)
print(f"Iteration {iteration}")
for i, res in enumerate(results):
subspace_dim = np.prod(res.sci_state.amplitudes.shape)
print(
f" Subsample {i}: Energy = {res.energy + nuclear_repulsion_energy:.8f} Ha | Subspace dim = {subspace_dim}"
)

print("\nRunning SQD post-processing...")
rng = np.random.default_rng(42)
sqd_result = diagonalize_fermionic_hamiltonian(
hcore,
eri,
bit_array,
samples_per_batch=samples_per_batch,
norb=norb,
nelec=nelec,
num_batches=num_batches,
energy_tol=energy_tol,
occupancies_tol=occupancies_tol,
max_iterations=max_iterations,
sci_solver=sci_solver,
symmetrize_spin=symmetrize_spin,
initial_occupancies=initial_occupancies,
carryover_threshold=carryover_threshold,
callback=callback,
seed=rng,
)

final_energy = sqd_result.energy + nuclear_repulsion_energy
energy_error = final_energy - reference_energy

print("\n=== Energy Summary (Nโ‚‚/cc-pVDZ active space) ===")
print(f"SCF energy: {scf.e_tot:.8f} Ha")
print(f"Reference energy: {reference_energy:.8f} Ha")
print(f"Final SQD energy: {final_energy:.8f} Ha")
print(
f"Energy error: {energy_error:.8f} Ha ({abs(energy_error) * 627.5:.4f} kcal/mol)"
)

# โ”€โ”€ Visualization โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

x1 = range(len(result_history))
min_e = [
min(res, key=lambda r: r.energy).energy + nuclear_repulsion_energy
for res in result_history
]
e_diff = [abs(e - reference_energy) for e in min_e]
chem_accuracy = 0.001 # ~1 mHa / ~0.6 kcal/mol

y2 = np.sum(sqd_result.orbital_occupancies, axis=0)
x2 = range(len(y2))

fig, axs = plt.subplots(1, 2, figsize=(12, 5))

# Energies convergence plot
axs[0].plot(x1, e_diff, label="Energy error", marker="o")
axs[0].set_xticks(list(x1))
axs[0].set_xticklabels(list(x1))
axs[0].set_yscale("log")
axs[0].axhline(
y=chem_accuracy,
color="#BF5700",
linestyle="--",
label="Chemical accuracy (1 mHa)",
)
axs[0].set_title("SQD Energy Error vs Iteration")
axs[0].set_xlabel("Iteration")
axs[0].set_ylabel("Energy Error (Ha)")
axs[0].legend()

# Spatial orbital occupancy plot
axs[1].bar(x2, y2, width=0.8)
axs[1].set_xticks(list(x2)[::2])
axs[1].set_xticklabels(list(x2)[::2])
axs[1].set_title("Avg Occupancy per Spatial Orbital")
axs[1].set_xlabel("Spatial Orbital Index")
axs[1].set_ylabel("Avg Occupancy")

plt.tight_layout()
plt.show()
WARN: Unable to to identify input symmetry using original axes.
Different symmetry axes will be used.

converged SCF energy = -108.929838385609
Nโ‚‚/cc-pVDZ active space: 26 orbitals (52 qubits), (5, 5) electrons
SCF energy: -108.92983839 Ha
Reference energy: -109.22802922 Ha
E(CCSD) = -109.2177884185545 E_corr = -0.2879500329450047
CCSD energy: -109.21778842 Ha
Using QRMI resource: ibm_kingston
LUCJ circuit: 52 qubits, depth 3
Transpiled gate counts: OrderedDict([('sx', 7041), ('rz', 6969), ('cz', 1858), ('measure', 52), ('x', 47), ('barrier', 1)])
Job submitted via QRMI: dai43o0mhr3c73e7a81g | Status: JobStatus.QUEUED
Waiting for results from hardware...
Total shots collected: 100000
Fraction of valid configurations sampled: 0.00319
Expected fraction from uniform random: 9.6079e-07

Running SQD post-processing...
Iteration 1
Subsample 0: Energy = -109.09341960 Ha | Subspace dim = 208849
Subsample 1: Energy = -109.11738590 Ha | Subspace dim = 204304
Subsample 2: Energy = -109.09947704 Ha | Subspace dim = 212521
Iteration 2
Subsample 0: Energy = -109.16015998 Ha | Subspace dim = 332929
Subsample 1: Energy = -109.16823702 Ha | Subspace dim = 319225
Subsample 2: Energy = -109.16189785 Ha | Subspace dim = 336400
Iteration 3
Subsample 0: Energy = -109.17759299 Ha | Subspace dim = 471969
Subsample 1: Energy = -109.17937442 Ha | Subspace dim = 512656
Subsample 2: Energy = -109.17970409 Ha | Subspace dim = 504100
Iteration 4
Subsample 0: Energy = -109.18410905 Ha | Subspace dim = 608400
Subsample 1: Energy = -109.18265405 Ha | Subspace dim = 636804
Subsample 2: Energy = -109.18608430 Ha | Subspace dim = 657721
Iteration 5
Subsample 0: Energy = -109.18870837 Ha | Subspace dim = 846400
Subsample 1: Energy = -109.18890818 Ha | Subspace dim = 848241
Subsample 2: Energy = -109.19022232 Ha | Subspace dim = 804609

=== Energy Summary (Nโ‚‚/cc-pVDZ active space) ===
SCF energy: -108.92983839 Ha
Reference energy: -109.22802922 Ha
Final SQD energy: -109.19022232 Ha
Energy error: 0.03780690 Ha (23.7238 kcal/mol)

Output of the previous code cell

Langkah seterusnyaโ€‹

Cadangan

Jika anda mendapati kerja ini menarik, anda mungkin berminat dengan bahan berikut: