Skip to main content

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)]
38#[non_exhaustive]
39pub enum CloudPlatform {
40    /// IBM Quantum
41    IBM,
42    /// AWS Braket
43    AWS,
44    /// Google Quantum AI
45    Google,
46    /// Microsoft Azure Quantum
47    Azure,
48    /// Rigetti Quantum Cloud Services
49    Rigetti,
50    /// IonQ Cloud
51    IonQ,
52}
53
54impl CloudPlatform {
55    /// Get platform name
56    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    /// Get default API endpoint
68    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    /// Check if platform supports specific qubit count
80    pub const fn supports_qubits(&self, num_qubits: usize) -> bool {
81        match self {
82            Self::IBM => num_qubits <= 127,    // IBM Quantum Eagle
83            Self::AWS => num_qubits <= 34,     // AWS Braket max
84            Self::Google => num_qubits <= 72,  // Google Sycamore
85            Self::Azure => num_qubits <= 40,   // Azure various backends
86            Self::Rigetti => num_qubits <= 80, // Rigetti Aspen-M
87            Self::IonQ => num_qubits <= 32,    // IonQ Aria
88        }
89    }
90}
91
92/// Device type (hardware or simulator)
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum DeviceType {
95    /// Quantum processing unit (real hardware)
96    QPU,
97    /// State vector simulator
98    Simulator,
99    /// Tensor network simulator
100    TensorNetworkSimulator,
101    /// Noisy simulator with error models
102    NoisySimulator,
103}
104
105/// Backend device information
106#[derive(Debug, Clone)]
107pub struct DeviceInfo {
108    /// Platform the device belongs to
109    pub platform: CloudPlatform,
110    /// Device name
111    pub name: String,
112    /// Device type (QPU or simulator)
113    pub device_type: DeviceType,
114    /// Number of qubits
115    pub num_qubits: usize,
116    /// Connectivity graph (which qubits are connected)
117    pub connectivity: Vec<(usize, usize)>,
118    /// Gate set supported by the device
119    pub gate_set: Vec<String>,
120    /// Average gate fidelities
121    pub gate_fidelities: HashMap<String, f64>,
122    /// Qubit coherence times T1 (microseconds)
123    pub t1_times: Vec<f64>,
124    /// Qubit coherence times T2 (microseconds)
125    pub t2_times: Vec<f64>,
126    /// Readout fidelity per qubit
127    pub readout_fidelity: Vec<f64>,
128    /// Whether device is currently available
129    pub is_available: bool,
130    /// Queue depth
131    pub queue_depth: usize,
132    /// Estimated cost per shot (in credits or USD)
133    pub cost_per_shot: f64,
134}
135
136impl DeviceInfo {
137    /// Get average single-qubit gate fidelity
138    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 // Default
154        }
155    }
156
157    /// Get average two-qubit gate fidelity
158    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 // Default
174        }
175    }
176
177    /// Calculate quality score for ranking devices
178    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// ================================================================================================
193// Quantum Job Management
194// ================================================================================================
195
196/// Job status
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198#[non_exhaustive]
199pub enum JobStatus {
200    /// Job is queued
201    Queued,
202    /// Job is running
203    Running,
204    /// Job completed successfully
205    Completed,
206    /// Job failed with error
207    Failed,
208    /// Job was cancelled
209    Cancelled,
210}
211
212/// Quantum job submitted to cloud platform
213#[derive(Debug, Clone)]
214pub struct QuantumJob {
215    /// Unique job ID
216    pub job_id: String,
217    /// Platform where job is running
218    pub platform: CloudPlatform,
219    /// Device name
220    pub device_name: String,
221    /// Job status
222    pub status: JobStatus,
223    /// Number of shots requested
224    pub shots: usize,
225    /// Submission time
226    pub submitted_at: SystemTime,
227    /// Completion time (if completed)
228    pub completed_at: Option<SystemTime>,
229    /// Result data (if completed)
230    pub result: Option<JobResult>,
231    /// Error message (if failed)
232    pub error_message: Option<String>,
233    /// Estimated cost
234    pub estimated_cost: f64,
235}
236
237impl QuantumJob {
238    /// Get execution time
239    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    /// Check if job is finished (completed, failed, or cancelled)
245    pub const fn is_finished(&self) -> bool {
246        matches!(
247            self.status,
248            JobStatus::Completed | JobStatus::Failed | JobStatus::Cancelled
249        )
250    }
251}
252
253/// Job execution result
254#[derive(Debug, Clone)]
255pub struct JobResult {
256    /// Measurement counts (bitstring -> count)
257    pub counts: HashMap<String, usize>,
258    /// Measured expectation values (if applicable)
259    pub expectation_values: Option<Vec<f64>>,
260    /// State vector (if using simulator)
261    pub state_vector: Option<Array1<Complex>>,
262    /// Density matrix (if using noisy simulator)
263    pub density_matrix: Option<Array2<Complex>>,
264    /// Raw measurement data
265    pub raw_data: Vec<Vec<usize>>,
266    /// Job metadata
267    pub metadata: HashMap<String, String>,
268}
269
270impl JobResult {
271    /// Get probability distribution from counts
272    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    /// Get most probable measurement outcome
281    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    /// Calculate measurement entropy
289    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// ================================================================================================
300// Cloud Platform Client
301// ================================================================================================
302
303/// Configuration for cloud platform connection
304#[derive(Debug, Clone)]
305pub struct CloudConfig {
306    /// Platform to connect to
307    pub platform: CloudPlatform,
308    /// API token/key for authentication
309    pub api_token: String,
310    /// API endpoint (optional, uses default if not specified)
311    pub endpoint: Option<String>,
312    /// Default number of shots
313    pub default_shots: usize,
314    /// Timeout for API requests (seconds)
315    pub timeout: u64,
316    /// Enable automatic circuit optimization
317    pub auto_optimize: bool,
318    /// Maximum qubits to use
319    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
336/// Cloud platform client for job submission and management
337pub struct CloudClient {
338    config: CloudConfig,
339    devices: Vec<DeviceInfo>,
340}
341
342impl CloudClient {
343    /// Create a new cloud client
344    pub const fn new(config: CloudConfig) -> Self {
345        Self {
346            config,
347            devices: Vec::new(),
348        }
349    }
350
351    /// Connect to the cloud platform and authenticate
352    pub fn connect(&mut self) -> QuantRS2Result<()> {
353        // Simplified: in production would make actual API call
354        if self.config.api_token.is_empty() {
355            return Err(QuantRS2Error::InvalidInput(
356                "API token is required".to_string(),
357            ));
358        }
359
360        // Load available devices
361        self.devices = self.load_devices()?;
362
363        Ok(())
364    }
365
366    /// Load available devices from platform
367    fn load_devices(&self) -> QuantRS2Result<Vec<DeviceInfo>> {
368        // Simplified: return mock devices based on platform
369        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![], // Fully connected
398                    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)], // Simplified
438                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)], // Simplified
477                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![], // All-to-all connectivity
498                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], // Very long T1 for trapped ions
507                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    /// Get list of available devices
517    pub fn list_devices(&self) -> &[DeviceInfo] {
518        &self.devices
519    }
520
521    /// Get device by name
522    pub fn get_device(&self, name: &str) -> Option<&DeviceInfo> {
523        self.devices.iter().find(|d| d.name == name)
524    }
525
526    /// Get best available device based on requirements
527    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    /// Submit a quantum job
542    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        // Validate circuit
555        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        // Calculate estimated cost
563        let estimated_cost = shots as f64 * device.cost_per_shot;
564
565        // Create job (simplified - in production would make API call)
566        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    /// Check job status
585    pub const fn check_job_status(&self, job_id: &str) -> QuantRS2Result<JobStatus> {
586        // Simplified: in production would make API call
587        Ok(JobStatus::Queued)
588    }
589
590    /// Wait for job completion
591    pub fn wait_for_job(
592        &self,
593        job_id: &str,
594        timeout: Option<Duration>,
595    ) -> QuantRS2Result<QuantumJob> {
596        // Simplified: in production would poll API until job completes
597        Err(QuantRS2Error::UnsupportedOperation(
598            "Job waiting not implemented in this simplified version".to_string(),
599        ))
600    }
601
602    /// Get job result
603    pub fn get_job_result(&self, job_id: &str) -> QuantRS2Result<JobResult> {
604        // Simplified: in production would fetch from API
605        Err(QuantRS2Error::UnsupportedOperation(
606            "Job result retrieval not implemented in this simplified version".to_string(),
607        ))
608    }
609
610    /// Cancel a job
611    pub const fn cancel_job(&self, job_id: &str) -> QuantRS2Result<()> {
612        // Simplified: in production would make API call
613        Ok(())
614    }
615
616    /// List user's jobs
617    pub const fn list_jobs(&self, limit: Option<usize>) -> QuantRS2Result<Vec<QuantumJob>> {
618        // Simplified: in production would fetch from API
619        Ok(Vec::new())
620    }
621}
622
623/// Quantum circuit representation for cloud submission
624#[derive(Debug, Clone)]
625pub struct QuantumCircuit {
626    /// Number of qubits
627    pub num_qubits: usize,
628    /// Circuit gates
629    pub gates: Vec<Box<dyn GateOp>>,
630    /// Measurements to perform
631    pub measurements: Vec<usize>,
632}
633
634impl QuantumCircuit {
635    /// Create a new quantum circuit
636    pub fn new(num_qubits: usize) -> Self {
637        Self {
638            num_qubits,
639            gates: Vec::new(),
640            measurements: Vec::new(),
641        }
642    }
643
644    /// Add a gate to the circuit
645    pub fn add_gate(&mut self, gate: Box<dyn GateOp>) {
646        self.gates.push(gate);
647    }
648
649    /// Add measurement
650    pub fn measure(&mut self, qubit: usize) {
651        if qubit < self.num_qubits {
652            self.measurements.push(qubit);
653        }
654    }
655
656    /// Measure all qubits
657    pub fn measure_all(&mut self) {
658        self.measurements = (0..self.num_qubits).collect();
659    }
660
661    /// Get circuit depth
662    pub fn depth(&self) -> usize {
663        // Simplified: actual implementation would compute proper depth
664        self.gates.len()
665    }
666
667    /// Count gates by type
668    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}