quantrs2_core/optimizations_stable/
adaptive_precision.rs

1//! Adaptive Precision Management for Quantum Simulations
2//!
3//! Dynamically adjusts numerical precision based on circuit characteristics,
4//! error tolerance requirements, and computational resources to optimize
5//! performance while maintaining accuracy.
6
7use crate::error::{QuantRS2Error, QuantRS2Result};
8use scirs2_core::Complex64;
9use std::collections::HashMap;
10
11/// Available precision levels for quantum computations
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub enum PrecisionLevel {
14    /// Single precision (f32) - fastest but least accurate
15    Single,
16    /// Double precision (f64) - standard accuracy
17    Double,
18    /// Extended precision - highest accuracy but slowest
19    Extended,
20    /// Mixed precision - adaptive based on operation
21    Mixed,
22}
23
24impl PrecisionLevel {
25    /// Get the number of significant decimal digits for this precision level
26    pub fn decimal_digits(self) -> usize {
27        match self {
28            PrecisionLevel::Single => 7,
29            PrecisionLevel::Double => 15,
30            PrecisionLevel::Extended => 34,
31            PrecisionLevel::Mixed => 15, // Default to double
32        }
33    }
34
35    /// Get the epsilon (machine precision) for this level
36    pub fn epsilon(self) -> f64 {
37        match self {
38            PrecisionLevel::Single => f32::EPSILON as f64,
39            PrecisionLevel::Double => f64::EPSILON,
40            PrecisionLevel::Extended => 1e-34,
41            PrecisionLevel::Mixed => f64::EPSILON,
42        }
43    }
44
45    /// Get relative performance factor (1.0 = baseline double precision)
46    pub fn performance_factor(self) -> f64 {
47        match self {
48            PrecisionLevel::Single => 1.8,   // ~80% faster
49            PrecisionLevel::Double => 1.0,   // Baseline
50            PrecisionLevel::Extended => 0.3, // ~70% slower
51            PrecisionLevel::Mixed => 1.2,    // ~20% faster on average
52        }
53    }
54}
55
56/// Precision requirements for different quantum operations
57#[derive(Debug, Clone)]
58pub struct PrecisionRequirements {
59    pub min_precision: PrecisionLevel,
60    pub error_tolerance: f64,
61    pub relative_importance: f64, // 0.0 to 1.0
62}
63
64impl Default for PrecisionRequirements {
65    fn default() -> Self {
66        Self {
67            min_precision: PrecisionLevel::Double,
68            error_tolerance: 1e-12,
69            relative_importance: 0.5,
70        }
71    }
72}
73
74/// Adaptive precision manager
75pub struct AdaptivePrecisionManager {
76    /// Current global precision level
77    current_precision: PrecisionLevel,
78    /// Operation-specific precision requirements
79    operation_requirements: HashMap<String, PrecisionRequirements>,
80    /// Accumulated error estimates
81    error_estimates: HashMap<String, f64>,
82    /// Performance statistics
83    statistics: PrecisionStatistics,
84    /// Target error budget
85    global_error_budget: f64,
86}
87
88/// Precision management statistics
89#[derive(Debug, Clone, Default)]
90pub struct PrecisionStatistics {
91    pub total_operations: u64,
92    pub precision_downgrades: u64,
93    pub precision_upgrades: u64,
94    pub mixed_precision_ops: u64,
95    pub error_budget_used: f64,
96    pub average_speedup: f64,
97    pub memory_savings: f64,
98}
99
100impl AdaptivePrecisionManager {
101    /// Create a new adaptive precision manager
102    pub fn new(global_error_budget: f64) -> Self {
103        let mut manager = Self {
104            current_precision: PrecisionLevel::Double,
105            operation_requirements: HashMap::new(),
106            error_estimates: HashMap::new(),
107            statistics: PrecisionStatistics::default(),
108            global_error_budget,
109        };
110
111        // Initialize with common quantum operation requirements
112        manager.initialize_default_requirements();
113        manager
114    }
115
116    /// Initialize default precision requirements for quantum operations
117    fn initialize_default_requirements(&mut self) {
118        let operations = vec![
119            // High-precision operations
120            (
121                "eigenvalue_decomposition",
122                PrecisionRequirements {
123                    min_precision: PrecisionLevel::Double,
124                    error_tolerance: 1e-14,
125                    relative_importance: 0.9,
126                },
127            ),
128            (
129                "quantum_fourier_transform",
130                PrecisionRequirements {
131                    min_precision: PrecisionLevel::Double,
132                    error_tolerance: 1e-13,
133                    relative_importance: 0.8,
134                },
135            ),
136            (
137                "phase_estimation",
138                PrecisionRequirements {
139                    min_precision: PrecisionLevel::Double,
140                    error_tolerance: 1e-12,
141                    relative_importance: 0.9,
142                },
143            ),
144            // Medium-precision operations
145            (
146                "gate_application",
147                PrecisionRequirements {
148                    min_precision: PrecisionLevel::Single,
149                    error_tolerance: 1e-10,
150                    relative_importance: 0.6,
151                },
152            ),
153            (
154                "state_vector_norm",
155                PrecisionRequirements {
156                    min_precision: PrecisionLevel::Single,
157                    error_tolerance: 1e-8,
158                    relative_importance: 0.4,
159                },
160            ),
161            (
162                "expectation_value",
163                PrecisionRequirements {
164                    min_precision: PrecisionLevel::Double,
165                    error_tolerance: 1e-11,
166                    relative_importance: 0.7,
167                },
168            ),
169            // Lower-precision operations
170            (
171                "probability_calculation",
172                PrecisionRequirements {
173                    min_precision: PrecisionLevel::Single,
174                    error_tolerance: 1e-6,
175                    relative_importance: 0.3,
176                },
177            ),
178            (
179                "measurement_sampling",
180                PrecisionRequirements {
181                    min_precision: PrecisionLevel::Single,
182                    error_tolerance: 1e-5,
183                    relative_importance: 0.2,
184                },
185            ),
186        ];
187
188        for (op_name, requirements) in operations {
189            self.operation_requirements
190                .insert(op_name.to_string(), requirements);
191        }
192    }
193
194    /// Determine optimal precision for a specific operation
195    pub fn determine_optimal_precision(
196        &mut self,
197        operation_name: &str,
198        input_size: usize,
199        target_accuracy: Option<f64>,
200    ) -> PrecisionLevel {
201        self.statistics.total_operations += 1;
202
203        // Get operation requirements
204        let requirements = self
205            .operation_requirements
206            .get(operation_name)
207            .cloned()
208            .unwrap_or_default();
209
210        // Factor in target accuracy if provided
211        let effective_tolerance = if let Some(target) = target_accuracy {
212            target.min(requirements.error_tolerance)
213        } else {
214            requirements.error_tolerance
215        };
216
217        // Consider current error budget
218        let remaining_budget = self.global_error_budget - self.statistics.error_budget_used;
219
220        // Size-based precision adjustment
221        let size_factor = self.calculate_size_factor(input_size);
222
223        // Determine precision level
224        let optimal_precision = self.select_precision_level(
225            &requirements,
226            effective_tolerance,
227            remaining_budget,
228            size_factor,
229        );
230
231        // Update statistics
232        self.update_precision_statistics(optimal_precision, &requirements);
233
234        // Track error contribution
235        let estimated_error =
236            self.estimate_operation_error(operation_name, optimal_precision, input_size);
237        self.error_estimates
238            .insert(operation_name.to_string(), estimated_error);
239        self.statistics.error_budget_used += estimated_error;
240
241        optimal_precision
242    }
243
244    /// Calculate size factor for precision adjustment
245    fn calculate_size_factor(&self, input_size: usize) -> f64 {
246        // Larger problems may accumulate more numerical error
247        match input_size {
248            0..=100 => 1.0,
249            101..=1000 => 1.2,
250            1001..=10000 => 1.5,
251            _ => 2.0,
252        }
253    }
254
255    /// Select the appropriate precision level
256    fn select_precision_level(
257        &self,
258        requirements: &PrecisionRequirements,
259        effective_tolerance: f64,
260        remaining_budget: f64,
261        size_factor: f64,
262    ) -> PrecisionLevel {
263        let adjusted_tolerance = effective_tolerance / size_factor;
264
265        // If we're running out of error budget, upgrade precision
266        if remaining_budget < self.global_error_budget * 0.1 {
267            return match requirements.min_precision {
268                PrecisionLevel::Single => PrecisionLevel::Double,
269                PrecisionLevel::Double => PrecisionLevel::Extended,
270                other => other,
271            };
272        }
273
274        // Select based on tolerance requirements
275        if adjusted_tolerance < 1e-14 {
276            PrecisionLevel::Extended
277        } else if adjusted_tolerance < 1e-10 {
278            PrecisionLevel::Double
279        } else if adjusted_tolerance < 1e-6 {
280            PrecisionLevel::Single
281        } else {
282            PrecisionLevel::Single
283        }
284    }
285
286    /// Update precision statistics
287    fn update_precision_statistics(
288        &mut self,
289        chosen_precision: PrecisionLevel,
290        requirements: &PrecisionRequirements,
291    ) {
292        match chosen_precision.cmp(&requirements.min_precision) {
293            std::cmp::Ordering::Less => {
294                // This shouldn't happen in a well-designed system
295            }
296            std::cmp::Ordering::Greater => {
297                self.statistics.precision_upgrades += 1;
298            }
299            std::cmp::Ordering::Equal => {
300                // No change
301            }
302        }
303
304        if chosen_precision == PrecisionLevel::Mixed {
305            self.statistics.mixed_precision_ops += 1;
306        }
307
308        // Update performance metrics
309        let speedup = chosen_precision.performance_factor();
310        self.statistics.average_speedup = (self.statistics.average_speedup
311            * (self.statistics.total_operations - 1) as f64
312            + speedup)
313            / self.statistics.total_operations as f64;
314
315        // Memory savings (rough estimate)
316        let memory_factor = match chosen_precision {
317            PrecisionLevel::Single => 0.5,   // Half the memory of double
318            PrecisionLevel::Double => 1.0,   // Baseline
319            PrecisionLevel::Extended => 2.0, // Double the memory
320            PrecisionLevel::Mixed => 0.75,   // Average savings
321        };
322
323        self.statistics.memory_savings = (self.statistics.memory_savings
324            * (self.statistics.total_operations - 1) as f64
325            + memory_factor)
326            / self.statistics.total_operations as f64;
327    }
328
329    /// Estimate numerical error for an operation at given precision
330    fn estimate_operation_error(
331        &self,
332        operation_name: &str,
333        precision: PrecisionLevel,
334        input_size: usize,
335    ) -> f64 {
336        let base_error = precision.epsilon();
337        let size_scaling = (input_size as f64).log2() * 0.1; // Error grows logarithmically with size
338
339        // Operation-specific error factors
340        let operation_factor = match operation_name {
341            "eigenvalue_decomposition" => 10.0, // Iterative algorithms accumulate error
342            "quantum_fourier_transform" => 5.0, // Many arithmetic operations
343            "matrix_multiplication" => 3.0,     // O(n³) operations
344            "gate_application" => 1.0,          // Simple operations
345            "probability_calculation" => 0.5,   // Usually normalized
346            _ => 2.0,                           // Default factor
347        };
348
349        base_error * operation_factor * (1.0 + size_scaling)
350    }
351
352    /// Get current precision statistics
353    pub fn get_statistics(&self) -> &PrecisionStatistics {
354        &self.statistics
355    }
356
357    /// Reset the error budget and statistics
358    pub fn reset_error_budget(&mut self) {
359        self.statistics.error_budget_used = 0.0;
360        self.error_estimates.clear();
361    }
362
363    /// Check if we're within error budget
364    pub fn within_error_budget(&self) -> bool {
365        self.statistics.error_budget_used < self.global_error_budget
366    }
367
368    /// Get current error budget utilization (0.0 to 1.0)
369    pub fn error_budget_utilization(&self) -> f64 {
370        self.statistics.error_budget_used / self.global_error_budget
371    }
372
373    /// Add custom precision requirements for an operation
374    pub fn set_operation_requirements(
375        &mut self,
376        operation_name: String,
377        requirements: PrecisionRequirements,
378    ) {
379        self.operation_requirements
380            .insert(operation_name, requirements);
381    }
382
383    /// Suggest optimal precision level for a quantum circuit
384    pub fn suggest_circuit_precision(
385        &self,
386        circuit_operations: &[(String, usize)],
387        target_fidelity: f64,
388    ) -> PrecisionLevel {
389        let error_tolerance = 1.0 - target_fidelity;
390        let total_operations = circuit_operations.len();
391
392        // Allocate error budget across operations
393        let per_operation_budget = error_tolerance / total_operations as f64;
394
395        // Find the most stringent precision requirement
396        let mut max_precision = PrecisionLevel::Single;
397
398        for (op_name, size) in circuit_operations {
399            let requirements = self
400                .operation_requirements
401                .get(op_name)
402                .cloned()
403                .unwrap_or_default();
404
405            if per_operation_budget < requirements.error_tolerance {
406                max_precision = match max_precision {
407                    PrecisionLevel::Single => {
408                        if requirements.min_precision != PrecisionLevel::Single {
409                            requirements.min_precision
410                        } else {
411                            PrecisionLevel::Double
412                        }
413                    }
414                    PrecisionLevel::Double => {
415                        if per_operation_budget < 1e-14 {
416                            PrecisionLevel::Extended
417                        } else {
418                            PrecisionLevel::Double
419                        }
420                    }
421                    PrecisionLevel::Extended => PrecisionLevel::Extended,
422                    PrecisionLevel::Mixed => PrecisionLevel::Mixed,
423                };
424            }
425        }
426
427        max_precision
428    }
429}
430
431/// Adapt precision level for a complete quantum circuit
432pub fn adapt_precision_for_circuit(
433    circuit_operations: &[(String, usize)],
434    target_fidelity: f64,
435    error_budget: f64,
436) -> QuantRS2Result<PrecisionLevel> {
437    let manager = AdaptivePrecisionManager::new(error_budget);
438    Ok(manager.suggest_circuit_precision(circuit_operations, target_fidelity))
439}
440
441/// Precision-aware complex number operations
442pub struct PrecisionAwareOps;
443
444impl PrecisionAwareOps {
445    /// Multiply two complex numbers with specified precision
446    pub fn multiply_complex(a: Complex64, b: Complex64, precision: PrecisionLevel) -> Complex64 {
447        match precision {
448            PrecisionLevel::Single => {
449                let a32 = Complex::<f32>::new(a.re as f32, a.im as f32);
450                let b32 = Complex::<f32>::new(b.re as f32, b.im as f32);
451                let result32 = a32 * b32;
452                Complex64::new(result32.re as f64, result32.im as f64)
453            }
454            PrecisionLevel::Double | PrecisionLevel::Mixed => a * b,
455            PrecisionLevel::Extended => {
456                // For extended precision, we'd use a higher-precision library
457                // For now, fall back to double precision
458                a * b
459            }
460        }
461    }
462
463    /// Add two complex numbers with specified precision
464    pub fn add_complex(a: Complex64, b: Complex64, precision: PrecisionLevel) -> Complex64 {
465        match precision {
466            PrecisionLevel::Single => {
467                let a32 = Complex::<f32>::new(a.re as f32, a.im as f32);
468                let b32 = Complex::<f32>::new(b.re as f32, b.im as f32);
469                let result32 = a32 + b32;
470                Complex64::new(result32.re as f64, result32.im as f64)
471            }
472            PrecisionLevel::Double | PrecisionLevel::Mixed => a + b,
473            PrecisionLevel::Extended => a + b,
474        }
475    }
476
477    /// Compute norm with specified precision
478    pub fn norm_complex(a: Complex64, precision: PrecisionLevel) -> f64 {
479        match precision {
480            PrecisionLevel::Single => {
481                let a32 = Complex::<f32>::new(a.re as f32, a.im as f32);
482                a32.norm() as f64
483            }
484            PrecisionLevel::Double | PrecisionLevel::Mixed | PrecisionLevel::Extended => a.norm(),
485        }
486    }
487}
488
489// Import Complex for single precision operations
490use scirs2_core::Complex;
491
492#[cfg(test)]
493mod tests {
494    use super::*;
495
496    #[test]
497    fn test_precision_level_properties() {
498        assert_eq!(PrecisionLevel::Single.decimal_digits(), 7);
499        assert_eq!(PrecisionLevel::Double.decimal_digits(), 15);
500        assert!(
501            PrecisionLevel::Single.performance_factor()
502                > PrecisionLevel::Double.performance_factor()
503        );
504    }
505
506    #[test]
507    fn test_adaptive_precision_manager() {
508        let mut manager = AdaptivePrecisionManager::new(1e-10);
509
510        // Test operation precision determination
511        let precision = manager.determine_optimal_precision("gate_application", 100, Some(1e-8));
512        assert!(precision == PrecisionLevel::Single || precision == PrecisionLevel::Double);
513
514        // Test high-precision requirement
515        let high_precision =
516            manager.determine_optimal_precision("eigenvalue_decomposition", 1000, Some(1e-14));
517        assert!(
518            high_precision == PrecisionLevel::Double || high_precision == PrecisionLevel::Extended
519        );
520    }
521
522    #[test]
523    fn test_error_budget_management() {
524        let mut manager = AdaptivePrecisionManager::new(1e-6);
525
526        // Consume some error budget
527        manager.determine_optimal_precision("gate_application", 100, None);
528        manager.determine_optimal_precision("measurement_sampling", 50, None);
529
530        assert!(manager.error_budget_utilization() > 0.0);
531        assert!(manager.error_budget_utilization() < 1.0);
532    }
533
534    #[test]
535    fn test_circuit_precision_suggestion() {
536        let manager = AdaptivePrecisionManager::new(1e-10);
537
538        let operations = vec![
539            ("gate_application".to_string(), 100),
540            ("measurement_sampling".to_string(), 50),
541            ("expectation_value".to_string(), 200),
542        ];
543
544        let suggested = manager.suggest_circuit_precision(&operations, 0.999);
545        assert!(matches!(
546            suggested,
547            PrecisionLevel::Single | PrecisionLevel::Double
548        ));
549
550        // High fidelity should suggest higher precision
551        let high_fidelity = manager.suggest_circuit_precision(&operations, 0.9999999);
552        assert!(matches!(
553            high_fidelity,
554            PrecisionLevel::Double | PrecisionLevel::Extended
555        ));
556    }
557
558    #[test]
559    fn test_precision_aware_operations() {
560        let a = Complex64::new(1.1234567890123456, 2.9876543210987654);
561        let b = Complex64::new(std::f64::consts::PI, std::f64::consts::SQRT_2);
562
563        let single_result = PrecisionAwareOps::multiply_complex(a, b, PrecisionLevel::Single);
564        let double_result = PrecisionAwareOps::multiply_complex(a, b, PrecisionLevel::Double);
565
566        // Single precision should be less accurate
567        assert!((single_result - double_result).norm() > 0.0);
568
569        // But should be reasonably close for this simple case
570        assert!((single_result - double_result).norm() < 1e-6);
571    }
572
573    #[test]
574    fn test_operation_requirements() {
575        let mut manager = AdaptivePrecisionManager::new(1e-10);
576
577        let custom_req = PrecisionRequirements {
578            min_precision: PrecisionLevel::Extended,
579            error_tolerance: 1e-16,
580            relative_importance: 1.0,
581        };
582
583        manager.set_operation_requirements("custom_operation".to_string(), custom_req);
584
585        let precision = manager.determine_optimal_precision("custom_operation", 1000, None);
586        assert_eq!(precision, PrecisionLevel::Extended);
587    }
588
589    #[test]
590    fn test_size_factor_scaling() {
591        let manager = AdaptivePrecisionManager::new(1e-10);
592
593        let small_factor = manager.calculate_size_factor(50);
594        let large_factor = manager.calculate_size_factor(50000);
595
596        assert!(large_factor > small_factor);
597        assert_eq!(small_factor, 1.0); // Small sizes don't need adjustment
598    }
599}