當量子運算從實驗室走向雲端平台,開發者開始有機會透過 API 存取真實的量子處理器。IBM Q Experience 作為業界領先的量子雲端平台,提供了完整的 REST API 讓開發者能夠程式化地操作量子電腦。然而官方的 SDK 主要以 Python 為主,對於熟悉 JavaScript 生態系統的 Web 開發者來說,缺乏一個原生的 Node.js 解決方案。Node.js 的非同步特性與事件驅動架構,實際上非常適合處理量子實驗提交這類需要等待執行結果的操作。
IBM Q Experience 的 REST API 設計遵循標準的 RESTful 原則,提供了從身份驗證、裝置查詢到實驗提交的完整功能。開發者可以透過 API 取得可用的量子處理器列表,查詢每個裝置的校準參數與即時狀態,然後使用 QASM 量子組合語言描述量子電路,最後提交到選定的裝置執行。整個流程涉及多個非同步的網路請求,需要妥善處理錯誤與狀態管理。
本文將從零開始建構一個 Node.js 的 IBM Q Experience API 客戶端,展示如何使用現代的 JavaScript 特性來簡化非同步程式設計。我們將深入探討身份驗證機制的實作、裝置資訊的查詢方法,以及如何組織 QASM 程式碼並提交量子實驗。透過這個實作過程,讀者不僅能學習到量子運算 API 的使用方式,也能掌握 Node.js 非同步程式設計的最佳實務。
IBM Q Experience REST API 架構
IBM Q Experience 平台的 REST API 提供了一個標準化的介面,讓開發者能夠透過 HTTP 請求與量子運算資源互動。API 的基礎 URL 為 https://quantumexperience.ng.bluemix.net/api,所有的端點都建構在這個基礎之上。身份驗證採用 Token 機制,使用者需要先從 IBM Q Experience 網站取得 API Token,然後透過登入端點換取臨時的存取令牌,後續的所有請求都需要攜帶這個存取令牌。
API 的設計分為幾個主要功能區塊。使用者管理端點負責身份驗證與帳戶資訊查詢,後端裝置端點提供量子處理器與模擬器的列表與詳細資訊,任務端點則用於提交與管理量子實驗。對於企業客戶,IBM 還提供了專屬的端點路徑,包含 Hub、Group 與 Project 的階層結構,允許組織內的團隊共享與管理量子運算資源。
在使用 API 時需要注意幾個關鍵點。首先是請求頭的設定,所有請求都應該包含 x-qx-client-application 標頭來識別客戶端應用程式。其次是錯誤處理,API 可能回傳各種錯誤狀態碼,包括認證失敗、資源不存在或服務暫時不可用。最後是速率限制,雖然 IBM 沒有明確公布具體的限制值,但建議在應用程式中實作適當的請求間隔控制。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 14
skinparam minClassWidth 100
|開發者|
start
:準備 API Token;
note right
從 IBM Q Experience
網站取得
end note
|Node.js 客戶端|
:初始化配置;
:呼叫 loginWithToken;
|IBM Q API|
:驗證 API Token;
if (Token 有效?) then (是)
:產生存取令牌\n(Access Token);
|Node.js 客戶端|
:儲存存取令牌;
:查詢可用裝置;
|IBM Q API|
:回傳裝置列表;
|Node.js 客戶端|
:選擇目標裝置;
:準備 QASM 程式碼;
:提交量子實驗;
|IBM Q API|
:驗證實驗參數;
:加入執行佇列;
:回傳任務 ID;
|Node.js 客戶端|
:接收任務 ID;
|開發者|
:等待實驗完成;
stop
else (否)
:回傳認證錯誤;
|Node.js 客戶端|
:拋出例外;
stop
endif
@enduml建構 Node.js API 客戶端的基礎架構
建立一個可維護的 API 客戶端需要良好的專案結構與模組化設計。首先建立專案目錄並初始化 Node.js 專案,這會產生 package.json 檔案來管理相依套件與專案中繼資料。主要的實作程式碼放在 index.js 中,這個檔案將匯出所有公開的 API 方法供外部使用。
# 建立專案目錄
mkdir IBMQuantumExperience
cd IBMQuantumExperience
# 初始化 Node.js 專案
# 這會互動式地建立 package.json 檔案
npm init
# 安裝必要的相依套件
# request 套件用於發送 HTTP 請求
npm install request
專案的核心是 index.js 檔案,這個模組匯出所有與 IBM Q Experience API 互動的方法。模組採用單例模式設計,內部維護配置資訊與存取令牌等狀態。對外提供的 API 包含初始化方法、裝置查詢方法、實驗提交方法等,每個方法都回傳 Promise 以支援非同步操作。
/**
* IBM Quantum Experience Node.js 客戶端
*
* 這個模組提供與 IBM Q Experience REST API 互動的功能
* 包含身份驗證、裝置查詢與量子實驗提交
*
* @module IBMQuantumExperience
* @author 玄貓(BlackCat)
*/
// 引入 HTTP 請求函式庫
// request 套件提供簡潔的 API 來發送各種 HTTP 請求
const request = require('request');
// 內部狀態變數
// 使用底線前綴表示這些是私有變數,不應該被外部直接存取
let _config = null; // 儲存配置資訊
let _accessToken = null; // 儲存存取令牌
let _userId = null; // 儲存使用者 ID
const _userAgent = 'qiskit-api-node'; // 客戶端識別字串
// 預設的 HTTP 請求標頭
// 所有 API 請求都會包含這個標頭來識別客戶端應用程式
const _defaultHeaders = {
'x-qx-client-application': _userAgent
};
/**
* 使用 API Token 進行身份驗證
*
* 這個函數向 IBM Q Experience API 發送登入請求
* 使用 API Token 換取臨時的存取令牌
*
* @returns {Promise} 解析為使用者資訊的 Promise
* @private
*/
function loginWithToken() {
// 準備 HTTP POST 請求的選項
const options = {
// 登入端點的完整 URL
url: _config.url + '/users/loginWithToken',
// 表單資料,包含 API Token
form: {
'apiToken': _config.APItoken
}
};
// 回傳一個 Promise 來處理非同步操作
return new Promise(function(resolve, reject) {
// 發送 POST 請求進行登入
request.post(options, function(err, response, body) {
// 檢查是否有錯誤發生
if (err) {
// 如果有錯誤,拒絕 Promise 並傳遞錯誤物件
reject(err);
} else {
// 解析回應的 JSON 字串
const json = JSON.parse(body);
// 儲存存取令牌與使用者 ID
// 這些資訊會在後續的 API 請求中使用
_accessToken = json.id;
_userId = json.userId;
// 如果啟用除錯模式,輸出日誌
if (_config.debug) {
console.log(`已取得使用者 ID: ${_userId}`);
console.log(`存取令牌: ${_accessToken}`);
}
// 解析 Promise 並回傳使用者資訊
resolve(json);
}
});
});
}
/**
* 取得可用的後端裝置列表
*
* 查詢 IBM Q Experience 平台上所有可用的量子處理器與模擬器
* 包含裝置名稱、狀態、量子位元數等資訊
*
* @returns {Promise} 解析為裝置列表陣列的 Promise
*/
function getBackends() {
// 準備 HTTP GET 請求的選項
const options = {
// Backends 端點的完整 URL,包含存取令牌
url: `${_config.url}/Backends?access_token=${_accessToken}`,
// 請求標頭
headers: _defaultHeaders
};
return new Promise(function(resolve, reject) {
// 發送 GET 請求查詢裝置列表
request.get(options, function(err, response, body) {
if (err) {
reject(err);
} else {
// 解析並回傳裝置列表
resolve(JSON.parse(body));
}
});
});
}
/**
* 取得特定裝置的校準資料
*
* 量子處理器需要定期校準以維持運作品質
* 這個方法回傳指定裝置的最新校準資料
* 包含閘操作的錯誤率、讀取錯誤等資訊
*
* @param {string} deviceName - 裝置名稱,例如 'ibmqx4'
* @returns {Promise} 解析為校準資料物件的 Promise
*/
function getCalibration(deviceName) {
const options = {
url: `${_config.url}/Backends/${deviceName}/calibration?access_token=${_accessToken}`,
headers: _defaultHeaders
};
return new Promise(function(resolve, reject) {
request.get(options, function(err, response, body) {
if (err) {
reject(err);
} else {
resolve(JSON.parse(body));
}
});
});
}
/**
* 取得特定裝置的參數資訊
*
* 回傳裝置的硬體規格與限制
* 例如量子位元數、連接拓撲、支援的閘操作等
*
* @param {string} deviceName - 裝置名稱
* @returns {Promise} 解析為參數物件的 Promise
*/
function getParameters(deviceName) {
const options = {
url: `${_config.url}/Backends/${deviceName}/parameters?access_token=${_accessToken}`,
headers: _defaultHeaders
};
return new Promise(function(resolve, reject) {
request.get(options, function(err, response, body) {
if (err) {
reject(err);
} else {
resolve(JSON.parse(body));
}
});
});
}
/**
* 提交量子實驗到指定裝置
*
* 這是客戶端的核心功能,用於提交 QASM 程式碼到量子處理器執行
*
* @param {string} name - 實驗名稱
* @param {string} qasm - QASM 量子電路程式碼
* @param {number} shots - 執行次數(重複測量以提高準確性)
* @param {string} device - 目標裝置名稱
* @returns {Promise} 解析為任務資訊的 Promise
*/
function runExperiment(name, qasm, shots, device) {
// 準備實驗的 JSON 負載
const payload = {
// QASM 程式碼陣列,可以包含多個電路
qasms: [{
qasm: qasm
}],
// 執行次數
shots: shots,
// 目標後端裝置
backend: {
name: device
},
// 最大允許使用的積分
maxCredits: 3
};
const options = {
url: `${_config.url}/Jobs?access_token=${_accessToken}`,
headers: _defaultHeaders,
// 將 payload 物件轉換為 JSON 字串
body: JSON.stringify(payload),
// 設定內容類型為 JSON
headers: {
..._defaultHeaders,
'Content-Type': 'application/json'
}
};
return new Promise(function(resolve, reject) {
// 使用 POST 方法提交實驗
request.post(options, function(err, response, body) {
if (err) {
reject(err);
} else {
const result = JSON.parse(body);
if (_config.debug) {
console.log(`實驗已提交: ${name}`);
console.log(`任務 ID: ${result.id}`);
}
resolve(result);
}
});
});
}
/**
* 模組匯出的公開 API
*
* 這些方法可以被外部程式碼呼叫
*/
module.exports = {
/**
* 初始化客戶端
*
* @param {Object} config - 配置物件
* @param {string} config.APItoken - IBM Q Experience API Token
* @param {string} config.url - API 基礎 URL
* @param {boolean} config.debug - 是否啟用除錯模式
* @returns {Promise} 解析為使用者資訊的 Promise
*/
init: function(config) {
_config = config;
return loginWithToken();
},
// 匯出其他 API 方法
getBackends: getBackends,
getCalibration: getCalibration,
getParameters: getParameters,
runExperiment: runExperiment
};
這個客戶端的設計遵循幾個重要原則。首先是一致的錯誤處理,所有方法都回傳 Promise,讓呼叫者能夠使用統一的方式處理成功與失敗的情況。其次是狀態封裝,存取令牌等敏感資訊儲存在模組的私有變數中,不會暴露給外部。最後是可擴展性,新增 API 方法時只需要實作對應的函數並加入到 module.exports 中。
使用 async/await 簡化非同步操作
Node.js 的非同步程式設計從早期的回呼函數演進到 Promise,再到現代的 async/await 語法。async/await 建構在 Promise 之上,提供了更接近同步程式碼的寫法,大幅提升了可讀性與維護性。在使用 IBM Q Experience API 客戶端時,async/await 讓複雜的操作流程變得清晰易懂。
/**
* IBM Quantum Experience 客戶端使用範例
*
* 展示如何使用 async/await 語法來操作量子運算 API
*/
// 引入我們建立的客戶端模組
const qx = require('./IBMQuantumExperience');
// 配置物件
// 包含 API Token 與其他必要的設定
const config = {
// 從 IBM Q Experience 網站取得的 API Token
// 這是一個敏感資訊,實務上應該從環境變數或配置檔讀取
APItoken: 'YOUR_API_TOKEN_HERE',
// IBM Q Experience API 的基礎 URL
url: 'https://quantumexperience.ng.bluemix.net/api',
// 啟用除錯模式,會輸出詳細的日誌資訊
debug: true,
// 以下參數為企業客戶使用,一般使用者可以省略
hub: 'MY_HUB',
group: 'MY_GROUP',
project: 'MY_PROJECT'
};
/**
* 測試取得後端裝置列表
*
* 這個函數展示如何查詢可用的量子處理器與模擬器
*/
async function testGetBackends() {
try {
// 初始化客戶端並進行身份驗證
// await 關鍵字會等待 Promise 解析完成
await qx.init(config);
console.log('身份驗證成功\n');
// 取得可用的後端裝置列表
const backends = await qx.getBackends();
// 顯示裝置資訊
console.log('=== 可用的量子裝置 ===\n');
backends.forEach(backend => {
console.log(`裝置名稱: ${backend.name}`);
console.log(`狀態: ${backend.status}`);
console.log(`量子位元數: ${backend.nQubits}`);
console.log(`是否模擬器: ${backend.simulator}`);
console.log('---');
});
} catch (error) {
// 統一的錯誤處理
console.error('操作失敗:', error.message);
}
}
/**
* 測試取得裝置的詳細資訊
*
* 展示如何查詢特定裝置的校準資料與參數
*/
async function testDeviceInfo() {
try {
await qx.init(config);
// 指定要查詢的裝置名稱
const deviceName = 'ibmqx4';
// 同時取得校準資料與參數
// Promise.all 可以平行執行多個非同步操作
const [calibration, parameters] = await Promise.all([
qx.getCalibration(deviceName),
qx.getParameters(deviceName)
]);
console.log(`=== ${deviceName} 裝置資訊 ===\n`);
console.log('校準資料:');
console.log(JSON.stringify(calibration, null, 2));
console.log('\n參數資訊:');
console.log(JSON.stringify(parameters, null, 2));
} catch (error) {
console.error('查詢失敗:', error.message);
}
}
/**
* 測試提交量子實驗
*
* 展示完整的實驗提交流程
* 包含 QASM 程式碼的準備與實驗參數的設定
*/
async function testRunExperiment() {
try {
await qx.init(config);
// 實驗名稱
const experimentName = 'Node.js 量子實驗 #1';
// QASM 量子電路程式碼
// 這段程式碼定義了一個簡單的量子電路
// 包含量子閘操作與測量指令
const qasmCode = `
// QASM 2.0 量子組合語言
// 引入標準量子閘函式庫
include "qelib1.inc";
// 宣告 5 個量子位元的暫存器
qreg q[5];
// 宣告 5 個古典位元的暫存器用於儲存測量結果
creg c[5];
// 對第 0 個量子位元應用一系列的 U 閘
// U 閘是通用的單量子位元旋轉閘
u2(-4*pi/3, 2*pi) q[0];
u2(-3*pi/2, 2*pi) q[0];
u3(-pi, 0, -pi) q[0];
u3(-pi, 0, -pi/2) q[0];
u2(pi, -pi/2) q[0];
u3(-pi, 0, -pi/2) q[0];
// 測量所有量子位元並將結果儲存到古典暫存器
measure q -> c;
`;
// 執行次數
// 量子測量具有機率性,需要多次執行來統計結果分布
const shots = 1024;
// 目標裝置
// 可以選擇真實的量子處理器或模擬器
const targetDevice = 'ibmqx4';
console.log(`準備提交實驗: ${experimentName}`);
console.log(`目標裝置: ${targetDevice}`);
console.log(`執行次數: ${shots}\n`);
// 提交實驗
const result = await qx.runExperiment(
experimentName,
qasmCode,
shots,
targetDevice
);
console.log('實驗提交成功!');
console.log(`任務 ID: ${result.id}`);
console.log(`狀態: ${result.status}`);
// 實務上可以使用任務 ID 來查詢執行結果
// 由於量子計算需要時間,通常需要輪詢或使用 WebSocket 來取得結果
} catch (error) {
console.error('實驗提交失敗:', error.message);
}
}
// 執行測試函數
// 可以根據需要選擇要執行的測試
testGetBackends();
// testDeviceInfo();
// testRunExperiment();
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 14
skinparam minClassWidth 100
|應用程式|
start
:呼叫 init(config);
|API 客戶端|
:執行 loginWithToken();
note right
發送 POST 請求到
/users/loginWithToken
end note
:等待回應;
if (認證成功?) then (是)
:儲存 Access Token;
|應用程式|
:呼叫 getBackends();
|API 客戶端|
:發送 GET 請求;
note right
攜帶 Access Token
end note
:解析回應;
:回傳裝置列表;
|應用程式|
:選擇目標裝置;
:準備 QASM 程式碼;
:呼叫 runExperiment();
|API 客戶端|
:建立請求負載;
note right
包含 QASM 程式碼
shots、backend 等
end note
:發送 POST 請求;
:解析回應;
:回傳任務資訊;
|應用程式|
:顯示任務 ID;
stop
else (否)
:拋出錯誤;
|應用程式|
:錯誤處理;
stop
endif
@enduml這些範例展示了 async/await 如何讓非同步程式碼的邏輯變得清晰。不需要巢狀的回呼函數或複雜的 Promise 鏈,程式碼的執行流程一目了然。錯誤處理也得到簡化,使用標準的 try/catch 區塊就能捕捉所有非同步操作中的錯誤。
從實務角度來看,這個 Node.js 客戶端提供了一個簡潔的介面來操作 IBM Q Experience 的量子運算資源。相較於 Python SDK 的完整功能,這個實作聚焦在核心的 API 操作,特別適合需要在 Web 應用程式或 Node.js 服務中整合量子運算能力的場景。Node.js 的事件驅動特性讓它能夠有效處理多個並行的量子實驗,而豐富的 npm 生態系統也提供了許多工具來擴展功能。
在開發過程中需要注意幾個要點。首先是 API Token 的安全管理,絕對不應該將 Token 硬編碼在程式碼中或提交到版本控制系統。建議使用環境變數或加密的配置檔來儲存敏感資訊。其次是錯誤處理的完善性,網路請求可能因為各種原因失敗,應用程式需要能夠妥善處理這些情況。最後是請求的速率控制,避免在短時間內發送過多請求導致被 API 伺服器限制。
展望未來,這個基礎的客戶端可以持續擴展功能。例如加入任務狀態輪詢機制,自動等待實驗完成並取得結果。也可以實作更高階的抽象,提供量子電路的物件導向建構介面,而不是直接撰寫 QASM 程式碼。對於企業應用,可以加入完整的日誌記錄與監控機制,追蹤 API 的使用情況與效能指標。隨著量子運算技術的成熟與 IBM Q Experience 平台的演進,這個客戶端將持續適應新的 API 功能與最佳實務。