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