quantrs2_core/
cloud_platforms.rs

1//! Quantum Cloud Platform Integration
2//!
3//! This module provides unified interfaces for interacting with major quantum computing
4//! cloud platforms including IBM Quantum, AWS Braket, and Google Quantum AI.
5//!
6//! ## Supported Platforms
7//!
8//! - **IBM Quantum**: Access to IBM's quantum processors and simulators
9//! - **AWS Braket**: Amazon's quantum computing service
10//! - **Google Quantum AI**: Google's quantum processors
11//! - **Azure Quantum**: Microsoft's quantum computing platform
12//!
13//! ## Features
14//!
15//! - Unified API across all platforms
16//! - Job submission and monitoring
17//! - Result retrieval and analysis
18//! - Device capability querying
19//! - Circuit transpilation for platform-specific requirements
20//! - Cost estimation and optimization
21
22use 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// ================================================================================================
33// Cloud Platform Types
34// ================================================================================================
35
36/// Supported quantum cloud platforms
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum CloudPlatform {
39    /// IBM Quantum
40    IBM,
41    /// AWS Braket
42    AWS,
43    /// Google Quantum AI
44    Google,
45    /// Microsoft Azure Quantum
46    Azure,
47    /// Rigetti Quantum Cloud Services
48    Rigetti,
49    /// IonQ Cloud
50    IonQ,
51}
52
53impl CloudPlatform {
54    /// Get platform name
55    pub fn name(&self) -> &'static str {
56        match self {
57            CloudPlatform::IBM => "IBM Quantum",
58            CloudPlatform::AWS => "AWS Braket",
59            CloudPlatform::Google => "Google Quantum AI",
60            CloudPlatform::Azure => "Azure Quantum",
61            CloudPlatform::Rigetti => "Rigetti QCS",
62            CloudPlatform::IonQ => "IonQ Cloud",
63        }
64    }
65
66    /// Get default API endpoint
67    pub fn endpoint(&self) -> &'static str {
68        match self {
69            CloudPlatform::IBM => "https://auth.quantum-computing.ibm.com/api",
70            CloudPlatform::AWS => "https://braket.us-east-1.amazonaws.com",
71            CloudPlatform::Google => "https://quantumengine.googleapis.com",
72            CloudPlatform::Azure => "https://quantum.azure.com",
73            CloudPlatform::Rigetti => "https://api.rigetti.com",
74            CloudPlatform::IonQ => "https://api.ionq.com",
75        }
76    }
77
78    /// Check if platform supports specific qubit count
79    pub fn supports_qubits(&self, num_qubits: usize) -> bool {
80        match self {
81            CloudPlatform::IBM => num_qubits <= 127, // IBM Quantum Eagle
82            CloudPlatform::AWS => num_qubits <= 34,  // AWS Braket max
83            CloudPlatform::Google => num_qubits <= 72, // Google Sycamore
84            CloudPlatform::Azure => num_qubits <= 40, // Azure various backends
85            CloudPlatform::Rigetti => num_qubits <= 80, // Rigetti Aspen-M
86            CloudPlatform::IonQ => num_qubits <= 32, // IonQ Aria
87        }
88    }
89}
90
91/// Device type (hardware or simulator)
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum DeviceType {
94    /// Quantum processing unit (real hardware)
95    QPU,
96    /// State vector simulator
97    Simulator,
98    /// Tensor network simulator
99    TensorNetworkSimulator,
100    /// Noisy simulator with error models
101    NoisySimulator,
102}
103
104/// Backend device information
105#[derive(Debug, Clone)]
106pub struct DeviceInfo {
107    /// Platform the device belongs to
108    pub platform: CloudPlatform,
109    /// Device name
110    pub name: String,
111    /// Device type (QPU or simulator)
112    pub device_type: DeviceType,
113    /// Number of qubits
114    pub num_qubits: usize,
115    /// Connectivity graph (which qubits are connected)
116    pub connectivity: Vec<(usize, usize)>,
117    /// Gate set supported by the device
118    pub gate_set: Vec<String>,
119    /// Average gate fidelities
120    pub gate_fidelities: HashMap<String, f64>,
121    /// Qubit coherence times T1 (microseconds)
122    pub t1_times: Vec<f64>,
123    /// Qubit coherence times T2 (microseconds)
124    pub t2_times: Vec<f64>,
125    /// Readout fidelity per qubit
126    pub readout_fidelity: Vec<f64>,
127    /// Whether device is currently available
128    pub is_available: bool,
129    /// Queue depth
130    pub queue_depth: usize,
131    /// Estimated cost per shot (in credits or USD)
132    pub cost_per_shot: f64,
133}
134
135impl DeviceInfo {
136    /// Get average single-qubit gate fidelity
137    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 // Default
153        }
154    }
155
156    /// Get average two-qubit gate fidelity
157    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 // Default
173        }
174    }
175
176    /// Calculate quality score for ranking devices
177    pub fn quality_score(&self) -> f64 {
178        let gate_score = (self.avg_single_qubit_fidelity() + self.avg_two_qubit_fidelity()) / 2.0;
179        let readout_score =
180            self.readout_fidelity.iter().sum::<f64>() / self.readout_fidelity.len() as f64;
181        let availability_score = if self.is_available { 1.0 } else { 0.5 };
182        let queue_score = 1.0 / (1.0 + self.queue_depth as f64 / 10.0);
183
184        gate_score * 0.4 + readout_score * 0.3 + availability_score * 0.2 + queue_score * 0.1
185    }
186}
187
188// ================================================================================================
189// Quantum Job Management
190// ================================================================================================
191
192/// Job status
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub enum JobStatus {
195    /// Job is queued
196    Queued,
197    /// Job is running
198    Running,
199    /// Job completed successfully
200    Completed,
201    /// Job failed with error
202    Failed,
203    /// Job was cancelled
204    Cancelled,
205}
206
207/// Quantum job submitted to cloud platform
208#[derive(Debug, Clone)]
209pub struct QuantumJob {
210    /// Unique job ID
211    pub job_id: String,
212    /// Platform where job is running
213    pub platform: CloudPlatform,
214    /// Device name
215    pub device_name: String,
216    /// Job status
217    pub status: JobStatus,
218    /// Number of shots requested
219    pub shots: usize,
220    /// Submission time
221    pub submitted_at: SystemTime,
222    /// Completion time (if completed)
223    pub completed_at: Option<SystemTime>,
224    /// Result data (if completed)
225    pub result: Option<JobResult>,
226    /// Error message (if failed)
227    pub error_message: Option<String>,
228    /// Estimated cost
229    pub estimated_cost: f64,
230}
231
232impl QuantumJob {
233    /// Get execution time
234    pub fn execution_time(&self) -> Option<Duration> {
235        self.completed_at
236            .and_then(|completed| completed.duration_since(self.submitted_at).ok())
237    }
238
239    /// Check if job is finished (completed, failed, or cancelled)
240    pub fn is_finished(&self) -> bool {
241        matches!(
242            self.status,
243            JobStatus::Completed | JobStatus::Failed | JobStatus::Cancelled
244        )
245    }
246}
247
248/// Job execution result
249#[derive(Debug, Clone)]
250pub struct JobResult {
251    /// Measurement counts (bitstring -> count)
252    pub counts: HashMap<String, usize>,
253    /// Measured expectation values (if applicable)
254    pub expectation_values: Option<Vec<f64>>,
255    /// State vector (if using simulator)
256    pub state_vector: Option<Array1<Complex>>,
257    /// Density matrix (if using noisy simulator)
258    pub density_matrix: Option<Array2<Complex>>,
259    /// Raw measurement data
260    pub raw_data: Vec<Vec<usize>>,
261    /// Job metadata
262    pub metadata: HashMap<String, String>,
263}
264
265impl JobResult {
266    /// Get probability distribution from counts
267    pub fn probabilities(&self) -> HashMap<String, f64> {
268        let total: usize = self.counts.values().sum();
269        self.counts
270            .iter()
271            .map(|(k, v)| (k.clone(), *v as f64 / total as f64))
272            .collect()
273    }
274
275    /// Get most probable measurement outcome
276    pub fn most_probable_outcome(&self) -> Option<String> {
277        self.counts
278            .iter()
279            .max_by_key(|(_, count)| *count)
280            .map(|(outcome, _)| outcome.clone())
281    }
282
283    /// Calculate measurement entropy
284    pub fn entropy(&self) -> f64 {
285        let probs = self.probabilities();
286        -probs
287            .values()
288            .filter(|&&p| p > 0.0)
289            .map(|&p| p * p.log2())
290            .sum::<f64>()
291    }
292}
293
294// ================================================================================================
295// Cloud Platform Client
296// ================================================================================================
297
298/// Configuration for cloud platform connection
299#[derive(Debug, Clone)]
300pub struct CloudConfig {
301    /// Platform to connect to
302    pub platform: CloudPlatform,
303    /// API token/key for authentication
304    pub api_token: String,
305    /// API endpoint (optional, uses default if not specified)
306    pub endpoint: Option<String>,
307    /// Default number of shots
308    pub default_shots: usize,
309    /// Timeout for API requests (seconds)
310    pub timeout: u64,
311    /// Enable automatic circuit optimization
312    pub auto_optimize: bool,
313    /// Maximum qubits to use
314    pub max_qubits: Option<usize>,
315}
316
317impl Default for CloudConfig {
318    fn default() -> Self {
319        Self {
320            platform: CloudPlatform::IBM,
321            api_token: String::new(),
322            endpoint: None,
323            default_shots: 1000,
324            timeout: 300,
325            auto_optimize: true,
326            max_qubits: None,
327        }
328    }
329}
330
331/// Cloud platform client for job submission and management
332pub struct CloudClient {
333    config: CloudConfig,
334    devices: Vec<DeviceInfo>,
335}
336
337impl CloudClient {
338    /// Create a new cloud client
339    pub fn new(config: CloudConfig) -> Self {
340        Self {
341            config,
342            devices: Vec::new(),
343        }
344    }
345
346    /// Connect to the cloud platform and authenticate
347    pub fn connect(&mut self) -> QuantRS2Result<()> {
348        // Simplified: in production would make actual API call
349        if self.config.api_token.is_empty() {
350            return Err(QuantRS2Error::InvalidInput(
351                "API token is required".to_string(),
352            ));
353        }
354
355        // Load available devices
356        self.devices = self.load_devices()?;
357
358        Ok(())
359    }
360
361    /// Load available devices from platform
362    fn load_devices(&self) -> QuantRS2Result<Vec<DeviceInfo>> {
363        // Simplified: return mock devices based on platform
364        match self.config.platform {
365            CloudPlatform::IBM => Ok(vec![
366                DeviceInfo {
367                    platform: CloudPlatform::IBM,
368                    name: "ibmq_jakarta".to_string(),
369                    device_type: DeviceType::QPU,
370                    num_qubits: 7,
371                    connectivity: vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)],
372                    gate_set: vec!["X", "Y", "Z", "H", "CNOT", "RZ"]
373                        .iter()
374                        .map(|s| s.to_string())
375                        .collect(),
376                    gate_fidelities: HashMap::from([
377                        ("X".to_string(), 0.9993),
378                        ("CNOT".to_string(), 0.987),
379                    ]),
380                    t1_times: vec![100.0, 95.0, 110.0, 98.0, 105.0, 92.0, 88.0],
381                    t2_times: vec![120.0, 110.0, 115.0, 108.0, 125.0, 105.0, 98.0],
382                    readout_fidelity: vec![0.98, 0.97, 0.98, 0.96, 0.97, 0.98, 0.97],
383                    is_available: true,
384                    queue_depth: 5,
385                    cost_per_shot: 0.001,
386                },
387                DeviceInfo {
388                    platform: CloudPlatform::IBM,
389                    name: "ibmq_qasm_simulator".to_string(),
390                    device_type: DeviceType::Simulator,
391                    num_qubits: 32,
392                    connectivity: vec![], // Fully connected
393                    gate_set: vec!["X", "Y", "Z", "H", "CNOT", "RX", "RY", "RZ"]
394                        .iter()
395                        .map(|s| s.to_string())
396                        .collect(),
397                    gate_fidelities: HashMap::from([
398                        ("X".to_string(), 1.0),
399                        ("CNOT".to_string(), 1.0),
400                    ]),
401                    t1_times: vec![],
402                    t2_times: vec![],
403                    readout_fidelity: vec![],
404                    is_available: true,
405                    queue_depth: 0,
406                    cost_per_shot: 0.0,
407                },
408            ]),
409            CloudPlatform::AWS => Ok(vec![DeviceInfo {
410                platform: CloudPlatform::AWS,
411                name: "SV1".to_string(),
412                device_type: DeviceType::Simulator,
413                num_qubits: 34,
414                connectivity: vec![],
415                gate_set: vec!["X", "Y", "Z", "H", "CNOT", "RX", "RY", "RZ", "CZ"]
416                    .iter()
417                    .map(|s| s.to_string())
418                    .collect(),
419                gate_fidelities: HashMap::from([("X".to_string(), 1.0), ("CNOT".to_string(), 1.0)]),
420                t1_times: vec![],
421                t2_times: vec![],
422                readout_fidelity: vec![],
423                is_available: true,
424                queue_depth: 0,
425                cost_per_shot: 0.00075,
426            }]),
427            CloudPlatform::Google => Ok(vec![DeviceInfo {
428                platform: CloudPlatform::Google,
429                name: "rainbow".to_string(),
430                device_type: DeviceType::QPU,
431                num_qubits: 23,
432                connectivity: vec![(0, 1), (1, 2), (2, 3)], // Simplified
433                gate_set: vec!["X", "Y", "Z", "PhasedXZ", "CZ", "SQRT_ISWAP"]
434                    .iter()
435                    .map(|s| s.to_string())
436                    .collect(),
437                gate_fidelities: HashMap::from([
438                    ("X".to_string(), 0.9995),
439                    ("CZ".to_string(), 0.993),
440                ]),
441                t1_times: vec![15.0; 23],
442                t2_times: vec![20.0; 23],
443                readout_fidelity: vec![0.96; 23],
444                is_available: true,
445                queue_depth: 3,
446                cost_per_shot: 0.002,
447            }]),
448            CloudPlatform::Azure => Ok(vec![DeviceInfo {
449                platform: CloudPlatform::Azure,
450                name: "azure-simulator".to_string(),
451                device_type: DeviceType::Simulator,
452                num_qubits: 40,
453                connectivity: vec![],
454                gate_set: vec!["X", "Y", "Z", "H", "CNOT", "T"]
455                    .iter()
456                    .map(|s| s.to_string())
457                    .collect(),
458                gate_fidelities: HashMap::from([("X".to_string(), 1.0), ("CNOT".to_string(), 1.0)]),
459                t1_times: vec![],
460                t2_times: vec![],
461                readout_fidelity: vec![],
462                is_available: true,
463                queue_depth: 0,
464                cost_per_shot: 0.0005,
465            }]),
466            CloudPlatform::Rigetti => Ok(vec![DeviceInfo {
467                platform: CloudPlatform::Rigetti,
468                name: "Aspen-M-3".to_string(),
469                device_type: DeviceType::QPU,
470                num_qubits: 80,
471                connectivity: vec![(0, 1), (1, 2)], // Simplified
472                gate_set: vec!["RX", "RZ", "CZ", "XY"]
473                    .iter()
474                    .map(|s| s.to_string())
475                    .collect(),
476                gate_fidelities: HashMap::from([
477                    ("RX".to_string(), 0.998),
478                    ("CZ".to_string(), 0.95),
479                ]),
480                t1_times: vec![20.0; 80],
481                t2_times: vec![15.0; 80],
482                readout_fidelity: vec![0.95; 80],
483                is_available: true,
484                queue_depth: 8,
485                cost_per_shot: 0.0015,
486            }]),
487            CloudPlatform::IonQ => Ok(vec![DeviceInfo {
488                platform: CloudPlatform::IonQ,
489                name: "ionq.qpu.aria-1".to_string(),
490                device_type: DeviceType::QPU,
491                num_qubits: 25,
492                connectivity: vec![], // All-to-all connectivity
493                gate_set: vec!["X", "Y", "Z", "RX", "RY", "RZ", "MS"]
494                    .iter()
495                    .map(|s| s.to_string())
496                    .collect(),
497                gate_fidelities: HashMap::from([
498                    ("X".to_string(), 0.9999),
499                    ("MS".to_string(), 0.995),
500                ]),
501                t1_times: vec![10000.0; 25], // Very long T1 for trapped ions
502                t2_times: vec![1000.0; 25],
503                readout_fidelity: vec![0.995; 25],
504                is_available: true,
505                queue_depth: 12,
506                cost_per_shot: 0.003,
507            }]),
508        }
509    }
510
511    /// Get list of available devices
512    pub fn list_devices(&self) -> &[DeviceInfo] {
513        &self.devices
514    }
515
516    /// Get device by name
517    pub fn get_device(&self, name: &str) -> Option<&DeviceInfo> {
518        self.devices.iter().find(|d| d.name == name)
519    }
520
521    /// Get best available device based on requirements
522    pub fn select_best_device(&self, min_qubits: usize, prefer_qpu: bool) -> Option<&DeviceInfo> {
523        self.devices
524            .iter()
525            .filter(|d| {
526                d.num_qubits >= min_qubits
527                    && (!prefer_qpu || matches!(d.device_type, DeviceType::QPU))
528            })
529            .max_by(|a, b| a.quality_score().partial_cmp(&b.quality_score()).unwrap())
530    }
531
532    /// Submit a quantum job
533    pub fn submit_job(
534        &self,
535        device_name: &str,
536        circuit: &QuantumCircuit,
537        shots: Option<usize>,
538    ) -> QuantRS2Result<QuantumJob> {
539        let device = self.get_device(device_name).ok_or_else(|| {
540            QuantRS2Error::InvalidInput(format!("Device {} not found", device_name))
541        })?;
542
543        let shots = shots.unwrap_or(self.config.default_shots);
544
545        // Validate circuit
546        if circuit.num_qubits > device.num_qubits {
547            return Err(QuantRS2Error::InvalidInput(format!(
548                "Circuit requires {} qubits, device only has {}",
549                circuit.num_qubits, device.num_qubits
550            )));
551        }
552
553        // Calculate estimated cost
554        let estimated_cost = shots as f64 * device.cost_per_shot;
555
556        // Create job (simplified - in production would make API call)
557        Ok(QuantumJob {
558            job_id: format!(
559                "job_{}",
560                SystemTime::now()
561                    .duration_since(SystemTime::UNIX_EPOCH)
562                    .unwrap()
563                    .as_millis()
564            ),
565            platform: self.config.platform,
566            device_name: device_name.to_string(),
567            status: JobStatus::Queued,
568            shots,
569            submitted_at: SystemTime::now(),
570            completed_at: None,
571            result: None,
572            error_message: None,
573            estimated_cost,
574        })
575    }
576
577    /// Check job status
578    pub fn check_job_status(&self, job_id: &str) -> QuantRS2Result<JobStatus> {
579        // Simplified: in production would make API call
580        Ok(JobStatus::Queued)
581    }
582
583    /// Wait for job completion
584    pub fn wait_for_job(
585        &self,
586        job_id: &str,
587        timeout: Option<Duration>,
588    ) -> QuantRS2Result<QuantumJob> {
589        // Simplified: in production would poll API until job completes
590        Err(QuantRS2Error::UnsupportedOperation(
591            "Job waiting not implemented in this simplified version".to_string(),
592        ))
593    }
594
595    /// Get job result
596    pub fn get_job_result(&self, job_id: &str) -> QuantRS2Result<JobResult> {
597        // Simplified: in production would fetch from API
598        Err(QuantRS2Error::UnsupportedOperation(
599            "Job result retrieval not implemented in this simplified version".to_string(),
600        ))
601    }
602
603    /// Cancel a job
604    pub fn cancel_job(&self, job_id: &str) -> QuantRS2Result<()> {
605        // Simplified: in production would make API call
606        Ok(())
607    }
608
609    /// List user's jobs
610    pub fn list_jobs(&self, limit: Option<usize>) -> QuantRS2Result<Vec<QuantumJob>> {
611        // Simplified: in production would fetch from API
612        Ok(Vec::new())
613    }
614}
615
616/// Quantum circuit representation for cloud submission
617#[derive(Debug, Clone)]
618pub struct QuantumCircuit {
619    /// Number of qubits
620    pub num_qubits: usize,
621    /// Circuit gates
622    pub gates: Vec<Box<dyn GateOp>>,
623    /// Measurements to perform
624    pub measurements: Vec<usize>,
625}
626
627impl QuantumCircuit {
628    /// Create a new quantum circuit
629    pub fn new(num_qubits: usize) -> Self {
630        Self {
631            num_qubits,
632            gates: Vec::new(),
633            measurements: Vec::new(),
634        }
635    }
636
637    /// Add a gate to the circuit
638    pub fn add_gate(&mut self, gate: Box<dyn GateOp>) {
639        self.gates.push(gate);
640    }
641
642    /// Add measurement
643    pub fn measure(&mut self, qubit: usize) {
644        if qubit < self.num_qubits {
645            self.measurements.push(qubit);
646        }
647    }
648
649    /// Measure all qubits
650    pub fn measure_all(&mut self) {
651        self.measurements = (0..self.num_qubits).collect();
652    }
653
654    /// Get circuit depth
655    pub fn depth(&self) -> usize {
656        // Simplified: actual implementation would compute proper depth
657        self.gates.len()
658    }
659
660    /// Count gates by type
661    pub fn gate_counts(&self) -> HashMap<String, usize> {
662        let mut counts = HashMap::new();
663        for gate in &self.gates {
664            *counts.entry(gate.name().to_string()).or_insert(0) += 1;
665        }
666        counts
667    }
668}
669
670#[cfg(test)]
671mod tests {
672    use super::*;
673
674    #[test]
675    fn test_cloud_platform_names() {
676        assert_eq!(CloudPlatform::IBM.name(), "IBM Quantum");
677        assert_eq!(CloudPlatform::AWS.name(), "AWS Braket");
678        assert_eq!(CloudPlatform::Google.name(), "Google Quantum AI");
679    }
680
681    #[test]
682    fn test_device_quality_score() {
683        let device = DeviceInfo {
684            platform: CloudPlatform::IBM,
685            name: "test_device".to_string(),
686            device_type: DeviceType::QPU,
687            num_qubits: 5,
688            connectivity: vec![],
689            gate_set: vec![],
690            gate_fidelities: HashMap::from([("X".to_string(), 0.999), ("CNOT".to_string(), 0.99)]),
691            t1_times: vec![],
692            t2_times: vec![],
693            readout_fidelity: vec![0.95, 0.96, 0.97, 0.98, 0.99],
694            is_available: true,
695            queue_depth: 5,
696            cost_per_shot: 0.001,
697        };
698
699        let score = device.quality_score();
700        assert!(score > 0.8 && score < 1.0);
701    }
702
703    #[test]
704    fn test_job_result_probabilities() {
705        let result = JobResult {
706            counts: HashMap::from([
707                ("00".to_string(), 500),
708                ("01".to_string(), 250),
709                ("10".to_string(), 150),
710                ("11".to_string(), 100),
711            ]),
712            expectation_values: None,
713            state_vector: None,
714            density_matrix: None,
715            raw_data: vec![],
716            metadata: HashMap::new(),
717        };
718
719        let probs = result.probabilities();
720        assert_eq!(probs.get("00"), Some(&0.5));
721        assert_eq!(probs.get("01"), Some(&0.25));
722
723        let most_probable = result.most_probable_outcome().unwrap();
724        assert_eq!(most_probable, "00");
725    }
726
727    #[test]
728    fn test_quantum_circuit() {
729        let mut circuit = QuantumCircuit::new(2);
730        assert_eq!(circuit.num_qubits, 2);
731        assert_eq!(circuit.gates.len(), 0);
732
733        circuit.measure_all();
734        assert_eq!(circuit.measurements.len(), 2);
735    }
736}