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