use std::collections::HashMap;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use reqwest::Client;
use tokio::time::sleep;
use async_trait::async_trait;
use quantrs2_circuit::prelude::Circuit;
use quantrs2_core::gate::GateOp;
use crate::{DeviceError, DeviceResult, CircuitResult, QuantumDevice, CircuitExecutor};
use crate::translation::HardwareBackend;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HoneywellConfig {
pub api_url: String,
pub api_key: String,
pub user_id: String,
pub default_machine: Option<String>,
pub timeout: u64,
pub max_retries: u32,
pub use_native_gates: bool,
}
impl Default for HoneywellConfig {
fn default() -> Self {
Self {
api_url: "https://qapi.quantinuum.com".to_string(),
api_key: String::new(),
user_id: String::new(),
default_machine: None,
timeout: 600, max_retries: 3,
use_native_gates: true,
}
}
}
pub struct HoneywellClient {
config: HoneywellConfig,
client: Client,
}
impl HoneywellClient {
pub fn new(config: HoneywellConfig) -> DeviceResult<Self> {
let client = Client::builder()
.timeout(Duration::from_secs(config.timeout))
.build()
.map_err(|e| DeviceError::Connection(format!("Failed to create HTTP client: {}", e)))?;
Ok(Self { config, client })
}
pub async fn get_available_machines(&self) -> DeviceResult<Vec<HoneywellMachine>> {
let url = format!("{}/v1/machines", self.config.api_url);
let response = self.client
.get(&url)
.header("Authorization", format!("Bearer {}", self.config.api_key))
.send()
.await
.map_err(|e| DeviceError::Connection(format!("Failed to get machines: {}", e)))?;
if !response.status().is_success() {
return Err(DeviceError::Connection(
format!("Machine listing failed with status: {}", response.status())
));
}
let machines: Vec<HoneywellMachine> = response
.json()
.await
.map_err(|e| DeviceError::Parsing(format!("Failed to parse machine response: {}", e)))?;
Ok(machines)
}
pub async fn submit_job(&self, job: &HoneywellJob) -> DeviceResult<String> {
let url = format!("{}/v1/jobs", self.config.api_url);
let response = self.client
.post(&url)
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("Content-Type", "application/json")
.json(job)
.send()
.await
.map_err(|e| DeviceError::Connection(format!("Failed to submit job: {}", e)))?;
if !response.status().is_success() {
return Err(DeviceError::Execution(
format!("Job submission failed with status: {}", response.status())
));
}
let job_response: HoneywellJobResponse = response
.json()
.await
.map_err(|e| DeviceError::Parsing(format!("Failed to parse job response: {}", e)))?;
Ok(job_response.job_id)
}
pub async fn get_job_results(&self, job_id: &str) -> DeviceResult<HoneywellJobResults> {
let url = format!("{}/v1/jobs/{}/results", self.config.api_url, job_id);
let response = self.client
.get(&url)
.header("Authorization", format!("Bearer {}", self.config.api_key))
.send()
.await
.map_err(|e| DeviceError::Connection(format!("Failed to get job results: {}", e)))?;
if !response.status().is_success() {
return Err(DeviceError::Execution(
format!("Failed to get results with status: {}", response.status())
));
}
let results: HoneywellJobResults = response
.json()
.await
.map_err(|e| DeviceError::Parsing(format!("Failed to parse job results: {}", e)))?;
Ok(results)
}
pub fn circuit_to_qasm<const N: usize>(&self, circuit: &Circuit<N>) -> DeviceResult<String> {
let mut qasm_lines = Vec::new();
qasm_lines.push("OPENQASM 2.0;".to_string());
qasm_lines.push("include \"qelib1.inc\";".to_string());
qasm_lines.push(format!("qreg q[{}];", N));
qasm_lines.push(format!("creg c[{}];", N));
for gate_info in circuit.iter_gates() {
let qasm_instruction = self.gate_to_qasm(&gate_info.gate, &gate_info.qubits)?;
qasm_lines.push(qasm_instruction);
}
for i in 0..N {
qasm_lines.push(format!("measure q[{}] -> c[{}];", i, i));
}
Ok(qasm_lines.join("\n"))
}
fn gate_to_qasm(&self, gate: &dyn GateOp, qubits: &[usize]) -> DeviceResult<String> {
let gate_name = gate.name();
match gate_name.as_str() {
"I" => Ok(format!("id q[{}];", qubits[0])),
"X" => Ok(format!("x q[{}];", qubits[0])),
"Y" => Ok(format!("y q[{}];", qubits[0])),
"Z" => Ok(format!("z q[{}];", qubits[0])),
"H" => Ok(format!("h q[{}];", qubits[0])),
"S" => Ok(format!("s q[{}];", qubits[0])),
"T" => Ok(format!("t q[{}];", qubits[0])),
"RX" => {
if let Some(params) = gate.parameters() {
Ok(format!("rx({}) q[{}];", params[0], qubits[0]))
} else {
Err(DeviceError::CircuitConversion("RX gate missing angle parameter".to_string()))
}
},
"RY" => {
if let Some(params) = gate.parameters() {
Ok(format!("ry({}) q[{}];", params[0], qubits[0]))
} else {
Err(DeviceError::CircuitConversion("RY gate missing angle parameter".to_string()))
}
},
"RZ" => {
if let Some(params) = gate.parameters() {
Ok(format!("rz({}) q[{}];", params[0], qubits[0]))
} else {
Err(DeviceError::CircuitConversion("RZ gate missing angle parameter".to_string()))
}
},
"CNOT" | "CX" => Ok(format!("cx q[{}],q[{}];", qubits[0], qubits[1])),
"CZ" => Ok(format!("cz q[{}],q[{}];", qubits[0], qubits[1])),
"SWAP" => {
Ok(format!("cx q[{}],q[{}];\ncx q[{}],q[{}];\ncx q[{}],q[{}];",
qubits[0], qubits[1], qubits[1], qubits[0], qubits[0], qubits[1]))
},
"CCX" | "CCNOT" => Ok(format!("ccx q[{}],q[{}],q[{}];", qubits[0], qubits[1], qubits[2])),
_ => {
if self.config.use_native_gates {
match gate_name.as_str() {
"RZZ" => {
if let Some(params) = gate.parameters() {
Ok(format!("rzz({}) q[{}],q[{}];", params[0], qubits[0], qubits[1]))
} else {
Err(DeviceError::CircuitConversion("RZZ gate missing angle parameter".to_string()))
}
},
"RXX" => {
if let Some(params) = gate.parameters() {
Ok(format!("rxx({}) q[{}],q[{}];", params[0], qubits[0], qubits[1]))
} else {
Err(DeviceError::CircuitConversion("RXX gate missing angle parameter".to_string()))
}
},
"MS" => {
if let Some(params) = gate.parameters() {
Ok(format!("ms({}) q[{}],q[{}];", params[0], qubits[0], qubits[1]))
} else {
Err(DeviceError::CircuitConversion("MS gate missing angle parameter".to_string()))
}
},
_ => Err(DeviceError::UnsupportedGate(format!("Gate not supported by Honeywell backend: {}", gate_name))),
}
} else {
Err(DeviceError::UnsupportedGate(format!("Gate not supported by Honeywell backend: {}", gate_name)))
}
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HoneywellMachine {
pub name: String,
pub id: String,
pub status: String,
pub num_qubits: usize,
pub generation: String, pub connectivity: String, pub gate_times: HashMap<String, f64>,
pub fidelities: HashMap<String, f64>,
pub queue_length: Option<usize>,
pub avg_queue_time: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HoneywellJob {
pub qasm: String,
pub machine: String,
pub shots: u32,
pub name: Option<String>,
pub priority: Option<String>,
pub tags: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HoneywellJobResponse {
pub job_id: String,
pub status: String,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HoneywellJobResults {
pub job_id: String,
pub counts: HashMap<String, u32>,
pub execution_time: f64,
pub queue_time: f64,
pub fidelity_metrics: Option<HashMap<String, f64>>,
pub raw_data: Option<serde_json::Value>,
}
pub struct HoneywellDevice {
client: HoneywellClient,
machine_info: HoneywellMachine,
}
impl HoneywellDevice {
pub async fn new(config: HoneywellConfig) -> DeviceResult<Self> {
let client = HoneywellClient::new(config.clone())?;
let machine_name = config.default_machine.as_ref()
.ok_or_else(|| DeviceError::Configuration("No default machine specified".to_string()))?;
let machines = client.get_available_machines().await?;
let machine_info = machines.into_iter()
.find(|machine| &machine.name == machine_name)
.ok_or_else(|| DeviceError::Configuration(format!("Machine '{}' not found", machine_name)))?;
Ok(Self { client, machine_info })
}
}
#[async_trait]
impl QuantumDevice for HoneywellDevice {
async fn is_available(&self) -> DeviceResult<bool> {
Ok(self.machine_info.status == "online")
}
async fn qubit_count(&self) -> DeviceResult<usize> {
Ok(self.machine_info.num_qubits)
}
async fn properties(&self) -> DeviceResult<HashMap<String, String>> {
let mut props = HashMap::new();
props.insert("backend".to_string(), "honeywell".to_string());
props.insert("machine_name".to_string(), self.machine_info.name.clone());
props.insert("num_qubits".to_string(), self.machine_info.num_qubits.to_string());
props.insert("generation".to_string(), self.machine_info.generation.clone());
props.insert("connectivity".to_string(), self.machine_info.connectivity.clone());
props.insert("status".to_string(), self.machine_info.status.clone());
if let Some(queue_length) = self.machine_info.queue_length {
props.insert("queue_length".to_string(), queue_length.to_string());
}
Ok(props)
}
fn is_simulator(&self) -> bool {
false
}
async fn estimated_queue_time<const N: usize>(&self, _circuit: &Circuit<N>) -> DeviceResult<Duration> {
if let Some(avg_time) = self.machine_info.avg_queue_time {
Ok(Duration::from_secs(avg_time as u64))
} else {
Ok(Duration::from_secs(120)) }
}
async fn can_execute_circuit<const N: usize>(&self, circuit: &Circuit<N>) -> DeviceResult<bool> {
if N > self.machine_info.num_qubits {
return Ok(false);
}
for gate_info in circuit.iter_gates() {
let gate_name = gate_info.gate.name();
match gate_name.as_str() {
"I" | "X" | "Y" | "Z" | "H" | "S" | "T" |
"RX" | "RY" | "RZ" | "CNOT" | "CX" | "CZ" | "SWAP" | "CCX" | "CCNOT" => {},
"RZZ" | "RXX" | "MS" if self.client.config.use_native_gates => {},
_ => return Ok(false),
}
}
Ok(true)
}
}
#[async_trait]
impl CircuitExecutor for HoneywellDevice {
async fn execute_circuit<const N: usize>(
&self,
circuit: &Circuit<N>,
shots: usize,
) -> DeviceResult<CircuitResult> {
let qasm_program = self.client.circuit_to_qasm(circuit)?;
let job = HoneywellJob {
qasm: qasm_program,
machine: self.machine_info.name.clone(),
shots: shots as u32,
name: Some(format!("quantrs_job_{}", chrono::Utc::now().timestamp())),
priority: None,
tags: Some(vec!["quantrs".to_string()]),
};
let job_id = self.client.submit_job(&job).await?;
tokio::time::sleep(Duration::from_secs(10)).await;
let results = self.client.get_job_results(&job_id).await?;
let mut counts = HashMap::new();
for (state, count) in &results.counts {
counts.insert(state.clone(), *count as usize);
}
Ok(CircuitResult { counts, shots })
}
async fn execute_circuits<const N: usize>(
&self,
circuits: Vec<&Circuit<N>>,
shots: usize,
) -> DeviceResult<Vec<CircuitResult>> {
let mut results = Vec::new();
for circuit in circuits {
let result = self.execute_circuit(circuit, shots).await?;
results.push(result);
}
Ok(results)
}
}
pub fn create_honeywell_client(config: HoneywellConfig) -> DeviceResult<HoneywellClient> {
HoneywellClient::new(config)
}
pub async fn create_honeywell_device(config: HoneywellConfig) -> DeviceResult<HoneywellDevice> {
HoneywellDevice::new(config).await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_honeywell_client_creation() {
let config = HoneywellConfig {
api_key: "test_key".to_string(),
user_id: "test_user".to_string(),
..HoneywellConfig::default()
};
let client = HoneywellClient::new(config);
assert!(client.is_ok());
}
#[test]
fn test_circuit_to_qasm_conversion() {
let config = HoneywellConfig::default();
let client = HoneywellClient::new(config).expect("Failed to create Honeywell client");
let circuit = Circuit::<2>::new();
let qasm = client.circuit_to_qasm(&circuit).expect("Failed to convert circuit to QASM");
assert!(qasm.contains("OPENQASM 2.0"));
assert!(qasm.contains("qreg q[2]"));
assert!(qasm.contains("creg c[2]"));
assert!(qasm.contains("measure q[0] -> c[0]"));
assert!(qasm.contains("measure q[1] -> c[1]"));
}
}