1use std::collections::HashMap;
14use std::fmt;
15
16use crate::circuit::QuantumCircuit;
17use crate::simulator::Simulator;
18
19#[derive(Debug)]
25pub enum HardwareError {
26 AuthenticationFailed(String),
28 DeviceNotFound(String),
30 DeviceOffline(String),
32 CircuitTooLarge { qubits: u32, max: u32 },
34 JobFailed(String),
36 NetworkError(String),
38 RateLimited { retry_after_ms: u64 },
40}
41
42impl fmt::Display for HardwareError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 HardwareError::AuthenticationFailed(msg) => {
46 write!(f, "authentication failed: {}", msg)
47 }
48 HardwareError::DeviceNotFound(name) => {
49 write!(f, "device not found: {}", name)
50 }
51 HardwareError::DeviceOffline(name) => {
52 write!(f, "device offline: {}", name)
53 }
54 HardwareError::CircuitTooLarge { qubits, max } => {
55 write!(
56 f,
57 "circuit requires {} qubits but device supports at most {}",
58 qubits, max
59 )
60 }
61 HardwareError::JobFailed(msg) => {
62 write!(f, "job failed: {}", msg)
63 }
64 HardwareError::NetworkError(msg) => {
65 write!(f, "network error: {}", msg)
66 }
67 HardwareError::RateLimited { retry_after_ms } => {
68 write!(f, "rate limited: retry after {} ms", retry_after_ms)
69 }
70 }
71 }
72}
73
74impl std::error::Error for HardwareError {}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82pub enum ProviderType {
83 IbmQuantum,
84 IonQ,
85 Rigetti,
86 AmazonBraket,
87 LocalSimulator,
88}
89
90impl fmt::Display for ProviderType {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 ProviderType::IbmQuantum => write!(f, "IBM Quantum"),
94 ProviderType::IonQ => write!(f, "IonQ"),
95 ProviderType::Rigetti => write!(f, "Rigetti"),
96 ProviderType::AmazonBraket => write!(f, "Amazon Braket"),
97 ProviderType::LocalSimulator => write!(f, "Local Simulator"),
98 }
99 }
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum DeviceStatus {
105 Online,
106 Offline,
107 Maintenance,
108 Retired,
109}
110
111impl fmt::Display for DeviceStatus {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 match self {
114 DeviceStatus::Online => write!(f, "online"),
115 DeviceStatus::Offline => write!(f, "offline"),
116 DeviceStatus::Maintenance => write!(f, "maintenance"),
117 DeviceStatus::Retired => write!(f, "retired"),
118 }
119 }
120}
121
122#[derive(Debug, Clone, PartialEq)]
124pub enum JobStatus {
125 Queued,
126 Running,
127 Completed,
128 Failed(String),
129 Cancelled,
130}
131
132#[derive(Debug, Clone)]
134pub struct DeviceInfo {
135 pub name: String,
136 pub provider: ProviderType,
137 pub num_qubits: u32,
138 pub basis_gates: Vec<String>,
139 pub coupling_map: Vec<(u32, u32)>,
140 pub max_shots: u32,
141 pub status: DeviceStatus,
142}
143
144#[derive(Debug, Clone)]
147pub struct JobHandle {
148 pub job_id: String,
149 pub provider: ProviderType,
150 pub submitted_at: u64,
151}
152
153#[derive(Debug, Clone)]
155pub struct HardwareResult {
156 pub counts: HashMap<Vec<bool>, usize>,
157 pub shots: u32,
158 pub execution_time_ms: u64,
159 pub device_name: String,
160}
161
162#[derive(Debug, Clone)]
164pub struct DeviceCalibration {
165 pub device_name: String,
166 pub timestamp: u64,
167 pub qubit_t1: Vec<f64>,
169 pub qubit_t2: Vec<f64>,
171 pub readout_error: Vec<(f64, f64)>,
173 pub gate_errors: HashMap<String, f64>,
175 pub gate_times: HashMap<String, f64>,
177 pub coupling_map: Vec<(u32, u32)>,
179}
180
181pub trait HardwareProvider: Send + Sync {
191 fn name(&self) -> &str;
193
194 fn provider_type(&self) -> ProviderType;
196
197 fn available_devices(&self) -> Vec<DeviceInfo>;
199
200 fn device_calibration(&self, device: &str) -> Option<DeviceCalibration>;
202
203 fn submit_circuit(
205 &self,
206 qasm: &str,
207 shots: u32,
208 device: &str,
209 ) -> Result<JobHandle, HardwareError>;
210
211 fn job_status(&self, handle: &JobHandle) -> Result<JobStatus, HardwareError>;
213
214 fn job_results(&self, handle: &JobHandle) -> Result<HardwareResult, HardwareError>;
216}
217
218fn parse_qubit_count(qasm: &str, default: u32) -> u32 {
227 let mut total: u32 = 0;
228 for line in qasm.lines() {
229 let trimmed = line.trim();
230 if trimmed.starts_with("qreg") {
232 if let Some(start) = trimmed.find('[') {
233 if let Some(end) = trimmed.find(']') {
234 if let Ok(n) = trimmed[start + 1..end].parse::<u32>() {
235 total += n;
236 }
237 }
238 }
239 }
240 if trimmed.starts_with("qubit[") {
242 if let Some(end) = trimmed.find(']') {
243 if let Ok(n) = trimmed[6..end].parse::<u32>() {
244 total += n;
245 }
246 }
247 }
248 }
249 if total == 0 { default } else { total }
250}
251
252#[allow(dead_code)]
255fn parse_gate_count(qasm: &str) -> usize {
256 qasm.lines()
257 .map(|l| l.trim())
258 .filter(|l| {
259 !l.is_empty()
260 && !l.starts_with("//")
261 && !l.starts_with("OPENQASM")
262 && !l.starts_with("include")
263 && !l.starts_with("qreg")
264 && !l.starts_with("creg")
265 && !l.starts_with("qubit")
266 && !l.starts_with("bit")
267 && !l.starts_with("gate ")
268 && !l.starts_with('{')
269 && !l.starts_with('}')
270 })
271 .count()
272}
273
274fn synthetic_calibration(
280 device_name: &str,
281 num_qubits: u32,
282 coupling_map: &[(u32, u32)],
283) -> DeviceCalibration {
284 let mut qubit_t1 = Vec::with_capacity(num_qubits as usize);
285 let mut qubit_t2 = Vec::with_capacity(num_qubits as usize);
286 let mut readout_error = Vec::with_capacity(num_qubits as usize);
287
288 for i in 0..num_qubits {
290 let variation = 1.0 + 0.05 * ((i as f64 * 7.3).sin());
291 qubit_t1.push(100.0 * variation);
293 qubit_t2.push(80.0 * variation);
295 let re0 = 0.015 + 0.005 * ((i as f64 * 3.1).cos());
297 let re1 = 0.020 + 0.005 * ((i as f64 * 5.7).sin());
298 readout_error.push((re0, re1));
299 }
300
301 let mut gate_errors = HashMap::new();
302 let mut gate_times = HashMap::new();
303
304 for i in 0..num_qubits {
306 let variation = 1.0 + 0.1 * ((i as f64 * 2.3).sin());
307 gate_errors.insert(format!("sx_{}", i), 0.0003 * variation);
308 gate_errors.insert(format!("rz_{}", i), 0.0);
309 gate_errors.insert(format!("x_{}", i), 0.0003 * variation);
310 gate_times.insert(format!("sx_{}", i), 35.5 * variation);
311 gate_times.insert(format!("rz_{}", i), 0.0);
312 gate_times.insert(format!("x_{}", i), 35.5 * variation);
313 }
314
315 for &(q0, q1) in coupling_map {
317 let variation = 1.0 + 0.1 * (((q0 + q1) as f64 * 1.7).sin());
318 gate_errors.insert(format!("cx_{}_{}", q0, q1), 0.008 * variation);
319 gate_times.insert(format!("cx_{}_{}", q0, q1), 300.0 * variation);
320 }
321
322 DeviceCalibration {
323 device_name: device_name.to_string(),
324 timestamp: 1700000000,
325 qubit_t1,
326 qubit_t2,
327 readout_error,
328 gate_errors,
329 gate_times,
330 coupling_map: coupling_map.to_vec(),
331 }
332}
333
334fn linear_coupling_map(n: u32) -> Vec<(u32, u32)> {
336 let mut map = Vec::with_capacity((n as usize).saturating_sub(1) * 2);
337 for i in 0..n.saturating_sub(1) {
338 map.push((i, i + 1));
339 map.push((i + 1, i));
340 }
341 map
342}
343
344fn heavy_hex_coupling_map(n: u32) -> Vec<(u32, u32)> {
349 let mut map = linear_coupling_map(n);
350 let mut i = 0;
352 while i + 4 < n {
353 map.push((i, i + 4));
354 map.push((i + 4, i));
355 i += 4;
356 }
357 map
358}
359
360pub struct LocalSimulatorProvider;
371
372impl LocalSimulatorProvider {
373 const MAX_QUBITS: u32 = 32;
375 const MAX_SHOTS: u32 = 1_000_000;
377 const DEVICE_NAME: &'static str = "local_statevector_simulator";
379
380 fn device_info(&self) -> DeviceInfo {
381 DeviceInfo {
382 name: Self::DEVICE_NAME.to_string(),
383 provider: ProviderType::LocalSimulator,
384 num_qubits: Self::MAX_QUBITS,
385 basis_gates: vec![
386 "h".into(),
387 "x".into(),
388 "y".into(),
389 "z".into(),
390 "s".into(),
391 "sdg".into(),
392 "t".into(),
393 "tdg".into(),
394 "rx".into(),
395 "ry".into(),
396 "rz".into(),
397 "cx".into(),
398 "cz".into(),
399 "swap".into(),
400 "measure".into(),
401 "reset".into(),
402 ],
403 coupling_map: Vec::new(), max_shots: Self::MAX_SHOTS,
405 status: DeviceStatus::Online,
406 }
407 }
408}
409
410impl HardwareProvider for LocalSimulatorProvider {
411 fn name(&self) -> &str {
412 "Local Simulator"
413 }
414
415 fn provider_type(&self) -> ProviderType {
416 ProviderType::LocalSimulator
417 }
418
419 fn available_devices(&self) -> Vec<DeviceInfo> {
420 vec![self.device_info()]
421 }
422
423 fn device_calibration(&self, device: &str) -> Option<DeviceCalibration> {
424 if device != Self::DEVICE_NAME {
425 return None;
426 }
427 let mut cal = synthetic_calibration(device, Self::MAX_QUBITS, &[]);
430 for t1 in &mut cal.qubit_t1 {
432 *t1 = f64::INFINITY;
433 }
434 for t2 in &mut cal.qubit_t2 {
435 *t2 = f64::INFINITY;
436 }
437 for re in &mut cal.readout_error {
438 *re = (0.0, 0.0);
439 }
440 cal.gate_errors.values_mut().for_each(|v| *v = 0.0);
441 Some(cal)
442 }
443
444 fn submit_circuit(
445 &self,
446 qasm: &str,
447 shots: u32,
448 device: &str,
449 ) -> Result<JobHandle, HardwareError> {
450 if device != Self::DEVICE_NAME {
451 return Err(HardwareError::DeviceNotFound(device.to_string()));
452 }
453
454 let num_qubits = parse_qubit_count(qasm, 2);
455 if num_qubits > Self::MAX_QUBITS {
456 return Err(HardwareError::CircuitTooLarge {
457 qubits: num_qubits,
458 max: Self::MAX_QUBITS,
459 });
460 }
461
462 let effective_shots = shots.min(Self::MAX_SHOTS);
463
464 let mut circuit = QuantumCircuit::new(num_qubits);
469 for q in 0..num_qubits {
471 circuit.h(q);
472 }
473 circuit.measure_all();
474
475 let start = std::time::Instant::now();
476 let shot_result = Simulator::run_shots(&circuit, effective_shots, Some(42))
477 .map_err(|e| HardwareError::JobFailed(format!("{}", e)))?;
478 let elapsed_ms = start.elapsed().as_millis() as u64;
479
480 let result = HardwareResult {
484 counts: shot_result.counts,
485 shots: effective_shots,
486 execution_time_ms: elapsed_ms,
487 device_name: Self::DEVICE_NAME.to_string(),
488 };
489
490 let job_id = format!("local-{}", fastrand_u64());
492 COMPLETED_JOBS.with(|jobs| {
493 jobs.borrow_mut().insert(job_id.clone(), result);
494 });
495
496 Ok(JobHandle {
497 job_id,
498 provider: ProviderType::LocalSimulator,
499 submitted_at: current_epoch_secs(),
500 })
501 }
502
503 fn job_status(&self, handle: &JobHandle) -> Result<JobStatus, HardwareError> {
504 if handle.provider != ProviderType::LocalSimulator {
505 return Err(HardwareError::DeviceNotFound(
506 "job does not belong to local simulator".to_string(),
507 ));
508 }
509 let exists = COMPLETED_JOBS.with(|jobs| jobs.borrow().contains_key(&handle.job_id));
511 if exists {
512 Ok(JobStatus::Completed)
513 } else {
514 Err(HardwareError::JobFailed(format!(
515 "unknown job id: {}",
516 handle.job_id
517 )))
518 }
519 }
520
521 fn job_results(&self, handle: &JobHandle) -> Result<HardwareResult, HardwareError> {
522 if handle.provider != ProviderType::LocalSimulator {
523 return Err(HardwareError::DeviceNotFound(
524 "job does not belong to local simulator".to_string(),
525 ));
526 }
527 COMPLETED_JOBS.with(|jobs| {
528 jobs.borrow()
529 .get(&handle.job_id)
530 .cloned()
531 .ok_or_else(|| {
532 HardwareError::JobFailed(format!("unknown job id: {}", handle.job_id))
533 })
534 })
535 }
536}
537
538thread_local! {
540 static COMPLETED_JOBS: std::cell::RefCell<HashMap<String, HardwareResult>> =
541 std::cell::RefCell::new(HashMap::new());
542}
543
544fn fastrand_u64() -> u64 {
546 use std::time::SystemTime;
547 let seed = SystemTime::now()
548 .duration_since(SystemTime::UNIX_EPOCH)
549 .unwrap_or_default()
550 .as_nanos() as u64;
551 let mut z = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
553 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
554 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
555 z ^ (z >> 31)
556}
557
558fn current_epoch_secs() -> u64 {
560 use std::time::SystemTime;
561 SystemTime::now()
562 .duration_since(SystemTime::UNIX_EPOCH)
563 .unwrap_or_default()
564 .as_secs()
565}
566
567pub struct IbmQuantumProvider;
577
578impl IbmQuantumProvider {
579 fn eagle_device() -> DeviceInfo {
580 DeviceInfo {
581 name: "ibm_brisbane".to_string(),
582 provider: ProviderType::IbmQuantum,
583 num_qubits: 127,
584 basis_gates: vec![
585 "id".into(),
586 "rz".into(),
587 "sx".into(),
588 "x".into(),
589 "cx".into(),
590 "reset".into(),
591 ],
592 coupling_map: heavy_hex_coupling_map(127),
593 max_shots: 100_000,
594 status: DeviceStatus::Online,
595 }
596 }
597
598 fn heron_device() -> DeviceInfo {
599 DeviceInfo {
600 name: "ibm_fez".to_string(),
601 provider: ProviderType::IbmQuantum,
602 num_qubits: 133,
603 basis_gates: vec![
604 "id".into(),
605 "rz".into(),
606 "sx".into(),
607 "x".into(),
608 "ecr".into(),
609 "reset".into(),
610 ],
611 coupling_map: heavy_hex_coupling_map(133),
612 max_shots: 100_000,
613 status: DeviceStatus::Online,
614 }
615 }
616}
617
618impl HardwareProvider for IbmQuantumProvider {
619 fn name(&self) -> &str {
620 "IBM Quantum"
621 }
622
623 fn provider_type(&self) -> ProviderType {
624 ProviderType::IbmQuantum
625 }
626
627 fn available_devices(&self) -> Vec<DeviceInfo> {
628 vec![Self::eagle_device(), Self::heron_device()]
629 }
630
631 fn device_calibration(&self, device: &str) -> Option<DeviceCalibration> {
632 let dev = self
633 .available_devices()
634 .into_iter()
635 .find(|d| d.name == device)?;
636 Some(synthetic_calibration(device, dev.num_qubits, &dev.coupling_map))
637 }
638
639 fn submit_circuit(
640 &self,
641 _qasm: &str,
642 _shots: u32,
643 _device: &str,
644 ) -> Result<JobHandle, HardwareError> {
645 Err(HardwareError::AuthenticationFailed(
646 "IBM Quantum API token not configured. Set IBMQ_TOKEN environment variable.".into(),
647 ))
648 }
649
650 fn job_status(&self, _handle: &JobHandle) -> Result<JobStatus, HardwareError> {
651 Err(HardwareError::AuthenticationFailed(
652 "IBM Quantum API token not configured.".into(),
653 ))
654 }
655
656 fn job_results(&self, _handle: &JobHandle) -> Result<HardwareResult, HardwareError> {
657 Err(HardwareError::AuthenticationFailed(
658 "IBM Quantum API token not configured.".into(),
659 ))
660 }
661}
662
663pub struct IonQProvider;
671
672impl IonQProvider {
673 fn aria_device() -> DeviceInfo {
674 let n = 25u32;
676 let mut cmap = Vec::new();
677 for i in 0..n {
678 for j in 0..n {
679 if i != j {
680 cmap.push((i, j));
681 }
682 }
683 }
684 DeviceInfo {
685 name: "ionq_aria".to_string(),
686 provider: ProviderType::IonQ,
687 num_qubits: n,
688 basis_gates: vec!["gpi".into(), "gpi2".into(), "ms".into()],
689 coupling_map: cmap,
690 max_shots: 10_000,
691 status: DeviceStatus::Online,
692 }
693 }
694
695 fn forte_device() -> DeviceInfo {
696 let n = 36u32;
697 let mut cmap = Vec::new();
698 for i in 0..n {
699 for j in 0..n {
700 if i != j {
701 cmap.push((i, j));
702 }
703 }
704 }
705 DeviceInfo {
706 name: "ionq_forte".to_string(),
707 provider: ProviderType::IonQ,
708 num_qubits: n,
709 basis_gates: vec!["gpi".into(), "gpi2".into(), "ms".into()],
710 coupling_map: cmap,
711 max_shots: 10_000,
712 status: DeviceStatus::Online,
713 }
714 }
715
716 fn aria_calibration() -> DeviceCalibration {
717 let dev = Self::aria_device();
718 let mut cal = synthetic_calibration(&dev.name, dev.num_qubits, &dev.coupling_map);
719 for t1 in &mut cal.qubit_t1 {
721 *t1 = 10_000_000.0; }
723 for t2 in &mut cal.qubit_t2 {
724 *t2 = 1_000_000.0; }
726 for val in cal.gate_errors.values_mut() {
728 *val *= 0.1;
729 }
730 cal
731 }
732}
733
734impl HardwareProvider for IonQProvider {
735 fn name(&self) -> &str {
736 "IonQ"
737 }
738
739 fn provider_type(&self) -> ProviderType {
740 ProviderType::IonQ
741 }
742
743 fn available_devices(&self) -> Vec<DeviceInfo> {
744 vec![Self::aria_device(), Self::forte_device()]
745 }
746
747 fn device_calibration(&self, device: &str) -> Option<DeviceCalibration> {
748 match device {
749 "ionq_aria" => Some(Self::aria_calibration()),
750 "ionq_forte" => {
751 let dev = Self::forte_device();
752 let mut cal =
753 synthetic_calibration(&dev.name, dev.num_qubits, &dev.coupling_map);
754 for t1 in &mut cal.qubit_t1 {
755 *t1 = 10_000_000.0;
756 }
757 for t2 in &mut cal.qubit_t2 {
758 *t2 = 1_000_000.0;
759 }
760 for val in cal.gate_errors.values_mut() {
761 *val *= 0.1;
762 }
763 Some(cal)
764 }
765 _ => None,
766 }
767 }
768
769 fn submit_circuit(
770 &self,
771 _qasm: &str,
772 _shots: u32,
773 _device: &str,
774 ) -> Result<JobHandle, HardwareError> {
775 Err(HardwareError::AuthenticationFailed(
776 "IonQ API key not configured. Set IONQ_API_KEY environment variable.".into(),
777 ))
778 }
779
780 fn job_status(&self, _handle: &JobHandle) -> Result<JobStatus, HardwareError> {
781 Err(HardwareError::AuthenticationFailed(
782 "IonQ API key not configured.".into(),
783 ))
784 }
785
786 fn job_results(&self, _handle: &JobHandle) -> Result<HardwareResult, HardwareError> {
787 Err(HardwareError::AuthenticationFailed(
788 "IonQ API key not configured.".into(),
789 ))
790 }
791}
792
793pub struct RigettiProvider;
801
802impl RigettiProvider {
803 fn ankaa_device() -> DeviceInfo {
804 DeviceInfo {
805 name: "rigetti_ankaa_2".to_string(),
806 provider: ProviderType::Rigetti,
807 num_qubits: 84,
808 basis_gates: vec![
809 "rx".into(),
810 "rz".into(),
811 "cz".into(),
812 "measure".into(),
813 ],
814 coupling_map: linear_coupling_map(84),
815 max_shots: 100_000,
816 status: DeviceStatus::Online,
817 }
818 }
819}
820
821impl HardwareProvider for RigettiProvider {
822 fn name(&self) -> &str {
823 "Rigetti"
824 }
825
826 fn provider_type(&self) -> ProviderType {
827 ProviderType::Rigetti
828 }
829
830 fn available_devices(&self) -> Vec<DeviceInfo> {
831 vec![Self::ankaa_device()]
832 }
833
834 fn device_calibration(&self, device: &str) -> Option<DeviceCalibration> {
835 if device != "rigetti_ankaa_2" {
836 return None;
837 }
838 let dev = Self::ankaa_device();
839 Some(synthetic_calibration(device, dev.num_qubits, &dev.coupling_map))
840 }
841
842 fn submit_circuit(
843 &self,
844 _qasm: &str,
845 _shots: u32,
846 _device: &str,
847 ) -> Result<JobHandle, HardwareError> {
848 Err(HardwareError::AuthenticationFailed(
849 "Rigetti QCS credentials not configured. Set QCS_ACCESS_TOKEN environment variable."
850 .into(),
851 ))
852 }
853
854 fn job_status(&self, _handle: &JobHandle) -> Result<JobStatus, HardwareError> {
855 Err(HardwareError::AuthenticationFailed(
856 "Rigetti QCS credentials not configured.".into(),
857 ))
858 }
859
860 fn job_results(&self, _handle: &JobHandle) -> Result<HardwareResult, HardwareError> {
861 Err(HardwareError::AuthenticationFailed(
862 "Rigetti QCS credentials not configured.".into(),
863 ))
864 }
865}
866
867pub struct AmazonBraketProvider;
876
877impl AmazonBraketProvider {
878 fn harmony_device() -> DeviceInfo {
879 let n = 11u32;
880 let mut cmap = Vec::new();
881 for i in 0..n {
882 for j in 0..n {
883 if i != j {
884 cmap.push((i, j));
885 }
886 }
887 }
888 DeviceInfo {
889 name: "braket_ionq_harmony".to_string(),
890 provider: ProviderType::AmazonBraket,
891 num_qubits: n,
892 basis_gates: vec!["gpi".into(), "gpi2".into(), "ms".into()],
893 coupling_map: cmap,
894 max_shots: 10_000,
895 status: DeviceStatus::Online,
896 }
897 }
898
899 fn aspen_device() -> DeviceInfo {
900 DeviceInfo {
901 name: "braket_rigetti_aspen_m3".to_string(),
902 provider: ProviderType::AmazonBraket,
903 num_qubits: 79,
904 basis_gates: vec![
905 "rx".into(),
906 "rz".into(),
907 "cz".into(),
908 "measure".into(),
909 ],
910 coupling_map: linear_coupling_map(79),
911 max_shots: 100_000,
912 status: DeviceStatus::Online,
913 }
914 }
915}
916
917impl HardwareProvider for AmazonBraketProvider {
918 fn name(&self) -> &str {
919 "Amazon Braket"
920 }
921
922 fn provider_type(&self) -> ProviderType {
923 ProviderType::AmazonBraket
924 }
925
926 fn available_devices(&self) -> Vec<DeviceInfo> {
927 vec![Self::harmony_device(), Self::aspen_device()]
928 }
929
930 fn device_calibration(&self, device: &str) -> Option<DeviceCalibration> {
931 let dev = self
932 .available_devices()
933 .into_iter()
934 .find(|d| d.name == device)?;
935 Some(synthetic_calibration(device, dev.num_qubits, &dev.coupling_map))
936 }
937
938 fn submit_circuit(
939 &self,
940 _qasm: &str,
941 _shots: u32,
942 _device: &str,
943 ) -> Result<JobHandle, HardwareError> {
944 Err(HardwareError::AuthenticationFailed(
945 "AWS credentials not configured. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY."
946 .into(),
947 ))
948 }
949
950 fn job_status(&self, _handle: &JobHandle) -> Result<JobStatus, HardwareError> {
951 Err(HardwareError::AuthenticationFailed(
952 "AWS credentials not configured.".into(),
953 ))
954 }
955
956 fn job_results(&self, _handle: &JobHandle) -> Result<HardwareResult, HardwareError> {
957 Err(HardwareError::AuthenticationFailed(
958 "AWS credentials not configured.".into(),
959 ))
960 }
961}
962
963pub struct ProviderRegistry {
972 providers: Vec<Box<dyn HardwareProvider>>,
973}
974
975impl ProviderRegistry {
976 pub fn new() -> Self {
978 Self {
979 providers: Vec::new(),
980 }
981 }
982
983 pub fn register(&mut self, provider: Box<dyn HardwareProvider>) {
985 self.providers.push(provider);
986 }
987
988 pub fn get(&self, provider: ProviderType) -> Option<&dyn HardwareProvider> {
993 self.providers
994 .iter()
995 .find(|p| p.provider_type() == provider)
996 .map(|p| p.as_ref())
997 }
998
999 pub fn all_devices(&self) -> Vec<DeviceInfo> {
1001 self.providers
1002 .iter()
1003 .flat_map(|p| p.available_devices())
1004 .collect()
1005 }
1006}
1007
1008impl Default for ProviderRegistry {
1009 fn default() -> Self {
1011 let mut reg = Self::new();
1012 reg.register(Box::new(LocalSimulatorProvider));
1013 reg
1014 }
1015}
1016
1017#[cfg(test)]
1022mod tests {
1023 use super::*;
1024
1025 #[test]
1028 fn provider_type_display() {
1029 assert_eq!(format!("{}", ProviderType::IbmQuantum), "IBM Quantum");
1030 assert_eq!(format!("{}", ProviderType::IonQ), "IonQ");
1031 assert_eq!(format!("{}", ProviderType::Rigetti), "Rigetti");
1032 assert_eq!(format!("{}", ProviderType::AmazonBraket), "Amazon Braket");
1033 assert_eq!(
1034 format!("{}", ProviderType::LocalSimulator),
1035 "Local Simulator"
1036 );
1037 }
1038
1039 #[test]
1040 fn provider_type_equality() {
1041 assert_eq!(ProviderType::IbmQuantum, ProviderType::IbmQuantum);
1042 assert_ne!(ProviderType::IbmQuantum, ProviderType::IonQ);
1043 }
1044
1045 #[test]
1048 fn device_status_display() {
1049 assert_eq!(format!("{}", DeviceStatus::Online), "online");
1050 assert_eq!(format!("{}", DeviceStatus::Offline), "offline");
1051 assert_eq!(format!("{}", DeviceStatus::Maintenance), "maintenance");
1052 assert_eq!(format!("{}", DeviceStatus::Retired), "retired");
1053 }
1054
1055 #[test]
1058 fn job_status_variants() {
1059 let queued = JobStatus::Queued;
1060 let running = JobStatus::Running;
1061 let completed = JobStatus::Completed;
1062 let failed = JobStatus::Failed("timeout".to_string());
1063 let cancelled = JobStatus::Cancelled;
1064
1065 assert_eq!(queued, JobStatus::Queued);
1066 assert_eq!(running, JobStatus::Running);
1067 assert_eq!(completed, JobStatus::Completed);
1068 assert_eq!(failed, JobStatus::Failed("timeout".to_string()));
1069 assert_eq!(cancelled, JobStatus::Cancelled);
1070 }
1071
1072 #[test]
1075 fn hardware_error_display() {
1076 let e = HardwareError::AuthenticationFailed("no token".into());
1077 assert!(format!("{}", e).contains("authentication failed"));
1078
1079 let e = HardwareError::DeviceNotFound("foo".into());
1080 assert!(format!("{}", e).contains("device not found"));
1081
1082 let e = HardwareError::DeviceOffline("bar".into());
1083 assert!(format!("{}", e).contains("device offline"));
1084
1085 let e = HardwareError::CircuitTooLarge {
1086 qubits: 50,
1087 max: 32,
1088 };
1089 let msg = format!("{}", e);
1090 assert!(msg.contains("50"));
1091 assert!(msg.contains("32"));
1092
1093 let e = HardwareError::JobFailed("oops".into());
1094 assert!(format!("{}", e).contains("job failed"));
1095
1096 let e = HardwareError::NetworkError("timeout".into());
1097 assert!(format!("{}", e).contains("network error"));
1098
1099 let e = HardwareError::RateLimited {
1100 retry_after_ms: 5000,
1101 };
1102 assert!(format!("{}", e).contains("5000"));
1103 }
1104
1105 #[test]
1106 fn hardware_error_is_error_trait() {
1107 let e: Box<dyn std::error::Error> =
1108 Box::new(HardwareError::NetworkError("test".into()));
1109 assert!(e.to_string().contains("network error"));
1110 }
1111
1112 #[test]
1115 fn device_info_construction() {
1116 let dev = DeviceInfo {
1117 name: "test_device".into(),
1118 provider: ProviderType::LocalSimulator,
1119 num_qubits: 5,
1120 basis_gates: vec!["h".into(), "cx".into()],
1121 coupling_map: vec![(0, 1), (1, 2)],
1122 max_shots: 1000,
1123 status: DeviceStatus::Online,
1124 };
1125 assert_eq!(dev.name, "test_device");
1126 assert_eq!(dev.num_qubits, 5);
1127 assert_eq!(dev.basis_gates.len(), 2);
1128 assert_eq!(dev.coupling_map.len(), 2);
1129 assert_eq!(dev.status, DeviceStatus::Online);
1130 }
1131
1132 #[test]
1135 fn job_handle_construction() {
1136 let handle = JobHandle {
1137 job_id: "abc-123".into(),
1138 provider: ProviderType::IonQ,
1139 submitted_at: 1700000000,
1140 };
1141 assert_eq!(handle.job_id, "abc-123");
1142 assert_eq!(handle.provider, ProviderType::IonQ);
1143 assert_eq!(handle.submitted_at, 1700000000);
1144 }
1145
1146 #[test]
1149 fn hardware_result_construction() {
1150 let mut counts = HashMap::new();
1151 counts.insert(vec![false, false], 500);
1152 counts.insert(vec![true, true], 500);
1153 let result = HardwareResult {
1154 counts,
1155 shots: 1000,
1156 execution_time_ms: 42,
1157 device_name: "test".into(),
1158 };
1159 assert_eq!(result.shots, 1000);
1160 assert_eq!(result.counts.len(), 2);
1161 assert_eq!(result.execution_time_ms, 42);
1162 }
1163
1164 #[test]
1167 fn device_calibration_construction() {
1168 let cal = DeviceCalibration {
1169 device_name: "dev".into(),
1170 timestamp: 1700000000,
1171 qubit_t1: vec![100.0, 110.0],
1172 qubit_t2: vec![80.0, 85.0],
1173 readout_error: vec![(0.01, 0.02), (0.015, 0.025)],
1174 gate_errors: HashMap::new(),
1175 gate_times: HashMap::new(),
1176 coupling_map: vec![(0, 1)],
1177 };
1178 assert_eq!(cal.qubit_t1.len(), 2);
1179 assert_eq!(cal.qubit_t2.len(), 2);
1180 assert_eq!(cal.readout_error.len(), 2);
1181 }
1182
1183 #[test]
1186 fn parse_qubit_count_openqasm2() {
1187 let qasm = "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[5];\ncreg c[5];\nh q[0];\n";
1188 assert_eq!(parse_qubit_count(qasm, 1), 5);
1189 }
1190
1191 #[test]
1192 fn parse_qubit_count_openqasm3() {
1193 let qasm = "OPENQASM 3.0;\nqubit[8] q;\nbit[8] c;\n";
1194 assert_eq!(parse_qubit_count(qasm, 1), 8);
1195 }
1196
1197 #[test]
1198 fn parse_qubit_count_multiple_registers() {
1199 let qasm = "qreg a[3];\nqreg b[4];\n";
1200 assert_eq!(parse_qubit_count(qasm, 1), 7);
1201 }
1202
1203 #[test]
1204 fn parse_qubit_count_fallback() {
1205 let qasm = "h q[0];\ncx q[0], q[1];\n";
1206 assert_eq!(parse_qubit_count(qasm, 2), 2);
1207 }
1208
1209 #[test]
1210 fn parse_gate_count_basic() {
1211 let qasm =
1212 "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";
1213 assert_eq!(parse_gate_count(qasm), 3);
1214 }
1215
1216 #[test]
1217 fn parse_gate_count_empty() {
1218 let qasm = "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\n";
1219 assert_eq!(parse_gate_count(qasm), 0);
1220 }
1221
1222 #[test]
1225 fn synthetic_calibration_correct_sizes() {
1226 let coupling = vec![(0, 1), (1, 0), (1, 2), (2, 1)];
1227 let cal = synthetic_calibration("test", 3, &coupling);
1228 assert_eq!(cal.device_name, "test");
1229 assert_eq!(cal.qubit_t1.len(), 3);
1230 assert_eq!(cal.qubit_t2.len(), 3);
1231 assert_eq!(cal.readout_error.len(), 3);
1232 assert_eq!(cal.coupling_map.len(), 4);
1233 assert!(cal.gate_errors.len() >= 9);
1236 assert!(cal.gate_times.len() >= 9);
1237 }
1238
1239 #[test]
1240 fn synthetic_calibration_values_positive() {
1241 let cal = synthetic_calibration("dev", 5, &[(0, 1)]);
1242 for t1 in &cal.qubit_t1 {
1243 assert!(*t1 > 0.0, "T1 must be positive");
1244 }
1245 for t2 in &cal.qubit_t2 {
1246 assert!(*t2 > 0.0, "T2 must be positive");
1247 }
1248 for &(p0, p1) in &cal.readout_error {
1249 assert!(p0 >= 0.0 && p0 <= 1.0);
1250 assert!(p1 >= 0.0 && p1 <= 1.0);
1251 }
1252 }
1253
1254 #[test]
1257 fn linear_coupling_map_correct() {
1258 let map = linear_coupling_map(4);
1259 assert_eq!(map.len(), 6);
1261 assert!(map.contains(&(0, 1)));
1262 assert!(map.contains(&(1, 0)));
1263 assert!(map.contains(&(2, 3)));
1264 assert!(map.contains(&(3, 2)));
1265 }
1266
1267 #[test]
1268 fn linear_coupling_map_single_qubit() {
1269 let map = linear_coupling_map(1);
1270 assert!(map.is_empty());
1271 }
1272
1273 #[test]
1274 fn heavy_hex_coupling_map_has_cross_links() {
1275 let map = heavy_hex_coupling_map(20);
1276 assert!(map.len() > linear_coupling_map(20).len());
1278 assert!(map.contains(&(0, 4)));
1280 assert!(map.contains(&(4, 0)));
1281 }
1282
1283 #[test]
1286 fn local_provider_name_and_type() {
1287 let prov = LocalSimulatorProvider;
1288 assert_eq!(prov.name(), "Local Simulator");
1289 assert_eq!(prov.provider_type(), ProviderType::LocalSimulator);
1290 }
1291
1292 #[test]
1293 fn local_provider_devices() {
1294 let prov = LocalSimulatorProvider;
1295 let devs = prov.available_devices();
1296 assert_eq!(devs.len(), 1);
1297 assert_eq!(devs[0].name, "local_statevector_simulator");
1298 assert_eq!(devs[0].num_qubits, 32);
1299 assert_eq!(devs[0].status, DeviceStatus::Online);
1300 assert!(devs[0].basis_gates.contains(&"h".to_string()));
1301 assert!(devs[0].basis_gates.contains(&"cx".to_string()));
1302 }
1303
1304 #[test]
1305 fn local_provider_calibration() {
1306 let prov = LocalSimulatorProvider;
1307 let cal = prov
1308 .device_calibration("local_statevector_simulator")
1309 .expect("calibration should exist");
1310 assert_eq!(cal.device_name, "local_statevector_simulator");
1311 assert_eq!(cal.qubit_t1.len(), 32);
1312 for &(p0, p1) in &cal.readout_error {
1314 assert!((p0 - 0.0).abs() < 1e-12);
1315 assert!((p1 - 0.0).abs() < 1e-12);
1316 }
1317 for val in cal.gate_errors.values() {
1318 assert!((*val - 0.0).abs() < 1e-12);
1319 }
1320 }
1321
1322 #[test]
1323 fn local_provider_calibration_unknown_device() {
1324 let prov = LocalSimulatorProvider;
1325 assert!(prov.device_calibration("nonexistent").is_none());
1326 }
1327
1328 #[test]
1329 fn local_provider_submit_and_retrieve() {
1330 let prov = LocalSimulatorProvider;
1331 let qasm = "OPENQASM 2.0;\nqreg q[2];\nh q[0];\ncx q[0], q[1];\n";
1332 let handle = prov
1333 .submit_circuit(qasm, 100, "local_statevector_simulator")
1334 .expect("submit should succeed");
1335
1336 assert_eq!(handle.provider, ProviderType::LocalSimulator);
1337 assert!(handle.job_id.starts_with("local-"));
1338
1339 let status = prov.job_status(&handle).expect("status should succeed");
1341 assert_eq!(status, JobStatus::Completed);
1342
1343 let result = prov.job_results(&handle).expect("results should succeed");
1345 assert_eq!(result.device_name, "local_statevector_simulator");
1346 let total: usize = result.counts.values().sum();
1348 assert_eq!(total, 100);
1349 assert_eq!(result.shots, 100);
1350 }
1351
1352 #[test]
1353 fn local_provider_submit_wrong_device() {
1354 let prov = LocalSimulatorProvider;
1355 let result = prov.submit_circuit("qreg q[2];", 10, "wrong_device");
1356 assert!(result.is_err());
1357 match result.unwrap_err() {
1358 HardwareError::DeviceNotFound(name) => assert_eq!(name, "wrong_device"),
1359 other => panic!("expected DeviceNotFound, got: {:?}", other),
1360 }
1361 }
1362
1363 #[test]
1364 fn local_provider_circuit_too_large() {
1365 let prov = LocalSimulatorProvider;
1366 let qasm = "OPENQASM 2.0;\nqreg q[50];\n";
1367 let result = prov.submit_circuit(qasm, 10, "local_statevector_simulator");
1368 assert!(result.is_err());
1369 match result.unwrap_err() {
1370 HardwareError::CircuitTooLarge { qubits, max } => {
1371 assert_eq!(qubits, 50);
1372 assert_eq!(max, 32);
1373 }
1374 other => panic!("expected CircuitTooLarge, got: {:?}", other),
1375 }
1376 }
1377
1378 #[test]
1379 fn local_provider_unknown_job() {
1380 let prov = LocalSimulatorProvider;
1381 let handle = JobHandle {
1382 job_id: "nonexistent".into(),
1383 provider: ProviderType::LocalSimulator,
1384 submitted_at: 0,
1385 };
1386 assert!(prov.job_status(&handle).is_err());
1387 assert!(prov.job_results(&handle).is_err());
1388 }
1389
1390 #[test]
1391 fn local_provider_wrong_provider_handle() {
1392 let prov = LocalSimulatorProvider;
1393 let handle = JobHandle {
1394 job_id: "some-id".into(),
1395 provider: ProviderType::IbmQuantum,
1396 submitted_at: 0,
1397 };
1398 assert!(prov.job_status(&handle).is_err());
1399 assert!(prov.job_results(&handle).is_err());
1400 }
1401
1402 #[test]
1405 fn ibm_provider_name_and_type() {
1406 let prov = IbmQuantumProvider;
1407 assert_eq!(prov.name(), "IBM Quantum");
1408 assert_eq!(prov.provider_type(), ProviderType::IbmQuantum);
1409 }
1410
1411 #[test]
1412 fn ibm_provider_devices() {
1413 let prov = IbmQuantumProvider;
1414 let devs = prov.available_devices();
1415 assert_eq!(devs.len(), 2);
1416
1417 let brisbane = devs.iter().find(|d| d.name == "ibm_brisbane").unwrap();
1418 assert_eq!(brisbane.num_qubits, 127);
1419 assert_eq!(brisbane.provider, ProviderType::IbmQuantum);
1420 assert_eq!(brisbane.status, DeviceStatus::Online);
1421
1422 let fez = devs.iter().find(|d| d.name == "ibm_fez").unwrap();
1423 assert_eq!(fez.num_qubits, 133);
1424 }
1425
1426 #[test]
1427 fn ibm_provider_calibration() {
1428 let prov = IbmQuantumProvider;
1429 let cal = prov
1430 .device_calibration("ibm_brisbane")
1431 .expect("calibration should exist");
1432 assert_eq!(cal.qubit_t1.len(), 127);
1433 assert_eq!(cal.qubit_t2.len(), 127);
1434 assert_eq!(cal.readout_error.len(), 127);
1435 }
1436
1437 #[test]
1438 fn ibm_provider_calibration_unknown_device() {
1439 let prov = IbmQuantumProvider;
1440 assert!(prov.device_calibration("nonexistent").is_none());
1441 }
1442
1443 #[test]
1444 fn ibm_provider_submit_fails_auth() {
1445 let prov = IbmQuantumProvider;
1446 let result = prov.submit_circuit("qreg q[2];", 100, "ibm_brisbane");
1447 assert!(result.is_err());
1448 match result.unwrap_err() {
1449 HardwareError::AuthenticationFailed(msg) => {
1450 assert!(msg.contains("IBM Quantum"));
1451 }
1452 other => panic!("expected AuthenticationFailed, got: {:?}", other),
1453 }
1454 }
1455
1456 #[test]
1457 fn ibm_provider_job_status_fails_auth() {
1458 let prov = IbmQuantumProvider;
1459 let handle = JobHandle {
1460 job_id: "x".into(),
1461 provider: ProviderType::IbmQuantum,
1462 submitted_at: 0,
1463 };
1464 assert!(prov.job_status(&handle).is_err());
1465 assert!(prov.job_results(&handle).is_err());
1466 }
1467
1468 #[test]
1471 fn ionq_provider_name_and_type() {
1472 let prov = IonQProvider;
1473 assert_eq!(prov.name(), "IonQ");
1474 assert_eq!(prov.provider_type(), ProviderType::IonQ);
1475 }
1476
1477 #[test]
1478 fn ionq_provider_devices() {
1479 let prov = IonQProvider;
1480 let devs = prov.available_devices();
1481 assert_eq!(devs.len(), 2);
1482
1483 let aria = devs.iter().find(|d| d.name == "ionq_aria").unwrap();
1484 assert_eq!(aria.num_qubits, 25);
1485 assert_eq!(aria.coupling_map.len(), 25 * 24);
1487
1488 let forte = devs.iter().find(|d| d.name == "ionq_forte").unwrap();
1489 assert_eq!(forte.num_qubits, 36);
1490 }
1491
1492 #[test]
1493 fn ionq_provider_calibration_aria() {
1494 let prov = IonQProvider;
1495 let cal = prov
1496 .device_calibration("ionq_aria")
1497 .expect("calibration should exist");
1498 assert_eq!(cal.qubit_t1.len(), 25);
1499 for t1 in &cal.qubit_t1 {
1501 assert!(*t1 > 1_000_000.0);
1502 }
1503 }
1504
1505 #[test]
1506 fn ionq_provider_calibration_forte() {
1507 let prov = IonQProvider;
1508 let cal = prov
1509 .device_calibration("ionq_forte")
1510 .expect("calibration should exist");
1511 assert_eq!(cal.qubit_t1.len(), 36);
1512 }
1513
1514 #[test]
1515 fn ionq_provider_calibration_unknown() {
1516 let prov = IonQProvider;
1517 assert!(prov.device_calibration("nonexistent").is_none());
1518 }
1519
1520 #[test]
1521 fn ionq_provider_submit_fails_auth() {
1522 let prov = IonQProvider;
1523 let result = prov.submit_circuit("qreg q[2];", 100, "ionq_aria");
1524 assert!(result.is_err());
1525 match result.unwrap_err() {
1526 HardwareError::AuthenticationFailed(msg) => {
1527 assert!(msg.contains("IonQ"));
1528 }
1529 other => panic!("expected AuthenticationFailed, got: {:?}", other),
1530 }
1531 }
1532
1533 #[test]
1536 fn rigetti_provider_name_and_type() {
1537 let prov = RigettiProvider;
1538 assert_eq!(prov.name(), "Rigetti");
1539 assert_eq!(prov.provider_type(), ProviderType::Rigetti);
1540 }
1541
1542 #[test]
1543 fn rigetti_provider_devices() {
1544 let prov = RigettiProvider;
1545 let devs = prov.available_devices();
1546 assert_eq!(devs.len(), 1);
1547 assert_eq!(devs[0].name, "rigetti_ankaa_2");
1548 assert_eq!(devs[0].num_qubits, 84);
1549 }
1550
1551 #[test]
1552 fn rigetti_provider_calibration() {
1553 let prov = RigettiProvider;
1554 let cal = prov
1555 .device_calibration("rigetti_ankaa_2")
1556 .expect("calibration should exist");
1557 assert_eq!(cal.qubit_t1.len(), 84);
1558 assert_eq!(cal.qubit_t2.len(), 84);
1559 }
1560
1561 #[test]
1562 fn rigetti_provider_calibration_unknown() {
1563 let prov = RigettiProvider;
1564 assert!(prov.device_calibration("nonexistent").is_none());
1565 }
1566
1567 #[test]
1568 fn rigetti_provider_submit_fails_auth() {
1569 let prov = RigettiProvider;
1570 let result = prov.submit_circuit("qreg q[2];", 100, "rigetti_ankaa_2");
1571 assert!(result.is_err());
1572 match result.unwrap_err() {
1573 HardwareError::AuthenticationFailed(msg) => {
1574 assert!(msg.contains("Rigetti"));
1575 }
1576 other => panic!("expected AuthenticationFailed, got: {:?}", other),
1577 }
1578 }
1579
1580 #[test]
1583 fn braket_provider_name_and_type() {
1584 let prov = AmazonBraketProvider;
1585 assert_eq!(prov.name(), "Amazon Braket");
1586 assert_eq!(prov.provider_type(), ProviderType::AmazonBraket);
1587 }
1588
1589 #[test]
1590 fn braket_provider_devices() {
1591 let prov = AmazonBraketProvider;
1592 let devs = prov.available_devices();
1593 assert_eq!(devs.len(), 2);
1594
1595 let harmony = devs
1596 .iter()
1597 .find(|d| d.name == "braket_ionq_harmony")
1598 .unwrap();
1599 assert_eq!(harmony.num_qubits, 11);
1600
1601 let aspen = devs
1602 .iter()
1603 .find(|d| d.name == "braket_rigetti_aspen_m3")
1604 .unwrap();
1605 assert_eq!(aspen.num_qubits, 79);
1606 }
1607
1608 #[test]
1609 fn braket_provider_calibration() {
1610 let prov = AmazonBraketProvider;
1611 let cal = prov
1612 .device_calibration("braket_ionq_harmony")
1613 .expect("calibration should exist");
1614 assert_eq!(cal.qubit_t1.len(), 11);
1615
1616 let cal2 = prov
1617 .device_calibration("braket_rigetti_aspen_m3")
1618 .expect("calibration should exist");
1619 assert_eq!(cal2.qubit_t1.len(), 79);
1620 }
1621
1622 #[test]
1623 fn braket_provider_calibration_unknown() {
1624 let prov = AmazonBraketProvider;
1625 assert!(prov.device_calibration("nonexistent").is_none());
1626 }
1627
1628 #[test]
1629 fn braket_provider_submit_fails_auth() {
1630 let prov = AmazonBraketProvider;
1631 let result = prov.submit_circuit("qreg q[2];", 100, "braket_ionq_harmony");
1632 assert!(result.is_err());
1633 match result.unwrap_err() {
1634 HardwareError::AuthenticationFailed(msg) => {
1635 assert!(msg.contains("AWS"));
1636 }
1637 other => panic!("expected AuthenticationFailed, got: {:?}", other),
1638 }
1639 }
1640
1641 #[test]
1644 fn registry_new_is_empty() {
1645 let reg = ProviderRegistry::new();
1646 assert!(reg.all_devices().is_empty());
1647 assert!(reg.get(ProviderType::LocalSimulator).is_none());
1648 }
1649
1650 #[test]
1651 fn registry_default_has_local_simulator() {
1652 let reg = ProviderRegistry::default();
1653 let local = reg.get(ProviderType::LocalSimulator);
1654 assert!(local.is_some());
1655 assert_eq!(local.unwrap().name(), "Local Simulator");
1656 }
1657
1658 #[test]
1659 fn registry_default_devices() {
1660 let reg = ProviderRegistry::default();
1661 let devs = reg.all_devices();
1662 assert_eq!(devs.len(), 1);
1663 assert_eq!(devs[0].name, "local_statevector_simulator");
1664 }
1665
1666 #[test]
1667 fn registry_register_multiple() {
1668 let mut reg = ProviderRegistry::new();
1669 reg.register(Box::new(LocalSimulatorProvider));
1670 reg.register(Box::new(IbmQuantumProvider));
1671 reg.register(Box::new(IonQProvider));
1672 reg.register(Box::new(RigettiProvider));
1673 reg.register(Box::new(AmazonBraketProvider));
1674
1675 assert!(reg.get(ProviderType::LocalSimulator).is_some());
1677 assert!(reg.get(ProviderType::IbmQuantum).is_some());
1678 assert!(reg.get(ProviderType::IonQ).is_some());
1679 assert!(reg.get(ProviderType::Rigetti).is_some());
1680 assert!(reg.get(ProviderType::AmazonBraket).is_some());
1681
1682 assert_eq!(reg.all_devices().len(), 8);
1684 }
1685
1686 #[test]
1687 fn registry_get_nonexistent() {
1688 let reg = ProviderRegistry::default();
1689 assert!(reg.get(ProviderType::IbmQuantum).is_none());
1690 }
1691
1692 #[test]
1693 fn registry_all_devices_aggregates() {
1694 let mut reg = ProviderRegistry::new();
1695 reg.register(Box::new(IbmQuantumProvider));
1696 reg.register(Box::new(IonQProvider));
1697
1698 let devs = reg.all_devices();
1699 assert_eq!(devs.len(), 4);
1701 let names: Vec<&str> = devs.iter().map(|d| d.name.as_str()).collect();
1702 assert!(names.contains(&"ibm_brisbane"));
1703 assert!(names.contains(&"ibm_fez"));
1704 assert!(names.contains(&"ionq_aria"));
1705 assert!(names.contains(&"ionq_forte"));
1706 }
1707
1708 #[test]
1711 fn registry_local_submit_integration() {
1712 let reg = ProviderRegistry::default();
1713 let local = reg.get(ProviderType::LocalSimulator).unwrap();
1714 let qasm = "OPENQASM 2.0;\nqreg q[2];\n";
1715 let handle = local
1716 .submit_circuit(qasm, 50, "local_statevector_simulator")
1717 .expect("submit should succeed");
1718 let status = local.job_status(&handle).expect("status should succeed");
1719 assert_eq!(status, JobStatus::Completed);
1720 let result = local.job_results(&handle).expect("results should succeed");
1721 let total: usize = result.counts.values().sum();
1722 assert_eq!(total, 50);
1723 }
1724
1725 #[test]
1726 fn registry_stub_submit_through_registry() {
1727 let mut reg = ProviderRegistry::new();
1728 reg.register(Box::new(IbmQuantumProvider));
1729 let ibm = reg.get(ProviderType::IbmQuantum).unwrap();
1730 let result = ibm.submit_circuit("qreg q[2];", 100, "ibm_brisbane");
1731 assert!(result.is_err());
1732 }
1733
1734 #[test]
1737 fn provider_trait_is_object_safe() {
1738 let providers: Vec<Box<dyn HardwareProvider>> = vec![
1740 Box::new(LocalSimulatorProvider),
1741 Box::new(IbmQuantumProvider),
1742 Box::new(IonQProvider),
1743 Box::new(RigettiProvider),
1744 Box::new(AmazonBraketProvider),
1745 ];
1746 assert_eq!(providers.len(), 5);
1747 for p in &providers {
1748 assert!(!p.name().is_empty());
1749 assert!(!p.available_devices().is_empty());
1750 }
1751 }
1752
1753 #[test]
1756 fn providers_are_send_sync() {
1757 fn assert_send_sync<T: Send + Sync>() {}
1758 assert_send_sync::<LocalSimulatorProvider>();
1759 assert_send_sync::<IbmQuantumProvider>();
1760 assert_send_sync::<IonQProvider>();
1761 assert_send_sync::<RigettiProvider>();
1762 assert_send_sync::<AmazonBraketProvider>();
1763 }
1764}