#[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
)));
}
let mut qasm = String::from("OPENQASM 2.0;\ninclude \"qelib1.inc\";\n\n");
use std::fmt::Write;
let _ = writeln!(qasm, "qreg q[{N}];");
let _ = writeln!(qasm, "creg c[{N}];");
Ok(qasm)
}
}
#[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 = "ibm")]
#[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 = "ibm"))]
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 = "ibm")]
#[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 = "ibm"))]
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(),
))
}
}