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 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    /// Get default API endpoint
67    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    /// Check if platform supports specific qubit count
79    pub const fn supports_qubits(&self, num_qubits: usize) -> bool {
80        match self {
81            Self::IBM => num_qubits <= 127,    // IBM Quantum Eagle
82            Self::AWS => num_qubits <= 34,     // AWS Braket max
83            Self::Google => num_qubits <= 72,  // Google Sycamore
84            Self::Azure => num_qubits <= 40,   // Azure various backends
85            Self::Rigetti => num_qubits <= 80, // Rigetti Aspen-M
86            Self::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 = 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// ================================================================================================
192// Quantum Job Management
193// ================================================================================================
194
195/// Job status
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum JobStatus {
198    /// Job is queued
199    Queued,
200    /// Job is running
201    Running,
202    /// Job completed successfully
203    Completed,
204    /// Job failed with error
205    Failed,
206    /// Job was cancelled
207    Cancelled,
208}
209
210/// Quantum job submitted to cloud platform
211#[derive(Debug, Clone)]
212pub struct QuantumJob {
213    /// Unique job ID
214    pub job_id: String,
215    /// Platform where job is running
216    pub platform: CloudPlatform,
217    /// Device name
218    pub device_name: String,
219    /// Job status
220    pub status: JobStatus,
221    /// Number of shots requested
222    pub shots: usize,
223    /// Submission time
224    pub submitted_at: SystemTime,
225    /// Completion time (if completed)
226    pub completed_at: Option<SystemTime>,
227    /// Result data (if completed)
228    pub result: Option<JobResult>,
229    /// Error message (if failed)
230    pub error_message: Option<String>,
231    /// Estimated cost
232    pub estimated_cost: f64,
233}
234
235impl QuantumJob {
236    /// Get execution time
237    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    /// Check if job is finished (completed, failed, or cancelled)
243    pub const fn is_finished(&self) -> bool {
244        matches!(
245            self.status,
246            JobStatus::Completed | JobStatus::Failed | JobStatus::Cancelled
247        )
248    }
249}
250
251/// Job execution result
252#[derive(Debug, Clone)]
253pub struct JobResult {
254    /// Measurement counts (bitstring -> count)
255    pub counts: HashMap<String, usize>,
256    /// Measured expectation values (if applicable)
257    pub expectation_values: Option<Vec<f64>>,
258    /// State vector (if using simulator)
259    pub state_vector: Option<Array1<Complex>>,
260    /// Density matrix (if using noisy simulator)
261    pub density_matrix: Option<Array2<Complex>>,
262    /// Raw measurement data
263    pub raw_data: Vec<Vec<usize>>,
264    /// Job metadata
265    pub metadata: HashMap<String, String>,
266}
267
268impl JobResult {
269    /// Get probability distribution from counts
270    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    /// Get most probable measurement outcome
279    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    /// Calculate measurement entropy
287    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// ================================================================================================
298// Cloud Platform Client
299// ================================================================================================
300
301/// Configuration for cloud platform connection
302#[derive(Debug, Clone)]
303pub struct CloudConfig {
304    /// Platform to connect to
305    pub platform: CloudPlatform,
306    /// API token/key for authentication
307    pub api_token: String,
308    /// API endpoint (optional, uses default if not specified)
309    pub endpoint: Option<String>,
310    /// Default number of shots
311    pub default_shots: usize,
312    /// Timeout for API requests (seconds)
313    pub timeout: u64,
314    /// Enable automatic circuit optimization
315    pub auto_optimize: bool,
316    /// Maximum qubits to use
317    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
334/// Cloud platform client for job submission and management
335pub struct CloudClient {
336    config: CloudConfig,
337    devices: Vec<DeviceInfo>,
338}
339
340impl CloudClient {
341    /// Create a new cloud client
342    pub const fn new(config: CloudConfig) -> Self {
343        Self {
344            config,
345            devices: Vec::new(),
346        }
347    }
348
349    /// Connect to the cloud platform and authenticate
350    pub fn connect(&mut self) -> QuantRS2Result<()> {
351        // Simplified: in production would make actual API call
352        if self.config.api_token.is_empty() {
353            return Err(QuantRS2Error::InvalidInput(
354                "API token is required".to_string(),
355            ));
356        }
357
358        // Load available devices
359        self.devices = self.load_devices()?;
360
361        Ok(())
362    }
363
364    /// Load available devices from platform
365    fn load_devices(&self) -> QuantRS2Result<Vec<DeviceInfo>> {
366        // Simplified: return mock devices based on platform
367        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![], // Fully connected
396                    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)], // Simplified
436                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)], // Simplified
475                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![], // All-to-all connectivity
496                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], // Very long T1 for trapped ions
505                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    /// Get list of available devices
515    pub fn list_devices(&self) -> &[DeviceInfo] {
516        &self.devices
517    }
518
519    /// Get device by name
520    pub fn get_device(&self, name: &str) -> Option<&DeviceInfo> {
521        self.devices.iter().find(|d| d.name == name)
522    }
523
524    /// Get best available device based on requirements
525    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    /// Submit a quantum job
540    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        // Validate circuit
553        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        // Calculate estimated cost
561        let estimated_cost = shots as f64 * device.cost_per_shot;
562
563        // Create job (simplified - in production would make API call)
564        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    /// Check job status
583    pub const fn check_job_status(&self, job_id: &str) -> QuantRS2Result<JobStatus> {
584        // Simplified: in production would make API call
585        Ok(JobStatus::Queued)
586    }
587
588    /// Wait for job completion
589    pub fn wait_for_job(
590        &self,
591        job_id: &str,
592        timeout: Option<Duration>,
593    ) -> QuantRS2Result<QuantumJob> {
594        // Simplified: in production would poll API until job completes
595        Err(QuantRS2Error::UnsupportedOperation(
596            "Job waiting not implemented in this simplified version".to_string(),
597        ))
598    }
599
600    /// Get job result
601    pub fn get_job_result(&self, job_id: &str) -> QuantRS2Result<JobResult> {
602        // Simplified: in production would fetch from API
603        Err(QuantRS2Error::UnsupportedOperation(
604            "Job result retrieval not implemented in this simplified version".to_string(),
605        ))
606    }
607
608    /// Cancel a job
609    pub const fn cancel_job(&self, job_id: &str) -> QuantRS2Result<()> {
610        // Simplified: in production would make API call
611        Ok(())
612    }
613
614    /// List user's jobs
615    pub const fn list_jobs(&self, limit: Option<usize>) -> QuantRS2Result<Vec<QuantumJob>> {
616        // Simplified: in production would fetch from API
617        Ok(Vec::new())
618    }
619}
620
621/// Quantum circuit representation for cloud submission
622#[derive(Debug, Clone)]
623pub struct QuantumCircuit {
624    /// Number of qubits
625    pub num_qubits: usize,
626    /// Circuit gates
627    pub gates: Vec<Box<dyn GateOp>>,
628    /// Measurements to perform
629    pub measurements: Vec<usize>,
630}
631
632impl QuantumCircuit {
633    /// Create a new quantum circuit
634    pub fn new(num_qubits: usize) -> Self {
635        Self {
636            num_qubits,
637            gates: Vec::new(),
638            measurements: Vec::new(),
639        }
640    }
641
642    /// Add a gate to the circuit
643    pub fn add_gate(&mut self, gate: Box<dyn GateOp>) {
644        self.gates.push(gate);
645    }
646
647    /// Add measurement
648    pub fn measure(&mut self, qubit: usize) {
649        if qubit < self.num_qubits {
650            self.measurements.push(qubit);
651        }
652    }
653
654    /// Measure all qubits
655    pub fn measure_all(&mut self) {
656        self.measurements = (0..self.num_qubits).collect();
657    }
658
659    /// Get circuit depth
660    pub fn depth(&self) -> usize {
661        // Simplified: actual implementation would compute proper depth
662        self.gates.len()
663    }
664
665    /// Count gates by type
666    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}