當量子運算從實驗室走向雲端平台,開發者開始有機會透過 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 功能與最佳實務。