quantrs2_sim/mixed_precision_impl/
mod.rs

1//! Mixed-precision quantum simulation module.
2//!
3//! This module provides adaptive precision algorithms that automatically
4//! select optimal numerical precision (f16, f32, f64) for different parts
5//! of quantum computations, leveraging performance optimization while
6//! maintaining required accuracy.
7
8pub mod analysis;
9pub mod config;
10pub mod simulator;
11pub mod state_vector;
12
13// Re-export commonly used types and structs
14pub use analysis::{AnalysisSummary, PerformanceMetrics, PrecisionAnalysis, PrecisionAnalyzer};
15pub use config::{
16    AdaptiveStrategy, MixedPrecisionConfig, MixedPrecisionContext, PrecisionLevel, QuantumPrecision,
17};
18pub use simulator::{MixedPrecisionSimulator, MixedPrecisionStats};
19pub use state_vector::MixedPrecisionStateVector;
20
21use crate::error::Result;
22
23/// Initialize the mixed-precision subsystem
24#[allow(clippy::missing_const_for_fn)] // Cannot be const due to non-const calls in cfg block
25pub fn initialize() -> Result<()> {
26    // Perform any necessary initialization
27    #[cfg(feature = "advanced_math")]
28    {
29        // Initialize SciRS2 mixed precision context if available
30        let _context = MixedPrecisionContext::new(AdaptiveStrategy::ErrorBased(1e-6));
31        let _ = _context; // Explicitly use to avoid unused variable warning
32    }
33
34    Ok(())
35}
36
37/// Check if mixed-precision features are available
38#[must_use]
39pub const fn is_available() -> bool {
40    cfg!(feature = "advanced_math")
41}
42
43/// Get supported precision levels
44#[must_use]
45pub fn get_supported_precisions() -> Vec<QuantumPrecision> {
46    vec![
47        QuantumPrecision::Half,
48        QuantumPrecision::BFloat16,
49        QuantumPrecision::TF32,
50        QuantumPrecision::Single,
51        QuantumPrecision::Double,
52        QuantumPrecision::Adaptive,
53    ]
54}
55
56/// Create a default configuration for accuracy
57#[must_use]
58pub const fn default_accuracy_config() -> MixedPrecisionConfig {
59    MixedPrecisionConfig::for_accuracy()
60}
61
62/// Create a default configuration for performance
63#[must_use]
64pub const fn default_performance_config() -> MixedPrecisionConfig {
65    MixedPrecisionConfig::for_performance()
66}
67
68/// Create a balanced configuration
69#[must_use]
70pub fn default_balanced_config() -> MixedPrecisionConfig {
71    MixedPrecisionConfig::balanced()
72}
73
74/// Validate a mixed-precision configuration
75pub fn validate_config(config: &MixedPrecisionConfig) -> Result<()> {
76    config.validate()
77}
78
79/// Estimate memory usage for a given configuration and number of qubits
80#[must_use]
81pub fn estimate_memory_usage(config: &MixedPrecisionConfig, num_qubits: usize) -> usize {
82    config.estimate_memory_usage(num_qubits)
83}
84
85/// Calculate memory savings compared to double precision
86#[must_use]
87pub fn calculate_memory_savings(config: &MixedPrecisionConfig, num_qubits: usize) -> f64 {
88    simulator::utils::memory_savings(config, num_qubits)
89}
90
91/// Get performance improvement factor for a precision level
92#[must_use]
93pub fn get_performance_factor(precision: QuantumPrecision) -> f64 {
94    simulator::utils::performance_improvement_factor(precision)
95}
96
97/// Benchmark different precision levels
98pub fn benchmark_precisions() -> Result<analysis::PrecisionAnalysis> {
99    let mut analyzer = PrecisionAnalyzer::new();
100    Ok(analyzer.analyze_for_tolerance(1e-6))
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use scirs2_core::ndarray::Array1;
107    use scirs2_core::Complex64;
108
109    #[test]
110    fn test_precision_initialization() {
111        let result = initialize();
112        assert!(result.is_ok());
113    }
114
115    #[test]
116    fn test_supported_precisions() {
117        let precisions = get_supported_precisions();
118        assert_eq!(precisions.len(), 6);
119        assert!(precisions.contains(&QuantumPrecision::Half));
120        assert!(precisions.contains(&QuantumPrecision::BFloat16));
121        assert!(precisions.contains(&QuantumPrecision::TF32));
122        assert!(precisions.contains(&QuantumPrecision::Single));
123        assert!(precisions.contains(&QuantumPrecision::Double));
124        assert!(precisions.contains(&QuantumPrecision::Adaptive));
125    }
126
127    #[test]
128    fn test_config_creation() {
129        let accuracy_config = default_accuracy_config();
130        assert_eq!(
131            accuracy_config.state_vector_precision,
132            QuantumPrecision::Double
133        );
134
135        let performance_config = default_performance_config();
136        assert_eq!(
137            performance_config.state_vector_precision,
138            QuantumPrecision::Half
139        );
140
141        let balanced_config = default_balanced_config();
142        assert_eq!(
143            balanced_config.state_vector_precision,
144            QuantumPrecision::Single
145        );
146    }
147
148    #[test]
149    fn test_config_validation() {
150        let config = MixedPrecisionConfig::default();
151        assert!(validate_config(&config).is_ok());
152
153        let mut invalid_config = config;
154        invalid_config.error_tolerance = -1.0;
155        assert!(validate_config(&invalid_config).is_err());
156    }
157
158    #[test]
159    fn test_memory_estimation() {
160        let config = MixedPrecisionConfig::default();
161        let memory_4q = estimate_memory_usage(&config, 4);
162        let memory_8q = estimate_memory_usage(&config, 8);
163
164        // Memory should scale exponentially with qubits
165        assert!(memory_8q > memory_4q * 10);
166    }
167
168    #[test]
169    fn test_precision_properties() {
170        assert_eq!(QuantumPrecision::Half.memory_factor(), 0.25);
171        assert_eq!(QuantumPrecision::Single.memory_factor(), 0.5);
172        assert_eq!(QuantumPrecision::Double.memory_factor(), 1.0);
173
174        assert!(QuantumPrecision::Half.typical_error() > QuantumPrecision::Single.typical_error());
175        assert!(
176            QuantumPrecision::Single.typical_error() > QuantumPrecision::Double.typical_error()
177        );
178    }
179
180    #[test]
181    fn test_precision_transitions() {
182        // Test higher precision chain
183        assert_eq!(
184            QuantumPrecision::Half.higher_precision(),
185            Some(QuantumPrecision::BFloat16)
186        );
187        assert_eq!(
188            QuantumPrecision::BFloat16.higher_precision(),
189            Some(QuantumPrecision::TF32)
190        );
191        assert_eq!(
192            QuantumPrecision::TF32.higher_precision(),
193            Some(QuantumPrecision::Single)
194        );
195        assert_eq!(
196            QuantumPrecision::Single.higher_precision(),
197            Some(QuantumPrecision::Double)
198        );
199        assert_eq!(QuantumPrecision::Double.higher_precision(), None);
200
201        // Test lower precision chain
202        assert_eq!(
203            QuantumPrecision::Double.lower_precision(),
204            Some(QuantumPrecision::Single)
205        );
206        assert_eq!(
207            QuantumPrecision::Single.lower_precision(),
208            Some(QuantumPrecision::TF32)
209        );
210        assert_eq!(
211            QuantumPrecision::TF32.lower_precision(),
212            Some(QuantumPrecision::BFloat16)
213        );
214        assert_eq!(
215            QuantumPrecision::BFloat16.lower_precision(),
216            Some(QuantumPrecision::Half)
217        );
218        assert_eq!(QuantumPrecision::Half.lower_precision(), None);
219    }
220
221    #[test]
222    fn test_tensor_core_precisions() {
223        // TF32 and BFloat16 require Tensor Cores
224        assert!(QuantumPrecision::TF32.requires_tensor_cores());
225        assert!(QuantumPrecision::BFloat16.requires_tensor_cores());
226        assert!(!QuantumPrecision::Half.requires_tensor_cores());
227        assert!(!QuantumPrecision::Single.requires_tensor_cores());
228        assert!(!QuantumPrecision::Double.requires_tensor_cores());
229
230        // Test reduced precision check
231        assert!(QuantumPrecision::TF32.is_reduced_precision());
232        assert!(QuantumPrecision::BFloat16.is_reduced_precision());
233        assert!(QuantumPrecision::Half.is_reduced_precision());
234        assert!(!QuantumPrecision::Single.is_reduced_precision());
235        assert!(!QuantumPrecision::Double.is_reduced_precision());
236    }
237
238    #[test]
239    fn test_state_vector_creation() {
240        let state = MixedPrecisionStateVector::new(4, QuantumPrecision::Single);
241        assert_eq!(state.len(), 4);
242        assert_eq!(state.precision(), QuantumPrecision::Single);
243
244        let basis_state =
245            MixedPrecisionStateVector::computational_basis(2, QuantumPrecision::Double);
246        assert_eq!(basis_state.len(), 4);
247        assert_eq!(basis_state.precision(), QuantumPrecision::Double);
248    }
249
250    #[test]
251    fn test_state_vector_operations() {
252        let mut state = MixedPrecisionStateVector::new(4, QuantumPrecision::Single);
253
254        // Test setting and getting amplitudes
255        let amplitude = Complex64::new(0.5, 0.3);
256        assert!(state.set_amplitude(0, amplitude).is_ok());
257
258        // For single precision, we need to account for precision loss
259        let retrieved_amplitude = state
260            .amplitude(0)
261            .expect("should retrieve amplitude at index 0");
262        assert!((retrieved_amplitude.re - amplitude.re).abs() < 1e-6);
263        assert!((retrieved_amplitude.im - amplitude.im).abs() < 1e-6);
264
265        // Test probability calculation
266        let prob = state
267            .probability(0)
268            .expect("should calculate probability at index 0");
269        assert!((prob - amplitude.norm_sqr()).abs() < 1e-6);
270    }
271
272    #[test]
273    fn test_precision_conversion() {
274        let state_single = MixedPrecisionStateVector::new(4, QuantumPrecision::Single);
275        let state_double = state_single.to_precision(QuantumPrecision::Double);
276
277        assert!(state_double.is_ok());
278        let converted = state_double.expect("precision conversion should succeed");
279        assert_eq!(converted.precision(), QuantumPrecision::Double);
280        assert_eq!(converted.len(), 4);
281    }
282
283    #[test]
284    fn test_simulator_creation() {
285        let config = MixedPrecisionConfig::default();
286        let simulator = MixedPrecisionSimulator::new(2, config);
287
288        assert!(simulator.is_ok());
289        let sim = simulator.expect("mixed precision simulator creation should succeed");
290        assert!(sim.get_state().is_some());
291    }
292
293    #[test]
294    fn test_performance_metrics() {
295        let metrics = PerformanceMetrics::new(100.0, 1024, 10.0, 5.0);
296        assert_eq!(metrics.execution_time_ms, 100.0);
297        assert_eq!(metrics.memory_usage_bytes, 1024);
298        assert_eq!(metrics.throughput_ops_per_sec, 10.0);
299        assert_eq!(metrics.energy_efficiency, 5.0);
300
301        let score = metrics.performance_score();
302        assert!((0.0..=1.0).contains(&score));
303    }
304
305    #[test]
306    fn test_precision_analysis() {
307        let mut analysis = PrecisionAnalysis::new();
308        analysis.add_recommendation("test_op".to_string(), QuantumPrecision::Single);
309        analysis.add_error_estimate(QuantumPrecision::Single, 1e-6);
310
311        let metrics = PerformanceMetrics::new(50.0, 512, 20.0, 10.0);
312        analysis.add_performance_metrics(QuantumPrecision::Single, metrics);
313
314        analysis.calculate_quality_score();
315
316        assert_eq!(
317            analysis.get_best_precision("test_op"),
318            Some(QuantumPrecision::Single)
319        );
320        assert!(analysis.quality_score > 0.0);
321    }
322
323    #[test]
324    fn test_analyzer() {
325        let mut analyzer = PrecisionAnalyzer::new();
326        let result = analyzer.analyze_for_tolerance(1e-6);
327        assert!(!result.error_estimates.is_empty());
328        assert!(!result.performance_metrics.is_empty());
329    }
330}