#[cfg(feature = "ibm")]
use async_trait::async_trait;
#[cfg(feature = "ibm")]
use chrono;
use quantrs2_circuit::prelude::Circuit;
use std::collections::HashMap;
#[cfg(feature = "ibm")]
use std::sync::Arc;
#[cfg(feature = "ibm")]
use std::time::Duration;
#[cfg(feature = "ibm")]
use crate::{
ibm::IBMQuantumClient, CircuitExecutor, CircuitResult, DeviceError, DeviceResult, QuantumDevice,
};
#[cfg(not(feature = "ibm"))]
use crate::{
ibm::IBMQuantumClient, CircuitExecutor, CircuitResult, DeviceError, DeviceResult, QuantumDevice,
};
#[cfg(feature = "ibm")]
pub struct IBMQuantumDevice {
client: Arc<IBMQuantumClient>,
backend: crate::ibm::IBMBackend,
config: IBMDeviceConfig,
}
#[cfg(not(feature = "ibm"))]
pub struct IBMQuantumDevice;
#[derive(Debug, Clone)]
pub struct IBMDeviceConfig {
pub default_shots: usize,
pub optimization_level: usize,
pub timeout_seconds: u64,
pub optimize_routing: bool,
pub max_parallel_jobs: usize,
}
#[cfg(feature = "ibm")]
impl Default for IBMDeviceConfig {
fn default() -> Self {
Self {
default_shots: 1024,
optimization_level: 1,
timeout_seconds: 300,
optimize_routing: true,
max_parallel_jobs: 5,
}
}
}
#[cfg(not(feature = "ibm"))]
impl Default for IBMDeviceConfig {
fn default() -> Self {
Self {
default_shots: 1024,
optimization_level: 1,
timeout_seconds: 300,
optimize_routing: true,
max_parallel_jobs: 5,
}
}
}
#[cfg(feature = "ibm")]
impl IBMQuantumDevice {
pub async fn new(
client: IBMQuantumClient,
backend_name: &str,
config: Option<IBMDeviceConfig>,
) -> DeviceResult<Self> {
let backend = client.get_backend(backend_name).await?;
let client = Arc::new(client);
Ok(Self {
client,
backend,
config: config.unwrap_or_default(),
})
}
fn create_circuit_config<const N: usize>(
&self,
circuit: &Circuit<N>,
shots: Option<usize>,
) -> DeviceResult<crate::ibm::IBMCircuitConfig> {
let qasm = self.circuit_to_qasm(circuit)?;
let shots = shots.unwrap_or(self.config.default_shots);
Ok(crate::ibm::IBMCircuitConfig {
name: format!("quantrs_circuit_{}", chrono::Utc::now().timestamp()),
qasm,
shots,
optimization_level: Some(self.config.optimization_level),
initial_layout: None, })
}
fn circuit_to_qasm<const N: usize>(&self, circuit: &Circuit<N>) -> DeviceResult<String> {
if N > self.backend.n_qubits {
return Err(DeviceError::CircuitConversion(format!(
"Circuit has {} qubits but backend {} only supports {} qubits",
N, self.backend.name, self.backend.n_qubits
)));
}
IBMQuantumClient::circuit_to_qasm(circuit, None)
}
}
#[cfg(not(feature = "ibm"))]
impl IBMQuantumDevice {
pub async fn new(
_client: IBMQuantumClient,
_backend_name: &str,
_config: Option<IBMDeviceConfig>,
) -> DeviceResult<Self> {
Err(DeviceError::UnsupportedDevice(
"IBM Quantum support not enabled. Recompile with the 'ibm' feature.".to_string(),
))
}
}
#[cfg(feature = "_async_device")]
#[async_trait]
impl QuantumDevice for IBMQuantumDevice {
async fn is_available(&self) -> DeviceResult<bool> {
let backend = self.client.get_backend(&self.backend.name).await?;
Ok(backend.status == "active")
}
async fn qubit_count(&self) -> DeviceResult<usize> {
Ok(self.backend.n_qubits)
}
async fn properties(&self) -> DeviceResult<HashMap<String, String>> {
let mut props = HashMap::new();
props.insert("name".to_string(), self.backend.name.clone());
props.insert("description".to_string(), self.backend.description.clone());
props.insert("version".to_string(), self.backend.version.clone());
props.insert("n_qubits".to_string(), self.backend.n_qubits.to_string());
props.insert("simulator".to_string(), self.backend.simulator.to_string());
Ok(props)
}
async fn is_simulator(&self) -> DeviceResult<bool> {
Ok(self.backend.simulator)
}
}
#[cfg(not(feature = "_async_device"))]
impl QuantumDevice for IBMQuantumDevice {
fn is_available(&self) -> DeviceResult<bool> {
Err(DeviceError::UnsupportedDevice(
"IBM Quantum support not enabled".to_string(),
))
}
fn qubit_count(&self) -> DeviceResult<usize> {
Err(DeviceError::UnsupportedDevice(
"IBM Quantum support not enabled".to_string(),
))
}
fn properties(&self) -> DeviceResult<HashMap<String, String>> {
Err(DeviceError::UnsupportedDevice(
"IBM Quantum support not enabled".to_string(),
))
}
fn is_simulator(&self) -> DeviceResult<bool> {
Err(DeviceError::UnsupportedDevice(
"IBM Quantum support not enabled".to_string(),
))
}
}
#[cfg(feature = "_async_device")]
#[async_trait]
impl CircuitExecutor for IBMQuantumDevice {
async fn execute_circuit<const N: usize>(
&self,
circuit: &Circuit<N>,
shots: usize,
) -> DeviceResult<CircuitResult> {
let config = self.create_circuit_config(circuit, Some(shots))?;
let job_id = self
.client
.submit_circuit(&self.backend.name, config)
.await?;
let result = self
.client
.wait_for_job(&job_id, Some(self.config.timeout_seconds))
.await?;
let mut metadata = HashMap::new();
metadata.insert("job_id".to_string(), job_id);
metadata.insert("backend".to_string(), self.backend.name.clone());
metadata.insert("shots".to_string(), shots.to_string());
Ok(CircuitResult {
counts: result.counts,
shots: result.shots,
metadata,
})
}
async fn execute_circuits<const N: usize>(
&self,
circuits: Vec<&Circuit<N>>,
shots: usize,
) -> DeviceResult<Vec<CircuitResult>> {
if circuits.is_empty() {
return Ok(Vec::new());
}
let chunk_size = self.config.max_parallel_jobs.max(1);
let mut results = Vec::new();
for chunk in circuits.chunks(chunk_size) {
let mut configs = Vec::new();
for circuit in chunk {
let config = self.create_circuit_config(circuit, Some(shots))?;
configs.push(config);
}
let job_ids = self
.client
.submit_circuits_parallel(&self.backend.name, configs)
.await?;
let mut chunk_results = Vec::new();
for job_id in job_ids {
let result = self
.client
.wait_for_job(&job_id, Some(self.config.timeout_seconds))
.await?;
let mut metadata = HashMap::new();
metadata.insert("job_id".to_string(), job_id);
metadata.insert("backend".to_string(), self.backend.name.clone());
metadata.insert("shots".to_string(), shots.to_string());
chunk_results.push(CircuitResult {
counts: result.counts,
shots: result.shots,
metadata,
});
}
results.extend(chunk_results);
}
Ok(results)
}
async fn can_execute_circuit<const N: usize>(
&self,
_circuit: &Circuit<N>,
) -> DeviceResult<bool> {
if N > self.backend.n_qubits {
return Ok(false);
}
Ok(true)
}
async fn estimated_queue_time<const N: usize>(
&self,
_circuit: &Circuit<N>,
) -> DeviceResult<Duration> {
if self.backend.simulator {
Ok(Duration::from_secs(10)) } else {
Ok(Duration::from_secs(3600)) }
}
}
#[cfg(not(feature = "_async_device"))]
impl CircuitExecutor for IBMQuantumDevice {
fn execute_circuit<const N: usize>(
&self,
_circuit: &Circuit<N>,
_shots: usize,
) -> DeviceResult<CircuitResult> {
Err(DeviceError::UnsupportedDevice(
"IBM Quantum support not enabled".to_string(),
))
}
fn execute_circuits<const N: usize>(
&self,
_circuits: Vec<&Circuit<N>>,
_shots: usize,
) -> DeviceResult<Vec<CircuitResult>> {
Err(DeviceError::UnsupportedDevice(
"IBM Quantum support not enabled".to_string(),
))
}
fn can_execute_circuit<const N: usize>(&self, _circuit: &Circuit<N>) -> DeviceResult<bool> {
Err(DeviceError::UnsupportedDevice(
"IBM Quantum support not enabled".to_string(),
))
}
fn estimated_queue_time<const N: usize>(
&self,
_circuit: &Circuit<N>,
) -> DeviceResult<std::time::Duration> {
Err(DeviceError::UnsupportedDevice(
"IBM Quantum support not enabled".to_string(),
))
}
}
#[cfg(all(test, feature = "ibm"))]
mod tests {
use super::*;
fn test_device() -> IBMQuantumDevice {
let client = IBMQuantumClient::new("test_token").expect("build test client");
let backend = crate::ibm::IBMBackend {
id: "ibmq_test".to_string(),
name: "ibmq_test".to_string(),
simulator: true,
n_qubits: 5,
status: "active".to_string(),
description: "Test backend".to_string(),
version: "1.0".to_string(),
};
IBMQuantumDevice {
client: Arc::new(client),
backend,
config: IBMDeviceConfig::default(),
}
}
#[test]
fn test_circuit_to_qasm_emits_real_gates_on_device() {
let mut circuit = Circuit::<2>::new();
circuit.h(0).expect("add H");
circuit.cnot(0, 1).expect("add CNOT");
let device = test_device();
let qasm = device
.circuit_to_qasm(&circuit)
.expect("QASM conversion should succeed");
assert!(qasm.contains("OPENQASM 2.0"), "missing header: {qasm}");
assert!(qasm.contains("qreg q[2]"), "missing register: {qasm}");
assert!(qasm.contains("h q[0]"), "missing H gate: {qasm}");
assert!(qasm.contains("cx q[0], q[1]"), "missing CNOT gate: {qasm}");
}
#[test]
fn test_circuit_to_qasm_rejects_oversized_circuit() {
let mut circuit = Circuit::<8>::new();
circuit.h(0).expect("add H");
let device = test_device(); let result = device.circuit_to_qasm(&circuit);
assert!(result.is_err(), "expected oversized circuit to be rejected");
}
}