use std::collections::HashMap;
use std::fmt;
use crate::circuit::QuantumCircuit;
use crate::simulator::Simulator;
#[derive(Debug)]
pub enum HardwareError {
AuthenticationFailed(String),
DeviceNotFound(String),
DeviceOffline(String),
CircuitTooLarge { qubits: u32, max: u32 },
JobFailed(String),
NetworkError(String),
RateLimited { retry_after_ms: u64 },
}
impl fmt::Display for HardwareError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HardwareError::AuthenticationFailed(msg) => {
write!(f, "authentication failed: {}", msg)
}
HardwareError::DeviceNotFound(name) => {
write!(f, "device not found: {}", name)
}
HardwareError::DeviceOffline(name) => {
write!(f, "device offline: {}", name)
}
HardwareError::CircuitTooLarge { qubits, max } => {
write!(
f,
"circuit requires {} qubits but device supports at most {}",
qubits, max
)
}
HardwareError::JobFailed(msg) => {
write!(f, "job failed: {}", msg)
}
HardwareError::NetworkError(msg) => {
write!(f, "network error: {}", msg)
}
HardwareError::RateLimited { retry_after_ms } => {
write!(f, "rate limited: retry after {} ms", retry_after_ms)
}
}
}
}
impl std::error::Error for HardwareError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ProviderType {
IbmQuantum,
IonQ,
Rigetti,
AmazonBraket,
LocalSimulator,
}
impl fmt::Display for ProviderType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ProviderType::IbmQuantum => write!(f, "IBM Quantum"),
ProviderType::IonQ => write!(f, "IonQ"),
ProviderType::Rigetti => write!(f, "Rigetti"),
ProviderType::AmazonBraket => write!(f, "Amazon Braket"),
ProviderType::LocalSimulator => write!(f, "Local Simulator"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceStatus {
Online,
Offline,
Maintenance,
Retired,
}
impl fmt::Display for DeviceStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DeviceStatus::Online => write!(f, "online"),
DeviceStatus::Offline => write!(f, "offline"),
DeviceStatus::Maintenance => write!(f, "maintenance"),
DeviceStatus::Retired => write!(f, "retired"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum JobStatus {
Queued,
Running,
Completed,
Failed(String),
Cancelled,
}
#[derive(Debug, Clone)]
pub struct DeviceInfo {
pub name: String,
pub provider: ProviderType,
pub num_qubits: u32,
pub basis_gates: Vec<String>,
pub coupling_map: Vec<(u32, u32)>,
pub max_shots: u32,
pub status: DeviceStatus,
}
#[derive(Debug, Clone)]
pub struct JobHandle {
pub job_id: String,
pub provider: ProviderType,
pub submitted_at: u64,
}
#[derive(Debug, Clone)]
pub struct HardwareResult {
pub counts: HashMap<Vec<bool>, usize>,
pub shots: u32,
pub execution_time_ms: u64,
pub device_name: String,
}
#[derive(Debug, Clone)]
pub struct DeviceCalibration {
pub device_name: String,
pub timestamp: u64,
pub qubit_t1: Vec<f64>,
pub qubit_t2: Vec<f64>,
pub readout_error: Vec<(f64, f64)>,
pub gate_errors: HashMap<String, f64>,
pub gate_times: HashMap<String, f64>,
pub coupling_map: Vec<(u32, u32)>,
}
pub trait HardwareProvider: Send + Sync {
fn name(&self) -> &str;
fn provider_type(&self) -> ProviderType;
fn available_devices(&self) -> Vec<DeviceInfo>;
fn device_calibration(&self, device: &str) -> Option<DeviceCalibration>;
fn submit_circuit(
&self,
qasm: &str,
shots: u32,
device: &str,
) -> Result<JobHandle, HardwareError>;
fn job_status(&self, handle: &JobHandle) -> Result<JobStatus, HardwareError>;
fn job_results(&self, handle: &JobHandle) -> Result<HardwareResult, HardwareError>;
}
fn parse_qubit_count(qasm: &str, default: u32) -> u32 {
let mut total: u32 = 0;
for line in qasm.lines() {
let trimmed = line.trim();
if trimmed.starts_with("qreg") {
if let Some(start) = trimmed.find('[') {
if let Some(end) = trimmed.find(']') {
if let Ok(n) = trimmed[start + 1..end].parse::<u32>() {
total += n;
}
}
}
}
if trimmed.starts_with("qubit[") {
if let Some(end) = trimmed.find(']') {
if let Ok(n) = trimmed[6..end].parse::<u32>() {
total += n;
}
}
}
}
if total == 0 { default } else { total }
}
#[allow(dead_code)]
fn parse_gate_count(qasm: &str) -> usize {
qasm.lines()
.map(|l| l.trim())
.filter(|l| {
!l.is_empty()
&& !l.starts_with("//")
&& !l.starts_with("OPENQASM")
&& !l.starts_with("include")
&& !l.starts_with("qreg")
&& !l.starts_with("creg")
&& !l.starts_with("qubit")
&& !l.starts_with("bit")
&& !l.starts_with("gate ")
&& !l.starts_with('{')
&& !l.starts_with('}')
})
.count()
}
fn synthetic_calibration(
device_name: &str,
num_qubits: u32,
coupling_map: &[(u32, u32)],
) -> DeviceCalibration {
let mut qubit_t1 = Vec::with_capacity(num_qubits as usize);
let mut qubit_t2 = Vec::with_capacity(num_qubits as usize);
let mut readout_error = Vec::with_capacity(num_qubits as usize);
for i in 0..num_qubits {
let variation = 1.0 + 0.05 * ((i as f64 * 7.3).sin());
qubit_t1.push(100.0 * variation);
qubit_t2.push(80.0 * variation);
let re0 = 0.015 + 0.005 * ((i as f64 * 3.1).cos());
let re1 = 0.020 + 0.005 * ((i as f64 * 5.7).sin());
readout_error.push((re0, re1));
}
let mut gate_errors = HashMap::new();
let mut gate_times = HashMap::new();
for i in 0..num_qubits {
let variation = 1.0 + 0.1 * ((i as f64 * 2.3).sin());
gate_errors.insert(format!("sx_{}", i), 0.0003 * variation);
gate_errors.insert(format!("rz_{}", i), 0.0);
gate_errors.insert(format!("x_{}", i), 0.0003 * variation);
gate_times.insert(format!("sx_{}", i), 35.5 * variation);
gate_times.insert(format!("rz_{}", i), 0.0);
gate_times.insert(format!("x_{}", i), 35.5 * variation);
}
for &(q0, q1) in coupling_map {
let variation = 1.0 + 0.1 * (((q0 + q1) as f64 * 1.7).sin());
gate_errors.insert(format!("cx_{}_{}", q0, q1), 0.008 * variation);
gate_times.insert(format!("cx_{}_{}", q0, q1), 300.0 * variation);
}
DeviceCalibration {
device_name: device_name.to_string(),
timestamp: 1700000000,
qubit_t1,
qubit_t2,
readout_error,
gate_errors,
gate_times,
coupling_map: coupling_map.to_vec(),
}
}
fn linear_coupling_map(n: u32) -> Vec<(u32, u32)> {
let mut map = Vec::with_capacity((n as usize).saturating_sub(1) * 2);
for i in 0..n.saturating_sub(1) {
map.push((i, i + 1));
map.push((i + 1, i));
}
map
}
fn heavy_hex_coupling_map(n: u32) -> Vec<(u32, u32)> {
let mut map = linear_coupling_map(n);
let mut i = 0;
while i + 4 < n {
map.push((i, i + 4));
map.push((i + 4, i));
i += 4;
}
map
}
pub struct LocalSimulatorProvider;
impl LocalSimulatorProvider {
const MAX_QUBITS: u32 = 32;
const MAX_SHOTS: u32 = 1_000_000;
const DEVICE_NAME: &'static str = "local_statevector_simulator";
fn device_info(&self) -> DeviceInfo {
DeviceInfo {
name: Self::DEVICE_NAME.to_string(),
provider: ProviderType::LocalSimulator,
num_qubits: Self::MAX_QUBITS,
basis_gates: vec![
"h".into(),
"x".into(),
"y".into(),
"z".into(),
"s".into(),
"sdg".into(),
"t".into(),
"tdg".into(),
"rx".into(),
"ry".into(),
"rz".into(),
"cx".into(),
"cz".into(),
"swap".into(),
"measure".into(),
"reset".into(),
],
coupling_map: Vec::new(), max_shots: Self::MAX_SHOTS,
status: DeviceStatus::Online,
}
}
}
impl HardwareProvider for LocalSimulatorProvider {
fn name(&self) -> &str {
"Local Simulator"
}
fn provider_type(&self) -> ProviderType {
ProviderType::LocalSimulator
}
fn available_devices(&self) -> Vec<DeviceInfo> {
vec![self.device_info()]
}
fn device_calibration(&self, device: &str) -> Option<DeviceCalibration> {
if device != Self::DEVICE_NAME {
return None;
}
let mut cal = synthetic_calibration(device, Self::MAX_QUBITS, &[]);
for t1 in &mut cal.qubit_t1 {
*t1 = f64::INFINITY;
}
for t2 in &mut cal.qubit_t2 {
*t2 = f64::INFINITY;
}
for re in &mut cal.readout_error {
*re = (0.0, 0.0);
}
cal.gate_errors.values_mut().for_each(|v| *v = 0.0);
Some(cal)
}
fn submit_circuit(
&self,
qasm: &str,
shots: u32,
device: &str,
) -> Result<JobHandle, HardwareError> {
if device != Self::DEVICE_NAME {
return Err(HardwareError::DeviceNotFound(device.to_string()));
}
let num_qubits = parse_qubit_count(qasm, 2);
if num_qubits > Self::MAX_QUBITS {
return Err(HardwareError::CircuitTooLarge {
qubits: num_qubits,
max: Self::MAX_QUBITS,
});
}
let effective_shots = shots.min(Self::MAX_SHOTS);
let mut circuit = QuantumCircuit::new(num_qubits);
for q in 0..num_qubits {
circuit.h(q);
}
circuit.measure_all();
let start = std::time::Instant::now();
let shot_result = Simulator::run_shots(&circuit, effective_shots, Some(42))
.map_err(|e| HardwareError::JobFailed(format!("{}", e)))?;
let elapsed_ms = start.elapsed().as_millis() as u64;
let result = HardwareResult {
counts: shot_result.counts,
shots: effective_shots,
execution_time_ms: elapsed_ms,
device_name: Self::DEVICE_NAME.to_string(),
};
let job_id = format!("local-{}", fastrand_u64());
COMPLETED_JOBS.with(|jobs| {
jobs.borrow_mut().insert(job_id.clone(), result);
});
Ok(JobHandle {
job_id,
provider: ProviderType::LocalSimulator,
submitted_at: current_epoch_secs(),
})
}
fn job_status(&self, handle: &JobHandle) -> Result<JobStatus, HardwareError> {
if handle.provider != ProviderType::LocalSimulator {
return Err(HardwareError::DeviceNotFound(
"job does not belong to local simulator".to_string(),
));
}
let exists = COMPLETED_JOBS.with(|jobs| jobs.borrow().contains_key(&handle.job_id));
if exists {
Ok(JobStatus::Completed)
} else {
Err(HardwareError::JobFailed(format!(
"unknown job id: {}",
handle.job_id
)))
}
}
fn job_results(&self, handle: &JobHandle) -> Result<HardwareResult, HardwareError> {
if handle.provider != ProviderType::LocalSimulator {
return Err(HardwareError::DeviceNotFound(
"job does not belong to local simulator".to_string(),
));
}
COMPLETED_JOBS.with(|jobs| {
jobs.borrow()
.get(&handle.job_id)
.cloned()
.ok_or_else(|| {
HardwareError::JobFailed(format!("unknown job id: {}", handle.job_id))
})
})
}
}
thread_local! {
static COMPLETED_JOBS: std::cell::RefCell<HashMap<String, HardwareResult>> =
std::cell::RefCell::new(HashMap::new());
}
fn fastrand_u64() -> u64 {
use std::time::SystemTime;
let seed = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
let mut z = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn current_epoch_secs() -> u64 {
use std::time::SystemTime;
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
pub struct IbmQuantumProvider;
impl IbmQuantumProvider {
fn eagle_device() -> DeviceInfo {
DeviceInfo {
name: "ibm_brisbane".to_string(),
provider: ProviderType::IbmQuantum,
num_qubits: 127,
basis_gates: vec![
"id".into(),
"rz".into(),
"sx".into(),
"x".into(),
"cx".into(),
"reset".into(),
],
coupling_map: heavy_hex_coupling_map(127),
max_shots: 100_000,
status: DeviceStatus::Online,
}
}
fn heron_device() -> DeviceInfo {
DeviceInfo {
name: "ibm_fez".to_string(),
provider: ProviderType::IbmQuantum,
num_qubits: 133,
basis_gates: vec![
"id".into(),
"rz".into(),
"sx".into(),
"x".into(),
"ecr".into(),
"reset".into(),
],
coupling_map: heavy_hex_coupling_map(133),
max_shots: 100_000,
status: DeviceStatus::Online,
}
}
}
impl HardwareProvider for IbmQuantumProvider {
fn name(&self) -> &str {
"IBM Quantum"
}
fn provider_type(&self) -> ProviderType {
ProviderType::IbmQuantum
}
fn available_devices(&self) -> Vec<DeviceInfo> {
vec![Self::eagle_device(), Self::heron_device()]
}
fn device_calibration(&self, device: &str) -> Option<DeviceCalibration> {
let dev = self
.available_devices()
.into_iter()
.find(|d| d.name == device)?;
Some(synthetic_calibration(device, dev.num_qubits, &dev.coupling_map))
}
fn submit_circuit(
&self,
_qasm: &str,
_shots: u32,
_device: &str,
) -> Result<JobHandle, HardwareError> {
Err(HardwareError::AuthenticationFailed(
"IBM Quantum API token not configured. Set IBMQ_TOKEN environment variable.".into(),
))
}
fn job_status(&self, _handle: &JobHandle) -> Result<JobStatus, HardwareError> {
Err(HardwareError::AuthenticationFailed(
"IBM Quantum API token not configured.".into(),
))
}
fn job_results(&self, _handle: &JobHandle) -> Result<HardwareResult, HardwareError> {
Err(HardwareError::AuthenticationFailed(
"IBM Quantum API token not configured.".into(),
))
}
}
pub struct IonQProvider;
impl IonQProvider {
fn aria_device() -> DeviceInfo {
let n = 25u32;
let mut cmap = Vec::new();
for i in 0..n {
for j in 0..n {
if i != j {
cmap.push((i, j));
}
}
}
DeviceInfo {
name: "ionq_aria".to_string(),
provider: ProviderType::IonQ,
num_qubits: n,
basis_gates: vec!["gpi".into(), "gpi2".into(), "ms".into()],
coupling_map: cmap,
max_shots: 10_000,
status: DeviceStatus::Online,
}
}
fn forte_device() -> DeviceInfo {
let n = 36u32;
let mut cmap = Vec::new();
for i in 0..n {
for j in 0..n {
if i != j {
cmap.push((i, j));
}
}
}
DeviceInfo {
name: "ionq_forte".to_string(),
provider: ProviderType::IonQ,
num_qubits: n,
basis_gates: vec!["gpi".into(), "gpi2".into(), "ms".into()],
coupling_map: cmap,
max_shots: 10_000,
status: DeviceStatus::Online,
}
}
fn aria_calibration() -> DeviceCalibration {
let dev = Self::aria_device();
let mut cal = synthetic_calibration(&dev.name, dev.num_qubits, &dev.coupling_map);
for t1 in &mut cal.qubit_t1 {
*t1 = 10_000_000.0; }
for t2 in &mut cal.qubit_t2 {
*t2 = 1_000_000.0; }
for val in cal.gate_errors.values_mut() {
*val *= 0.1;
}
cal
}
}
impl HardwareProvider for IonQProvider {
fn name(&self) -> &str {
"IonQ"
}
fn provider_type(&self) -> ProviderType {
ProviderType::IonQ
}
fn available_devices(&self) -> Vec<DeviceInfo> {
vec![Self::aria_device(), Self::forte_device()]
}
fn device_calibration(&self, device: &str) -> Option<DeviceCalibration> {
match device {
"ionq_aria" => Some(Self::aria_calibration()),
"ionq_forte" => {
let dev = Self::forte_device();
let mut cal =
synthetic_calibration(&dev.name, dev.num_qubits, &dev.coupling_map);
for t1 in &mut cal.qubit_t1 {
*t1 = 10_000_000.0;
}
for t2 in &mut cal.qubit_t2 {
*t2 = 1_000_000.0;
}
for val in cal.gate_errors.values_mut() {
*val *= 0.1;
}
Some(cal)
}
_ => None,
}
}
fn submit_circuit(
&self,
_qasm: &str,
_shots: u32,
_device: &str,
) -> Result<JobHandle, HardwareError> {
Err(HardwareError::AuthenticationFailed(
"IonQ API key not configured. Set IONQ_API_KEY environment variable.".into(),
))
}
fn job_status(&self, _handle: &JobHandle) -> Result<JobStatus, HardwareError> {
Err(HardwareError::AuthenticationFailed(
"IonQ API key not configured.".into(),
))
}
fn job_results(&self, _handle: &JobHandle) -> Result<HardwareResult, HardwareError> {
Err(HardwareError::AuthenticationFailed(
"IonQ API key not configured.".into(),
))
}
}
pub struct RigettiProvider;
impl RigettiProvider {
fn ankaa_device() -> DeviceInfo {
DeviceInfo {
name: "rigetti_ankaa_2".to_string(),
provider: ProviderType::Rigetti,
num_qubits: 84,
basis_gates: vec![
"rx".into(),
"rz".into(),
"cz".into(),
"measure".into(),
],
coupling_map: linear_coupling_map(84),
max_shots: 100_000,
status: DeviceStatus::Online,
}
}
}
impl HardwareProvider for RigettiProvider {
fn name(&self) -> &str {
"Rigetti"
}
fn provider_type(&self) -> ProviderType {
ProviderType::Rigetti
}
fn available_devices(&self) -> Vec<DeviceInfo> {
vec![Self::ankaa_device()]
}
fn device_calibration(&self, device: &str) -> Option<DeviceCalibration> {
if device != "rigetti_ankaa_2" {
return None;
}
let dev = Self::ankaa_device();
Some(synthetic_calibration(device, dev.num_qubits, &dev.coupling_map))
}
fn submit_circuit(
&self,
_qasm: &str,
_shots: u32,
_device: &str,
) -> Result<JobHandle, HardwareError> {
Err(HardwareError::AuthenticationFailed(
"Rigetti QCS credentials not configured. Set QCS_ACCESS_TOKEN environment variable."
.into(),
))
}
fn job_status(&self, _handle: &JobHandle) -> Result<JobStatus, HardwareError> {
Err(HardwareError::AuthenticationFailed(
"Rigetti QCS credentials not configured.".into(),
))
}
fn job_results(&self, _handle: &JobHandle) -> Result<HardwareResult, HardwareError> {
Err(HardwareError::AuthenticationFailed(
"Rigetti QCS credentials not configured.".into(),
))
}
}
pub struct AmazonBraketProvider;
impl AmazonBraketProvider {
fn harmony_device() -> DeviceInfo {
let n = 11u32;
let mut cmap = Vec::new();
for i in 0..n {
for j in 0..n {
if i != j {
cmap.push((i, j));
}
}
}
DeviceInfo {
name: "braket_ionq_harmony".to_string(),
provider: ProviderType::AmazonBraket,
num_qubits: n,
basis_gates: vec!["gpi".into(), "gpi2".into(), "ms".into()],
coupling_map: cmap,
max_shots: 10_000,
status: DeviceStatus::Online,
}
}
fn aspen_device() -> DeviceInfo {
DeviceInfo {
name: "braket_rigetti_aspen_m3".to_string(),
provider: ProviderType::AmazonBraket,
num_qubits: 79,
basis_gates: vec![
"rx".into(),
"rz".into(),
"cz".into(),
"measure".into(),
],
coupling_map: linear_coupling_map(79),
max_shots: 100_000,
status: DeviceStatus::Online,
}
}
}
impl HardwareProvider for AmazonBraketProvider {
fn name(&self) -> &str {
"Amazon Braket"
}
fn provider_type(&self) -> ProviderType {
ProviderType::AmazonBraket
}
fn available_devices(&self) -> Vec<DeviceInfo> {
vec![Self::harmony_device(), Self::aspen_device()]
}
fn device_calibration(&self, device: &str) -> Option<DeviceCalibration> {
let dev = self
.available_devices()
.into_iter()
.find(|d| d.name == device)?;
Some(synthetic_calibration(device, dev.num_qubits, &dev.coupling_map))
}
fn submit_circuit(
&self,
_qasm: &str,
_shots: u32,
_device: &str,
) -> Result<JobHandle, HardwareError> {
Err(HardwareError::AuthenticationFailed(
"AWS credentials not configured. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY."
.into(),
))
}
fn job_status(&self, _handle: &JobHandle) -> Result<JobStatus, HardwareError> {
Err(HardwareError::AuthenticationFailed(
"AWS credentials not configured.".into(),
))
}
fn job_results(&self, _handle: &JobHandle) -> Result<HardwareResult, HardwareError> {
Err(HardwareError::AuthenticationFailed(
"AWS credentials not configured.".into(),
))
}
}
pub struct ProviderRegistry {
providers: Vec<Box<dyn HardwareProvider>>,
}
impl ProviderRegistry {
pub fn new() -> Self {
Self {
providers: Vec::new(),
}
}
pub fn register(&mut self, provider: Box<dyn HardwareProvider>) {
self.providers.push(provider);
}
pub fn get(&self, provider: ProviderType) -> Option<&dyn HardwareProvider> {
self.providers
.iter()
.find(|p| p.provider_type() == provider)
.map(|p| p.as_ref())
}
pub fn all_devices(&self) -> Vec<DeviceInfo> {
self.providers
.iter()
.flat_map(|p| p.available_devices())
.collect()
}
}
impl Default for ProviderRegistry {
fn default() -> Self {
let mut reg = Self::new();
reg.register(Box::new(LocalSimulatorProvider));
reg
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn provider_type_display() {
assert_eq!(format!("{}", ProviderType::IbmQuantum), "IBM Quantum");
assert_eq!(format!("{}", ProviderType::IonQ), "IonQ");
assert_eq!(format!("{}", ProviderType::Rigetti), "Rigetti");
assert_eq!(format!("{}", ProviderType::AmazonBraket), "Amazon Braket");
assert_eq!(
format!("{}", ProviderType::LocalSimulator),
"Local Simulator"
);
}
#[test]
fn provider_type_equality() {
assert_eq!(ProviderType::IbmQuantum, ProviderType::IbmQuantum);
assert_ne!(ProviderType::IbmQuantum, ProviderType::IonQ);
}
#[test]
fn device_status_display() {
assert_eq!(format!("{}", DeviceStatus::Online), "online");
assert_eq!(format!("{}", DeviceStatus::Offline), "offline");
assert_eq!(format!("{}", DeviceStatus::Maintenance), "maintenance");
assert_eq!(format!("{}", DeviceStatus::Retired), "retired");
}
#[test]
fn job_status_variants() {
let queued = JobStatus::Queued;
let running = JobStatus::Running;
let completed = JobStatus::Completed;
let failed = JobStatus::Failed("timeout".to_string());
let cancelled = JobStatus::Cancelled;
assert_eq!(queued, JobStatus::Queued);
assert_eq!(running, JobStatus::Running);
assert_eq!(completed, JobStatus::Completed);
assert_eq!(failed, JobStatus::Failed("timeout".to_string()));
assert_eq!(cancelled, JobStatus::Cancelled);
}
#[test]
fn hardware_error_display() {
let e = HardwareError::AuthenticationFailed("no token".into());
assert!(format!("{}", e).contains("authentication failed"));
let e = HardwareError::DeviceNotFound("foo".into());
assert!(format!("{}", e).contains("device not found"));
let e = HardwareError::DeviceOffline("bar".into());
assert!(format!("{}", e).contains("device offline"));
let e = HardwareError::CircuitTooLarge {
qubits: 50,
max: 32,
};
let msg = format!("{}", e);
assert!(msg.contains("50"));
assert!(msg.contains("32"));
let e = HardwareError::JobFailed("oops".into());
assert!(format!("{}", e).contains("job failed"));
let e = HardwareError::NetworkError("timeout".into());
assert!(format!("{}", e).contains("network error"));
let e = HardwareError::RateLimited {
retry_after_ms: 5000,
};
assert!(format!("{}", e).contains("5000"));
}
#[test]
fn hardware_error_is_error_trait() {
let e: Box<dyn std::error::Error> =
Box::new(HardwareError::NetworkError("test".into()));
assert!(e.to_string().contains("network error"));
}
#[test]
fn device_info_construction() {
let dev = DeviceInfo {
name: "test_device".into(),
provider: ProviderType::LocalSimulator,
num_qubits: 5,
basis_gates: vec!["h".into(), "cx".into()],
coupling_map: vec![(0, 1), (1, 2)],
max_shots: 1000,
status: DeviceStatus::Online,
};
assert_eq!(dev.name, "test_device");
assert_eq!(dev.num_qubits, 5);
assert_eq!(dev.basis_gates.len(), 2);
assert_eq!(dev.coupling_map.len(), 2);
assert_eq!(dev.status, DeviceStatus::Online);
}
#[test]
fn job_handle_construction() {
let handle = JobHandle {
job_id: "abc-123".into(),
provider: ProviderType::IonQ,
submitted_at: 1700000000,
};
assert_eq!(handle.job_id, "abc-123");
assert_eq!(handle.provider, ProviderType::IonQ);
assert_eq!(handle.submitted_at, 1700000000);
}
#[test]
fn hardware_result_construction() {
let mut counts = HashMap::new();
counts.insert(vec![false, false], 500);
counts.insert(vec![true, true], 500);
let result = HardwareResult {
counts,
shots: 1000,
execution_time_ms: 42,
device_name: "test".into(),
};
assert_eq!(result.shots, 1000);
assert_eq!(result.counts.len(), 2);
assert_eq!(result.execution_time_ms, 42);
}
#[test]
fn device_calibration_construction() {
let cal = DeviceCalibration {
device_name: "dev".into(),
timestamp: 1700000000,
qubit_t1: vec![100.0, 110.0],
qubit_t2: vec![80.0, 85.0],
readout_error: vec![(0.01, 0.02), (0.015, 0.025)],
gate_errors: HashMap::new(),
gate_times: HashMap::new(),
coupling_map: vec![(0, 1)],
};
assert_eq!(cal.qubit_t1.len(), 2);
assert_eq!(cal.qubit_t2.len(), 2);
assert_eq!(cal.readout_error.len(), 2);
}
#[test]
fn parse_qubit_count_openqasm2() {
let qasm = "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[5];\ncreg c[5];\nh q[0];\n";
assert_eq!(parse_qubit_count(qasm, 1), 5);
}
#[test]
fn parse_qubit_count_openqasm3() {
let qasm = "OPENQASM 3.0;\nqubit[8] q;\nbit[8] c;\n";
assert_eq!(parse_qubit_count(qasm, 1), 8);
}
#[test]
fn parse_qubit_count_multiple_registers() {
let qasm = "qreg a[3];\nqreg b[4];\n";
assert_eq!(parse_qubit_count(qasm, 1), 7);
}
#[test]
fn parse_qubit_count_fallback() {
let qasm = "h q[0];\ncx q[0], q[1];\n";
assert_eq!(parse_qubit_count(qasm, 2), 2);
}
#[test]
fn parse_gate_count_basic() {
let qasm =
"OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\ncreg c[2];\nh q[0];\ncx q[0], q[1];\nmeasure q[0] -> c[0];\n";
assert_eq!(parse_gate_count(qasm), 3);
}
#[test]
fn parse_gate_count_empty() {
let qasm = "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\n";
assert_eq!(parse_gate_count(qasm), 0);
}
#[test]
fn synthetic_calibration_correct_sizes() {
let coupling = vec![(0, 1), (1, 0), (1, 2), (2, 1)];
let cal = synthetic_calibration("test", 3, &coupling);
assert_eq!(cal.device_name, "test");
assert_eq!(cal.qubit_t1.len(), 3);
assert_eq!(cal.qubit_t2.len(), 3);
assert_eq!(cal.readout_error.len(), 3);
assert_eq!(cal.coupling_map.len(), 4);
assert!(cal.gate_errors.len() >= 9);
assert!(cal.gate_times.len() >= 9);
}
#[test]
fn synthetic_calibration_values_positive() {
let cal = synthetic_calibration("dev", 5, &[(0, 1)]);
for t1 in &cal.qubit_t1 {
assert!(*t1 > 0.0, "T1 must be positive");
}
for t2 in &cal.qubit_t2 {
assert!(*t2 > 0.0, "T2 must be positive");
}
for &(p0, p1) in &cal.readout_error {
assert!(p0 >= 0.0 && p0 <= 1.0);
assert!(p1 >= 0.0 && p1 <= 1.0);
}
}
#[test]
fn linear_coupling_map_correct() {
let map = linear_coupling_map(4);
assert_eq!(map.len(), 6);
assert!(map.contains(&(0, 1)));
assert!(map.contains(&(1, 0)));
assert!(map.contains(&(2, 3)));
assert!(map.contains(&(3, 2)));
}
#[test]
fn linear_coupling_map_single_qubit() {
let map = linear_coupling_map(1);
assert!(map.is_empty());
}
#[test]
fn heavy_hex_coupling_map_has_cross_links() {
let map = heavy_hex_coupling_map(20);
assert!(map.len() > linear_coupling_map(20).len());
assert!(map.contains(&(0, 4)));
assert!(map.contains(&(4, 0)));
}
#[test]
fn local_provider_name_and_type() {
let prov = LocalSimulatorProvider;
assert_eq!(prov.name(), "Local Simulator");
assert_eq!(prov.provider_type(), ProviderType::LocalSimulator);
}
#[test]
fn local_provider_devices() {
let prov = LocalSimulatorProvider;
let devs = prov.available_devices();
assert_eq!(devs.len(), 1);
assert_eq!(devs[0].name, "local_statevector_simulator");
assert_eq!(devs[0].num_qubits, 32);
assert_eq!(devs[0].status, DeviceStatus::Online);
assert!(devs[0].basis_gates.contains(&"h".to_string()));
assert!(devs[0].basis_gates.contains(&"cx".to_string()));
}
#[test]
fn local_provider_calibration() {
let prov = LocalSimulatorProvider;
let cal = prov
.device_calibration("local_statevector_simulator")
.expect("calibration should exist");
assert_eq!(cal.device_name, "local_statevector_simulator");
assert_eq!(cal.qubit_t1.len(), 32);
for &(p0, p1) in &cal.readout_error {
assert!((p0 - 0.0).abs() < 1e-12);
assert!((p1 - 0.0).abs() < 1e-12);
}
for val in cal.gate_errors.values() {
assert!((*val - 0.0).abs() < 1e-12);
}
}
#[test]
fn local_provider_calibration_unknown_device() {
let prov = LocalSimulatorProvider;
assert!(prov.device_calibration("nonexistent").is_none());
}
#[test]
fn local_provider_submit_and_retrieve() {
let prov = LocalSimulatorProvider;
let qasm = "OPENQASM 2.0;\nqreg q[2];\nh q[0];\ncx q[0], q[1];\n";
let handle = prov
.submit_circuit(qasm, 100, "local_statevector_simulator")
.expect("submit should succeed");
assert_eq!(handle.provider, ProviderType::LocalSimulator);
assert!(handle.job_id.starts_with("local-"));
let status = prov.job_status(&handle).expect("status should succeed");
assert_eq!(status, JobStatus::Completed);
let result = prov.job_results(&handle).expect("results should succeed");
assert_eq!(result.device_name, "local_statevector_simulator");
let total: usize = result.counts.values().sum();
assert_eq!(total, 100);
assert_eq!(result.shots, 100);
}
#[test]
fn local_provider_submit_wrong_device() {
let prov = LocalSimulatorProvider;
let result = prov.submit_circuit("qreg q[2];", 10, "wrong_device");
assert!(result.is_err());
match result.unwrap_err() {
HardwareError::DeviceNotFound(name) => assert_eq!(name, "wrong_device"),
other => panic!("expected DeviceNotFound, got: {:?}", other),
}
}
#[test]
fn local_provider_circuit_too_large() {
let prov = LocalSimulatorProvider;
let qasm = "OPENQASM 2.0;\nqreg q[50];\n";
let result = prov.submit_circuit(qasm, 10, "local_statevector_simulator");
assert!(result.is_err());
match result.unwrap_err() {
HardwareError::CircuitTooLarge { qubits, max } => {
assert_eq!(qubits, 50);
assert_eq!(max, 32);
}
other => panic!("expected CircuitTooLarge, got: {:?}", other),
}
}
#[test]
fn local_provider_unknown_job() {
let prov = LocalSimulatorProvider;
let handle = JobHandle {
job_id: "nonexistent".into(),
provider: ProviderType::LocalSimulator,
submitted_at: 0,
};
assert!(prov.job_status(&handle).is_err());
assert!(prov.job_results(&handle).is_err());
}
#[test]
fn local_provider_wrong_provider_handle() {
let prov = LocalSimulatorProvider;
let handle = JobHandle {
job_id: "some-id".into(),
provider: ProviderType::IbmQuantum,
submitted_at: 0,
};
assert!(prov.job_status(&handle).is_err());
assert!(prov.job_results(&handle).is_err());
}
#[test]
fn ibm_provider_name_and_type() {
let prov = IbmQuantumProvider;
assert_eq!(prov.name(), "IBM Quantum");
assert_eq!(prov.provider_type(), ProviderType::IbmQuantum);
}
#[test]
fn ibm_provider_devices() {
let prov = IbmQuantumProvider;
let devs = prov.available_devices();
assert_eq!(devs.len(), 2);
let brisbane = devs.iter().find(|d| d.name == "ibm_brisbane").unwrap();
assert_eq!(brisbane.num_qubits, 127);
assert_eq!(brisbane.provider, ProviderType::IbmQuantum);
assert_eq!(brisbane.status, DeviceStatus::Online);
let fez = devs.iter().find(|d| d.name == "ibm_fez").unwrap();
assert_eq!(fez.num_qubits, 133);
}
#[test]
fn ibm_provider_calibration() {
let prov = IbmQuantumProvider;
let cal = prov
.device_calibration("ibm_brisbane")
.expect("calibration should exist");
assert_eq!(cal.qubit_t1.len(), 127);
assert_eq!(cal.qubit_t2.len(), 127);
assert_eq!(cal.readout_error.len(), 127);
}
#[test]
fn ibm_provider_calibration_unknown_device() {
let prov = IbmQuantumProvider;
assert!(prov.device_calibration("nonexistent").is_none());
}
#[test]
fn ibm_provider_submit_fails_auth() {
let prov = IbmQuantumProvider;
let result = prov.submit_circuit("qreg q[2];", 100, "ibm_brisbane");
assert!(result.is_err());
match result.unwrap_err() {
HardwareError::AuthenticationFailed(msg) => {
assert!(msg.contains("IBM Quantum"));
}
other => panic!("expected AuthenticationFailed, got: {:?}", other),
}
}
#[test]
fn ibm_provider_job_status_fails_auth() {
let prov = IbmQuantumProvider;
let handle = JobHandle {
job_id: "x".into(),
provider: ProviderType::IbmQuantum,
submitted_at: 0,
};
assert!(prov.job_status(&handle).is_err());
assert!(prov.job_results(&handle).is_err());
}
#[test]
fn ionq_provider_name_and_type() {
let prov = IonQProvider;
assert_eq!(prov.name(), "IonQ");
assert_eq!(prov.provider_type(), ProviderType::IonQ);
}
#[test]
fn ionq_provider_devices() {
let prov = IonQProvider;
let devs = prov.available_devices();
assert_eq!(devs.len(), 2);
let aria = devs.iter().find(|d| d.name == "ionq_aria").unwrap();
assert_eq!(aria.num_qubits, 25);
assert_eq!(aria.coupling_map.len(), 25 * 24);
let forte = devs.iter().find(|d| d.name == "ionq_forte").unwrap();
assert_eq!(forte.num_qubits, 36);
}
#[test]
fn ionq_provider_calibration_aria() {
let prov = IonQProvider;
let cal = prov
.device_calibration("ionq_aria")
.expect("calibration should exist");
assert_eq!(cal.qubit_t1.len(), 25);
for t1 in &cal.qubit_t1 {
assert!(*t1 > 1_000_000.0);
}
}
#[test]
fn ionq_provider_calibration_forte() {
let prov = IonQProvider;
let cal = prov
.device_calibration("ionq_forte")
.expect("calibration should exist");
assert_eq!(cal.qubit_t1.len(), 36);
}
#[test]
fn ionq_provider_calibration_unknown() {
let prov = IonQProvider;
assert!(prov.device_calibration("nonexistent").is_none());
}
#[test]
fn ionq_provider_submit_fails_auth() {
let prov = IonQProvider;
let result = prov.submit_circuit("qreg q[2];", 100, "ionq_aria");
assert!(result.is_err());
match result.unwrap_err() {
HardwareError::AuthenticationFailed(msg) => {
assert!(msg.contains("IonQ"));
}
other => panic!("expected AuthenticationFailed, got: {:?}", other),
}
}
#[test]
fn rigetti_provider_name_and_type() {
let prov = RigettiProvider;
assert_eq!(prov.name(), "Rigetti");
assert_eq!(prov.provider_type(), ProviderType::Rigetti);
}
#[test]
fn rigetti_provider_devices() {
let prov = RigettiProvider;
let devs = prov.available_devices();
assert_eq!(devs.len(), 1);
assert_eq!(devs[0].name, "rigetti_ankaa_2");
assert_eq!(devs[0].num_qubits, 84);
}
#[test]
fn rigetti_provider_calibration() {
let prov = RigettiProvider;
let cal = prov
.device_calibration("rigetti_ankaa_2")
.expect("calibration should exist");
assert_eq!(cal.qubit_t1.len(), 84);
assert_eq!(cal.qubit_t2.len(), 84);
}
#[test]
fn rigetti_provider_calibration_unknown() {
let prov = RigettiProvider;
assert!(prov.device_calibration("nonexistent").is_none());
}
#[test]
fn rigetti_provider_submit_fails_auth() {
let prov = RigettiProvider;
let result = prov.submit_circuit("qreg q[2];", 100, "rigetti_ankaa_2");
assert!(result.is_err());
match result.unwrap_err() {
HardwareError::AuthenticationFailed(msg) => {
assert!(msg.contains("Rigetti"));
}
other => panic!("expected AuthenticationFailed, got: {:?}", other),
}
}
#[test]
fn braket_provider_name_and_type() {
let prov = AmazonBraketProvider;
assert_eq!(prov.name(), "Amazon Braket");
assert_eq!(prov.provider_type(), ProviderType::AmazonBraket);
}
#[test]
fn braket_provider_devices() {
let prov = AmazonBraketProvider;
let devs = prov.available_devices();
assert_eq!(devs.len(), 2);
let harmony = devs
.iter()
.find(|d| d.name == "braket_ionq_harmony")
.unwrap();
assert_eq!(harmony.num_qubits, 11);
let aspen = devs
.iter()
.find(|d| d.name == "braket_rigetti_aspen_m3")
.unwrap();
assert_eq!(aspen.num_qubits, 79);
}
#[test]
fn braket_provider_calibration() {
let prov = AmazonBraketProvider;
let cal = prov
.device_calibration("braket_ionq_harmony")
.expect("calibration should exist");
assert_eq!(cal.qubit_t1.len(), 11);
let cal2 = prov
.device_calibration("braket_rigetti_aspen_m3")
.expect("calibration should exist");
assert_eq!(cal2.qubit_t1.len(), 79);
}
#[test]
fn braket_provider_calibration_unknown() {
let prov = AmazonBraketProvider;
assert!(prov.device_calibration("nonexistent").is_none());
}
#[test]
fn braket_provider_submit_fails_auth() {
let prov = AmazonBraketProvider;
let result = prov.submit_circuit("qreg q[2];", 100, "braket_ionq_harmony");
assert!(result.is_err());
match result.unwrap_err() {
HardwareError::AuthenticationFailed(msg) => {
assert!(msg.contains("AWS"));
}
other => panic!("expected AuthenticationFailed, got: {:?}", other),
}
}
#[test]
fn registry_new_is_empty() {
let reg = ProviderRegistry::new();
assert!(reg.all_devices().is_empty());
assert!(reg.get(ProviderType::LocalSimulator).is_none());
}
#[test]
fn registry_default_has_local_simulator() {
let reg = ProviderRegistry::default();
let local = reg.get(ProviderType::LocalSimulator);
assert!(local.is_some());
assert_eq!(local.unwrap().name(), "Local Simulator");
}
#[test]
fn registry_default_devices() {
let reg = ProviderRegistry::default();
let devs = reg.all_devices();
assert_eq!(devs.len(), 1);
assert_eq!(devs[0].name, "local_statevector_simulator");
}
#[test]
fn registry_register_multiple() {
let mut reg = ProviderRegistry::new();
reg.register(Box::new(LocalSimulatorProvider));
reg.register(Box::new(IbmQuantumProvider));
reg.register(Box::new(IonQProvider));
reg.register(Box::new(RigettiProvider));
reg.register(Box::new(AmazonBraketProvider));
assert!(reg.get(ProviderType::LocalSimulator).is_some());
assert!(reg.get(ProviderType::IbmQuantum).is_some());
assert!(reg.get(ProviderType::IonQ).is_some());
assert!(reg.get(ProviderType::Rigetti).is_some());
assert!(reg.get(ProviderType::AmazonBraket).is_some());
assert_eq!(reg.all_devices().len(), 8);
}
#[test]
fn registry_get_nonexistent() {
let reg = ProviderRegistry::default();
assert!(reg.get(ProviderType::IbmQuantum).is_none());
}
#[test]
fn registry_all_devices_aggregates() {
let mut reg = ProviderRegistry::new();
reg.register(Box::new(IbmQuantumProvider));
reg.register(Box::new(IonQProvider));
let devs = reg.all_devices();
assert_eq!(devs.len(), 4);
let names: Vec<&str> = devs.iter().map(|d| d.name.as_str()).collect();
assert!(names.contains(&"ibm_brisbane"));
assert!(names.contains(&"ibm_fez"));
assert!(names.contains(&"ionq_aria"));
assert!(names.contains(&"ionq_forte"));
}
#[test]
fn registry_local_submit_integration() {
let reg = ProviderRegistry::default();
let local = reg.get(ProviderType::LocalSimulator).unwrap();
let qasm = "OPENQASM 2.0;\nqreg q[2];\n";
let handle = local
.submit_circuit(qasm, 50, "local_statevector_simulator")
.expect("submit should succeed");
let status = local.job_status(&handle).expect("status should succeed");
assert_eq!(status, JobStatus::Completed);
let result = local.job_results(&handle).expect("results should succeed");
let total: usize = result.counts.values().sum();
assert_eq!(total, 50);
}
#[test]
fn registry_stub_submit_through_registry() {
let mut reg = ProviderRegistry::new();
reg.register(Box::new(IbmQuantumProvider));
let ibm = reg.get(ProviderType::IbmQuantum).unwrap();
let result = ibm.submit_circuit("qreg q[2];", 100, "ibm_brisbane");
assert!(result.is_err());
}
#[test]
fn provider_trait_is_object_safe() {
let providers: Vec<Box<dyn HardwareProvider>> = vec![
Box::new(LocalSimulatorProvider),
Box::new(IbmQuantumProvider),
Box::new(IonQProvider),
Box::new(RigettiProvider),
Box::new(AmazonBraketProvider),
];
assert_eq!(providers.len(), 5);
for p in &providers {
assert!(!p.name().is_empty());
assert!(!p.available_devices().is_empty());
}
}
#[test]
fn providers_are_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<LocalSimulatorProvider>();
assert_send_sync::<IbmQuantumProvider>();
assert_send_sync::<IonQProvider>();
assert_send_sync::<RigettiProvider>();
assert_send_sync::<AmazonBraketProvider>();
}
}