1use crate::error::{QuantRS2Error, QuantRS2Result};
8use scirs2_core::Complex64;
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub enum PrecisionLevel {
14 Single,
16 Double,
18 Extended,
20 Mixed,
22}
23
24impl PrecisionLevel {
25 pub const fn decimal_digits(self) -> usize {
27 match self {
28 Self::Single => 7,
29 Self::Double | Self::Mixed => 15, Self::Extended => 34,
31 }
32 }
33
34 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 pub const fn performance_factor(self) -> f64 {
45 match self {
46 Self::Single => 1.8, Self::Double => 1.0, Self::Extended => 0.3, Self::Mixed => 1.2, }
51 }
52}
53
54#[derive(Debug, Clone)]
56pub struct PrecisionRequirements {
57 pub min_precision: PrecisionLevel,
58 pub error_tolerance: f64,
59 pub relative_importance: f64, }
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
72pub struct AdaptivePrecisionManager {
74 current_precision: PrecisionLevel,
76 operation_requirements: HashMap<String, PrecisionRequirements>,
78 error_estimates: HashMap<String, f64>,
80 statistics: PrecisionStatistics,
82 global_error_budget: f64,
84}
85
86#[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 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 manager.initialize_default_requirements();
111 manager
112 }
113
114 fn initialize_default_requirements(&mut self) {
116 let operations = vec![
117 (
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 (
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 (
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 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 let requirements = self
203 .operation_requirements
204 .get(operation_name)
205 .cloned()
206 .unwrap_or_default();
207
208 let effective_tolerance = if let Some(target) = target_accuracy {
210 target.min(requirements.error_tolerance)
211 } else {
212 requirements.error_tolerance
213 };
214
215 let remaining_budget = self.global_error_budget - self.statistics.error_budget_used;
217
218 let size_factor = self.calculate_size_factor(input_size);
220
221 let optimal_precision = self.select_precision_level(
223 &requirements,
224 effective_tolerance,
225 remaining_budget,
226 size_factor,
227 );
228
229 self.update_precision_statistics(optimal_precision, &requirements);
231
232 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 const fn calculate_size_factor(&self, input_size: usize) -> f64 {
244 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 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 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 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 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 }
297 }
298
299 if chosen_precision == PrecisionLevel::Mixed {
300 self.statistics.mixed_precision_ops += 1;
301 }
302
303 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 let memory_factor = match chosen_precision {
313 PrecisionLevel::Single => 0.5, PrecisionLevel::Double => 1.0, PrecisionLevel::Extended => 2.0, PrecisionLevel::Mixed => 0.75, };
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 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; let operation_factor = match operation_name {
338 "eigenvalue_decomposition" => 10.0, "quantum_fourier_transform" => 5.0, "matrix_multiplication" => 3.0, "gate_application" => 1.0, "probability_calculation" => 0.5, _ => 2.0, };
345
346 base_error * operation_factor * (1.0 + size_scaling)
347 }
348
349 pub const fn get_statistics(&self) -> &PrecisionStatistics {
351 &self.statistics
352 }
353
354 pub fn reset_error_budget(&mut self) {
356 self.statistics.error_budget_used = 0.0;
357 self.error_estimates.clear();
358 }
359
360 pub fn within_error_budget(&self) -> bool {
362 self.statistics.error_budget_used < self.global_error_budget
363 }
364
365 pub fn error_budget_utilization(&self) -> f64 {
367 self.statistics.error_budget_used / self.global_error_budget
368 }
369
370 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 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 let per_operation_budget = error_tolerance / total_operations as f64;
391
392 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
428pub 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
438pub struct PrecisionAwareOps;
440
441impl PrecisionAwareOps {
442 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 a * b
456 }
457 }
458 }
459
460 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 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
485use 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 let precision = manager.determine_optimal_precision("gate_application", 100, Some(1e-8));
508 assert!(precision == PrecisionLevel::Single || precision == PrecisionLevel::Double);
509
510 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 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 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 assert!((single_result - double_result).norm() > 0.0);
564
565 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); }
595}