1use crate::{
23 error::{QuantRS2Error, QuantRS2Result},
24 gate::GateOp,
25 qubit::QubitId,
26};
27use scirs2_core::ndarray::{Array1, Array2};
28use scirs2_core::Complex64 as Complex;
29use std::collections::HashMap;
30use std::time::{Duration, SystemTime};
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum CloudPlatform {
39 IBM,
41 AWS,
43 Google,
45 Azure,
47 Rigetti,
49 IonQ,
51}
52
53impl CloudPlatform {
54 pub const fn name(&self) -> &'static str {
56 match self {
57 Self::IBM => "IBM Quantum",
58 Self::AWS => "AWS Braket",
59 Self::Google => "Google Quantum AI",
60 Self::Azure => "Azure Quantum",
61 Self::Rigetti => "Rigetti QCS",
62 Self::IonQ => "IonQ Cloud",
63 }
64 }
65
66 pub const fn endpoint(&self) -> &'static str {
68 match self {
69 Self::IBM => "https://auth.quantum-computing.ibm.com/api",
70 Self::AWS => "https://braket.us-east-1.amazonaws.com",
71 Self::Google => "https://quantumengine.googleapis.com",
72 Self::Azure => "https://quantum.azure.com",
73 Self::Rigetti => "https://api.rigetti.com",
74 Self::IonQ => "https://api.ionq.com",
75 }
76 }
77
78 pub const fn supports_qubits(&self, num_qubits: usize) -> bool {
80 match self {
81 Self::IBM => num_qubits <= 127, Self::AWS => num_qubits <= 34, Self::Google => num_qubits <= 72, Self::Azure => num_qubits <= 40, Self::Rigetti => num_qubits <= 80, Self::IonQ => num_qubits <= 32, }
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum DeviceType {
94 QPU,
96 Simulator,
98 TensorNetworkSimulator,
100 NoisySimulator,
102}
103
104#[derive(Debug, Clone)]
106pub struct DeviceInfo {
107 pub platform: CloudPlatform,
109 pub name: String,
111 pub device_type: DeviceType,
113 pub num_qubits: usize,
115 pub connectivity: Vec<(usize, usize)>,
117 pub gate_set: Vec<String>,
119 pub gate_fidelities: HashMap<String, f64>,
121 pub t1_times: Vec<f64>,
123 pub t2_times: Vec<f64>,
125 pub readout_fidelity: Vec<f64>,
127 pub is_available: bool,
129 pub queue_depth: usize,
131 pub cost_per_shot: f64,
133}
134
135impl DeviceInfo {
136 pub fn avg_single_qubit_fidelity(&self) -> f64 {
138 let single_qubit_gates = vec!["X", "Y", "Z", "H", "RX", "RY", "RZ"];
139 let mut sum = 0.0;
140 let mut count = 0;
141
142 for gate in single_qubit_gates {
143 if let Some(&fidelity) = self.gate_fidelities.get(gate) {
144 sum += fidelity;
145 count += 1;
146 }
147 }
148
149 if count > 0 {
150 sum / count as f64
151 } else {
152 0.99 }
154 }
155
156 pub fn avg_two_qubit_fidelity(&self) -> f64 {
158 let two_qubit_gates = vec!["CNOT", "CZ", "SWAP", "iSWAP"];
159 let mut sum = 0.0;
160 let mut count = 0;
161
162 for gate in two_qubit_gates {
163 if let Some(&fidelity) = self.gate_fidelities.get(gate) {
164 sum += fidelity;
165 count += 1;
166 }
167 }
168
169 if count > 0 {
170 sum / count as f64
171 } else {
172 0.95 }
174 }
175
176 pub fn quality_score(&self) -> f64 {
178 let gate_score = f64::midpoint(
179 self.avg_single_qubit_fidelity(),
180 self.avg_two_qubit_fidelity(),
181 );
182 let readout_score =
183 self.readout_fidelity.iter().sum::<f64>() / self.readout_fidelity.len() as f64;
184 let availability_score = if self.is_available { 1.0 } else { 0.5 };
185 let queue_score = 1.0 / (1.0 + self.queue_depth as f64 / 10.0);
186
187 gate_score.mul_add(0.4, readout_score * 0.3) + availability_score * 0.2 + queue_score * 0.1
188 }
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum JobStatus {
198 Queued,
200 Running,
202 Completed,
204 Failed,
206 Cancelled,
208}
209
210#[derive(Debug, Clone)]
212pub struct QuantumJob {
213 pub job_id: String,
215 pub platform: CloudPlatform,
217 pub device_name: String,
219 pub status: JobStatus,
221 pub shots: usize,
223 pub submitted_at: SystemTime,
225 pub completed_at: Option<SystemTime>,
227 pub result: Option<JobResult>,
229 pub error_message: Option<String>,
231 pub estimated_cost: f64,
233}
234
235impl QuantumJob {
236 pub fn execution_time(&self) -> Option<Duration> {
238 self.completed_at
239 .and_then(|completed| completed.duration_since(self.submitted_at).ok())
240 }
241
242 pub const fn is_finished(&self) -> bool {
244 matches!(
245 self.status,
246 JobStatus::Completed | JobStatus::Failed | JobStatus::Cancelled
247 )
248 }
249}
250
251#[derive(Debug, Clone)]
253pub struct JobResult {
254 pub counts: HashMap<String, usize>,
256 pub expectation_values: Option<Vec<f64>>,
258 pub state_vector: Option<Array1<Complex>>,
260 pub density_matrix: Option<Array2<Complex>>,
262 pub raw_data: Vec<Vec<usize>>,
264 pub metadata: HashMap<String, String>,
266}
267
268impl JobResult {
269 pub fn probabilities(&self) -> HashMap<String, f64> {
271 let total: usize = self.counts.values().sum();
272 self.counts
273 .iter()
274 .map(|(k, v)| (k.clone(), *v as f64 / total as f64))
275 .collect()
276 }
277
278 pub fn most_probable_outcome(&self) -> Option<String> {
280 self.counts
281 .iter()
282 .max_by_key(|(_, count)| *count)
283 .map(|(outcome, _)| outcome.clone())
284 }
285
286 pub fn entropy(&self) -> f64 {
288 let probs = self.probabilities();
289 -probs
290 .values()
291 .filter(|&&p| p > 0.0)
292 .map(|&p| p * p.log2())
293 .sum::<f64>()
294 }
295}
296
297#[derive(Debug, Clone)]
303pub struct CloudConfig {
304 pub platform: CloudPlatform,
306 pub api_token: String,
308 pub endpoint: Option<String>,
310 pub default_shots: usize,
312 pub timeout: u64,
314 pub auto_optimize: bool,
316 pub max_qubits: Option<usize>,
318}
319
320impl Default for CloudConfig {
321 fn default() -> Self {
322 Self {
323 platform: CloudPlatform::IBM,
324 api_token: String::new(),
325 endpoint: None,
326 default_shots: 1000,
327 timeout: 300,
328 auto_optimize: true,
329 max_qubits: None,
330 }
331 }
332}
333
334pub struct CloudClient {
336 config: CloudConfig,
337 devices: Vec<DeviceInfo>,
338}
339
340impl CloudClient {
341 pub const fn new(config: CloudConfig) -> Self {
343 Self {
344 config,
345 devices: Vec::new(),
346 }
347 }
348
349 pub fn connect(&mut self) -> QuantRS2Result<()> {
351 if self.config.api_token.is_empty() {
353 return Err(QuantRS2Error::InvalidInput(
354 "API token is required".to_string(),
355 ));
356 }
357
358 self.devices = self.load_devices()?;
360
361 Ok(())
362 }
363
364 fn load_devices(&self) -> QuantRS2Result<Vec<DeviceInfo>> {
366 match self.config.platform {
368 CloudPlatform::IBM => Ok(vec![
369 DeviceInfo {
370 platform: CloudPlatform::IBM,
371 name: "ibmq_jakarta".to_string(),
372 device_type: DeviceType::QPU,
373 num_qubits: 7,
374 connectivity: vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)],
375 gate_set: vec!["X", "Y", "Z", "H", "CNOT", "RZ"]
376 .iter()
377 .map(|s| s.to_string())
378 .collect(),
379 gate_fidelities: HashMap::from([
380 ("X".to_string(), 0.9993),
381 ("CNOT".to_string(), 0.987),
382 ]),
383 t1_times: vec![100.0, 95.0, 110.0, 98.0, 105.0, 92.0, 88.0],
384 t2_times: vec![120.0, 110.0, 115.0, 108.0, 125.0, 105.0, 98.0],
385 readout_fidelity: vec![0.98, 0.97, 0.98, 0.96, 0.97, 0.98, 0.97],
386 is_available: true,
387 queue_depth: 5,
388 cost_per_shot: 0.001,
389 },
390 DeviceInfo {
391 platform: CloudPlatform::IBM,
392 name: "ibmq_qasm_simulator".to_string(),
393 device_type: DeviceType::Simulator,
394 num_qubits: 32,
395 connectivity: vec![], gate_set: vec!["X", "Y", "Z", "H", "CNOT", "RX", "RY", "RZ"]
397 .iter()
398 .map(|s| s.to_string())
399 .collect(),
400 gate_fidelities: HashMap::from([
401 ("X".to_string(), 1.0),
402 ("CNOT".to_string(), 1.0),
403 ]),
404 t1_times: vec![],
405 t2_times: vec![],
406 readout_fidelity: vec![],
407 is_available: true,
408 queue_depth: 0,
409 cost_per_shot: 0.0,
410 },
411 ]),
412 CloudPlatform::AWS => Ok(vec![DeviceInfo {
413 platform: CloudPlatform::AWS,
414 name: "SV1".to_string(),
415 device_type: DeviceType::Simulator,
416 num_qubits: 34,
417 connectivity: vec![],
418 gate_set: vec!["X", "Y", "Z", "H", "CNOT", "RX", "RY", "RZ", "CZ"]
419 .iter()
420 .map(|s| s.to_string())
421 .collect(),
422 gate_fidelities: HashMap::from([("X".to_string(), 1.0), ("CNOT".to_string(), 1.0)]),
423 t1_times: vec![],
424 t2_times: vec![],
425 readout_fidelity: vec![],
426 is_available: true,
427 queue_depth: 0,
428 cost_per_shot: 0.00075,
429 }]),
430 CloudPlatform::Google => Ok(vec![DeviceInfo {
431 platform: CloudPlatform::Google,
432 name: "rainbow".to_string(),
433 device_type: DeviceType::QPU,
434 num_qubits: 23,
435 connectivity: vec![(0, 1), (1, 2), (2, 3)], gate_set: vec!["X", "Y", "Z", "PhasedXZ", "CZ", "SQRT_ISWAP"]
437 .iter()
438 .map(|s| s.to_string())
439 .collect(),
440 gate_fidelities: HashMap::from([
441 ("X".to_string(), 0.9995),
442 ("CZ".to_string(), 0.993),
443 ]),
444 t1_times: vec![15.0; 23],
445 t2_times: vec![20.0; 23],
446 readout_fidelity: vec![0.96; 23],
447 is_available: true,
448 queue_depth: 3,
449 cost_per_shot: 0.002,
450 }]),
451 CloudPlatform::Azure => Ok(vec![DeviceInfo {
452 platform: CloudPlatform::Azure,
453 name: "azure-simulator".to_string(),
454 device_type: DeviceType::Simulator,
455 num_qubits: 40,
456 connectivity: vec![],
457 gate_set: vec!["X", "Y", "Z", "H", "CNOT", "T"]
458 .iter()
459 .map(|s| s.to_string())
460 .collect(),
461 gate_fidelities: HashMap::from([("X".to_string(), 1.0), ("CNOT".to_string(), 1.0)]),
462 t1_times: vec![],
463 t2_times: vec![],
464 readout_fidelity: vec![],
465 is_available: true,
466 queue_depth: 0,
467 cost_per_shot: 0.0005,
468 }]),
469 CloudPlatform::Rigetti => Ok(vec![DeviceInfo {
470 platform: CloudPlatform::Rigetti,
471 name: "Aspen-M-3".to_string(),
472 device_type: DeviceType::QPU,
473 num_qubits: 80,
474 connectivity: vec![(0, 1), (1, 2)], gate_set: vec!["RX", "RZ", "CZ", "XY"]
476 .iter()
477 .map(|s| s.to_string())
478 .collect(),
479 gate_fidelities: HashMap::from([
480 ("RX".to_string(), 0.998),
481 ("CZ".to_string(), 0.95),
482 ]),
483 t1_times: vec![20.0; 80],
484 t2_times: vec![15.0; 80],
485 readout_fidelity: vec![0.95; 80],
486 is_available: true,
487 queue_depth: 8,
488 cost_per_shot: 0.0015,
489 }]),
490 CloudPlatform::IonQ => Ok(vec![DeviceInfo {
491 platform: CloudPlatform::IonQ,
492 name: "ionq.qpu.aria-1".to_string(),
493 device_type: DeviceType::QPU,
494 num_qubits: 25,
495 connectivity: vec![], gate_set: vec!["X", "Y", "Z", "RX", "RY", "RZ", "MS"]
497 .iter()
498 .map(|s| s.to_string())
499 .collect(),
500 gate_fidelities: HashMap::from([
501 ("X".to_string(), 0.9999),
502 ("MS".to_string(), 0.995),
503 ]),
504 t1_times: vec![10000.0; 25], t2_times: vec![1000.0; 25],
506 readout_fidelity: vec![0.995; 25],
507 is_available: true,
508 queue_depth: 12,
509 cost_per_shot: 0.003,
510 }]),
511 }
512 }
513
514 pub fn list_devices(&self) -> &[DeviceInfo] {
516 &self.devices
517 }
518
519 pub fn get_device(&self, name: &str) -> Option<&DeviceInfo> {
521 self.devices.iter().find(|d| d.name == name)
522 }
523
524 pub fn select_best_device(&self, min_qubits: usize, prefer_qpu: bool) -> Option<&DeviceInfo> {
526 self.devices
527 .iter()
528 .filter(|d| {
529 d.num_qubits >= min_qubits
530 && (!prefer_qpu || matches!(d.device_type, DeviceType::QPU))
531 })
532 .max_by(|a, b| {
533 a.quality_score()
534 .partial_cmp(&b.quality_score())
535 .unwrap_or(std::cmp::Ordering::Equal)
536 })
537 }
538
539 pub fn submit_job(
541 &self,
542 device_name: &str,
543 circuit: &QuantumCircuit,
544 shots: Option<usize>,
545 ) -> QuantRS2Result<QuantumJob> {
546 let device = self.get_device(device_name).ok_or_else(|| {
547 QuantRS2Error::InvalidInput(format!("Device {device_name} not found"))
548 })?;
549
550 let shots = shots.unwrap_or(self.config.default_shots);
551
552 if circuit.num_qubits > device.num_qubits {
554 return Err(QuantRS2Error::InvalidInput(format!(
555 "Circuit requires {} qubits, device only has {}",
556 circuit.num_qubits, device.num_qubits
557 )));
558 }
559
560 let estimated_cost = shots as f64 * device.cost_per_shot;
562
563 let timestamp = SystemTime::now()
565 .duration_since(SystemTime::UNIX_EPOCH)
566 .unwrap_or(Duration::ZERO)
567 .as_millis();
568 Ok(QuantumJob {
569 job_id: format!("job_{}", timestamp),
570 platform: self.config.platform,
571 device_name: device_name.to_string(),
572 status: JobStatus::Queued,
573 shots,
574 submitted_at: SystemTime::now(),
575 completed_at: None,
576 result: None,
577 error_message: None,
578 estimated_cost,
579 })
580 }
581
582 pub const fn check_job_status(&self, job_id: &str) -> QuantRS2Result<JobStatus> {
584 Ok(JobStatus::Queued)
586 }
587
588 pub fn wait_for_job(
590 &self,
591 job_id: &str,
592 timeout: Option<Duration>,
593 ) -> QuantRS2Result<QuantumJob> {
594 Err(QuantRS2Error::UnsupportedOperation(
596 "Job waiting not implemented in this simplified version".to_string(),
597 ))
598 }
599
600 pub fn get_job_result(&self, job_id: &str) -> QuantRS2Result<JobResult> {
602 Err(QuantRS2Error::UnsupportedOperation(
604 "Job result retrieval not implemented in this simplified version".to_string(),
605 ))
606 }
607
608 pub const fn cancel_job(&self, job_id: &str) -> QuantRS2Result<()> {
610 Ok(())
612 }
613
614 pub const fn list_jobs(&self, limit: Option<usize>) -> QuantRS2Result<Vec<QuantumJob>> {
616 Ok(Vec::new())
618 }
619}
620
621#[derive(Debug, Clone)]
623pub struct QuantumCircuit {
624 pub num_qubits: usize,
626 pub gates: Vec<Box<dyn GateOp>>,
628 pub measurements: Vec<usize>,
630}
631
632impl QuantumCircuit {
633 pub fn new(num_qubits: usize) -> Self {
635 Self {
636 num_qubits,
637 gates: Vec::new(),
638 measurements: Vec::new(),
639 }
640 }
641
642 pub fn add_gate(&mut self, gate: Box<dyn GateOp>) {
644 self.gates.push(gate);
645 }
646
647 pub fn measure(&mut self, qubit: usize) {
649 if qubit < self.num_qubits {
650 self.measurements.push(qubit);
651 }
652 }
653
654 pub fn measure_all(&mut self) {
656 self.measurements = (0..self.num_qubits).collect();
657 }
658
659 pub fn depth(&self) -> usize {
661 self.gates.len()
663 }
664
665 pub fn gate_counts(&self) -> HashMap<String, usize> {
667 let mut counts = HashMap::new();
668 for gate in &self.gates {
669 *counts.entry(gate.name().to_string()).or_insert(0) += 1;
670 }
671 counts
672 }
673}
674
675#[cfg(test)]
676mod tests {
677 use super::*;
678
679 #[test]
680 fn test_cloud_platform_names() {
681 assert_eq!(CloudPlatform::IBM.name(), "IBM Quantum");
682 assert_eq!(CloudPlatform::AWS.name(), "AWS Braket");
683 assert_eq!(CloudPlatform::Google.name(), "Google Quantum AI");
684 }
685
686 #[test]
687 fn test_device_quality_score() {
688 let device = DeviceInfo {
689 platform: CloudPlatform::IBM,
690 name: "test_device".to_string(),
691 device_type: DeviceType::QPU,
692 num_qubits: 5,
693 connectivity: vec![],
694 gate_set: vec![],
695 gate_fidelities: HashMap::from([("X".to_string(), 0.999), ("CNOT".to_string(), 0.99)]),
696 t1_times: vec![],
697 t2_times: vec![],
698 readout_fidelity: vec![0.95, 0.96, 0.97, 0.98, 0.99],
699 is_available: true,
700 queue_depth: 5,
701 cost_per_shot: 0.001,
702 };
703
704 let score = device.quality_score();
705 assert!(score > 0.8 && score < 1.0);
706 }
707
708 #[test]
709 fn test_job_result_probabilities() {
710 let result = JobResult {
711 counts: HashMap::from([
712 ("00".to_string(), 500),
713 ("01".to_string(), 250),
714 ("10".to_string(), 150),
715 ("11".to_string(), 100),
716 ]),
717 expectation_values: None,
718 state_vector: None,
719 density_matrix: None,
720 raw_data: vec![],
721 metadata: HashMap::new(),
722 };
723
724 let probs = result.probabilities();
725 assert_eq!(probs.get("00"), Some(&0.5));
726 assert_eq!(probs.get("01"), Some(&0.25));
727
728 let most_probable = result
729 .most_probable_outcome()
730 .expect("should have most probable outcome");
731 assert_eq!(most_probable, "00");
732 }
733
734 #[test]
735 fn test_quantum_circuit() {
736 let mut circuit = QuantumCircuit::new(2);
737 assert_eq!(circuit.num_qubits, 2);
738 assert_eq!(circuit.gates.len(), 0);
739
740 circuit.measure_all();
741 assert_eq!(circuit.measurements.len(), 2);
742 }
743}