quantrs2_sim/mixed_precision_impl/
config.rs

1//! Configuration structures for mixed-precision quantum simulation.
2//!
3//! This module provides configuration types for precision levels,
4//! adaptive strategies, and performance optimization settings.
5
6use serde::{Deserialize, Serialize};
7
8use crate::error::{Result, SimulatorError};
9
10// Note: scirs2_linalg mixed_precision module temporarily unavailable
11// #[cfg(feature = "advanced_math")]
12// use scirs2_linalg::mixed_precision::{AdaptiveStrategy, MixedPrecisionContext, PrecisionLevel};
13
14// Placeholder types when the feature is not available
15#[derive(Debug)]
16pub struct MixedPrecisionContext;
17
18#[derive(Debug)]
19pub enum PrecisionLevel {
20    F16,
21    F32,
22    F64,
23    Adaptive,
24}
25
26#[derive(Debug)]
27pub enum AdaptiveStrategy {
28    ErrorBased(f64),
29    Fixed(PrecisionLevel),
30}
31
32impl MixedPrecisionContext {
33    pub fn new(_strategy: AdaptiveStrategy) -> Result<Self> {
34        Err(SimulatorError::UnsupportedOperation(
35            "Mixed precision context not available without advanced_math feature".to_string(),
36        ))
37    }
38}
39
40/// Precision levels for quantum computations
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
42pub enum QuantumPrecision {
43    /// Half precision (16-bit floats)
44    Half,
45    /// Single precision (32-bit floats)
46    Single,
47    /// Double precision (64-bit floats)
48    Double,
49    /// Adaptive precision (automatically selected)
50    Adaptive,
51}
52
53impl QuantumPrecision {
54    /// Get the corresponding SciRS2 precision level
55    #[cfg(feature = "advanced_math")]
56    pub const fn to_scirs2_precision(&self) -> PrecisionLevel {
57        match self {
58            Self::Half => PrecisionLevel::F16,
59            Self::Single => PrecisionLevel::F32,
60            Self::Double => PrecisionLevel::F64,
61            Self::Adaptive => PrecisionLevel::Adaptive,
62        }
63    }
64
65    /// Get memory usage factor relative to double precision
66    #[must_use]
67    pub const fn memory_factor(&self) -> f64 {
68        match self {
69            Self::Half => 0.25,
70            Self::Single => 0.5,
71            Self::Double => 1.0,
72            Self::Adaptive => 0.75, // Average case
73        }
74    }
75
76    /// Get computational cost factor relative to double precision
77    #[must_use]
78    pub const fn computation_factor(&self) -> f64 {
79        match self {
80            Self::Half => 0.5,
81            Self::Single => 0.7,
82            Self::Double => 1.0,
83            Self::Adaptive => 0.8, // Average case
84        }
85    }
86
87    /// Get typical numerical error for this precision
88    #[must_use]
89    pub const fn typical_error(&self) -> f64 {
90        match self {
91            Self::Half => 1e-3,
92            Self::Single => 1e-6,
93            Self::Double => 1e-15,
94            Self::Adaptive => 1e-6, // Conservative estimate
95        }
96    }
97
98    /// Check if this precision is sufficient for the given error tolerance
99    #[must_use]
100    pub fn is_sufficient_for_tolerance(&self, tolerance: f64) -> bool {
101        self.typical_error() <= tolerance * 10.0 // Safety factor of 10
102    }
103
104    /// Get the next higher precision level
105    #[must_use]
106    pub const fn higher_precision(&self) -> Option<Self> {
107        match self {
108            Self::Half => Some(Self::Single),
109            Self::Single => Some(Self::Double),
110            Self::Double => None,
111            Self::Adaptive => Some(Self::Double),
112        }
113    }
114
115    /// Get the next lower precision level
116    #[must_use]
117    pub const fn lower_precision(&self) -> Option<Self> {
118        match self {
119            Self::Half => None,
120            Self::Single => Some(Self::Half),
121            Self::Double => Some(Self::Single),
122            Self::Adaptive => Some(Self::Single),
123        }
124    }
125}
126
127/// Mixed precision configuration
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct MixedPrecisionConfig {
130    /// Default precision for state vectors
131    pub state_vector_precision: QuantumPrecision,
132    /// Default precision for gate operations
133    pub gate_precision: QuantumPrecision,
134    /// Default precision for measurements
135    pub measurement_precision: QuantumPrecision,
136    /// Error tolerance for precision selection
137    pub error_tolerance: f64,
138    /// Enable automatic precision adaptation
139    pub adaptive_precision: bool,
140    /// Minimum precision level (never go below this)
141    pub min_precision: QuantumPrecision,
142    /// Maximum precision level (never go above this)
143    pub max_precision: QuantumPrecision,
144    /// Number of qubits threshold for precision reduction
145    pub large_system_threshold: usize,
146    /// Enable precision analysis and reporting
147    pub enable_analysis: bool,
148}
149
150impl Default for MixedPrecisionConfig {
151    fn default() -> Self {
152        Self {
153            state_vector_precision: QuantumPrecision::Single,
154            gate_precision: QuantumPrecision::Single,
155            measurement_precision: QuantumPrecision::Double,
156            error_tolerance: 1e-6,
157            adaptive_precision: true,
158            min_precision: QuantumPrecision::Half,
159            max_precision: QuantumPrecision::Double,
160            large_system_threshold: 20,
161            enable_analysis: true,
162        }
163    }
164}
165
166impl MixedPrecisionConfig {
167    /// Create configuration optimized for accuracy
168    #[must_use]
169    pub const fn for_accuracy() -> Self {
170        Self {
171            state_vector_precision: QuantumPrecision::Double,
172            gate_precision: QuantumPrecision::Double,
173            measurement_precision: QuantumPrecision::Double,
174            error_tolerance: 1e-12,
175            adaptive_precision: false,
176            min_precision: QuantumPrecision::Double,
177            max_precision: QuantumPrecision::Double,
178            large_system_threshold: 50,
179            enable_analysis: true,
180        }
181    }
182
183    /// Create configuration optimized for performance
184    #[must_use]
185    pub const fn for_performance() -> Self {
186        Self {
187            state_vector_precision: QuantumPrecision::Half,
188            gate_precision: QuantumPrecision::Single,
189            measurement_precision: QuantumPrecision::Single,
190            error_tolerance: 1e-3,
191            adaptive_precision: true,
192            min_precision: QuantumPrecision::Half,
193            max_precision: QuantumPrecision::Single,
194            large_system_threshold: 10,
195            enable_analysis: false,
196        }
197    }
198
199    /// Create configuration balanced between accuracy and performance
200    #[must_use]
201    pub fn balanced() -> Self {
202        Self::default()
203    }
204
205    /// Validate the configuration
206    pub fn validate(&self) -> Result<()> {
207        if self.error_tolerance <= 0.0 {
208            return Err(SimulatorError::InvalidInput(
209                "Error tolerance must be positive".to_string(),
210            ));
211        }
212
213        if self.large_system_threshold == 0 {
214            return Err(SimulatorError::InvalidInput(
215                "Large system threshold must be positive".to_string(),
216            ));
217        }
218
219        // Check precision consistency
220        if self.min_precision as u8 > self.max_precision as u8 {
221            return Err(SimulatorError::InvalidInput(
222                "Minimum precision cannot be higher than maximum precision".to_string(),
223            ));
224        }
225
226        Ok(())
227    }
228
229    /// Adjust configuration for a specific number of qubits
230    pub const fn adjust_for_qubits(&mut self, num_qubits: usize) {
231        if num_qubits >= self.large_system_threshold {
232            // For large systems, reduce precision to save memory
233            if self.adaptive_precision {
234                match self.state_vector_precision {
235                    QuantumPrecision::Double => {
236                        self.state_vector_precision = QuantumPrecision::Single;
237                    }
238                    QuantumPrecision::Single => {
239                        self.state_vector_precision = QuantumPrecision::Half;
240                    }
241                    _ => {}
242                }
243            }
244        }
245    }
246
247    /// Estimate memory usage for a given number of qubits
248    #[must_use]
249    pub fn estimate_memory_usage(&self, num_qubits: usize) -> usize {
250        let state_vector_size = 1 << num_qubits;
251        let base_memory = state_vector_size * 16; // Complex64 size
252
253        let factor = self.state_vector_precision.memory_factor();
254        (f64::from(base_memory) * factor) as usize
255    }
256
257    /// Check if the configuration is suitable for the available memory
258    #[must_use]
259    pub fn fits_in_memory(&self, num_qubits: usize, available_memory: usize) -> bool {
260        self.estimate_memory_usage(num_qubits) <= available_memory
261    }
262}