quantrs2_sim/mixed_precision_impl/
analysis.rs1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::time::Instant;
9
10use super::config::QuantumPrecision;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct PrecisionAnalysis {
15 pub recommended_precisions: HashMap<String, QuantumPrecision>,
17 pub error_estimates: HashMap<QuantumPrecision, f64>,
19 pub performance_metrics: HashMap<QuantumPrecision, PerformanceMetrics>,
21 pub selection_rationale: String,
23 pub quality_score: f64,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct PerformanceMetrics {
30 pub execution_time_ms: f64,
32 pub memory_usage_bytes: usize,
34 pub throughput_ops_per_sec: f64,
36 pub energy_efficiency: f64,
38}
39
40pub struct PrecisionAnalyzer {
42 analysis_state: AnalysisState,
44 benchmark_cache: HashMap<QuantumPrecision, PerformanceMetrics>,
46 error_history: Vec<ErrorSample>,
48}
49
50#[derive(Debug, Clone)]
52struct AnalysisState {
53 operations_count: usize,
55 total_time: f64,
57 current_precision: QuantumPrecision,
59}
60
61#[derive(Debug, Clone)]
63struct ErrorSample {
64 precision: QuantumPrecision,
66 error: f64,
68 operation_type: String,
70 timestamp: Instant,
72}
73
74impl PrecisionAnalysis {
75 pub fn new() -> Self {
77 Self {
78 recommended_precisions: HashMap::new(),
79 error_estimates: HashMap::new(),
80 performance_metrics: HashMap::new(),
81 selection_rationale: String::new(),
82 quality_score: 0.0,
83 }
84 }
85
86 pub fn add_recommendation(&mut self, operation: String, precision: QuantumPrecision) {
88 self.recommended_precisions.insert(operation, precision);
89 }
90
91 pub fn add_error_estimate(&mut self, precision: QuantumPrecision, error: f64) {
93 self.error_estimates.insert(precision, error);
94 }
95
96 pub fn add_performance_metrics(
98 &mut self,
99 precision: QuantumPrecision,
100 metrics: PerformanceMetrics,
101 ) {
102 self.performance_metrics.insert(precision, metrics);
103 }
104
105 pub fn set_rationale(&mut self, rationale: String) {
107 self.selection_rationale = rationale;
108 }
109
110 pub fn calculate_quality_score(&mut self) {
112 let mut score = 0.0;
113 let mut count = 0;
114
115 for (&precision, &error) in &self.error_estimates {
117 let error_score = 1.0 / (1.0 + error * 1000.0); score += error_score;
119 count += 1;
120 }
121
122 for (precision, metrics) in &self.performance_metrics {
124 let perf_score = metrics.throughput_ops_per_sec / 1000.0; let mem_score = 1.0 / (1.0 + metrics.memory_usage_bytes as f64 / 1e9); score += (perf_score + mem_score) / 2.0;
127 count += 1;
128 }
129
130 if count > 0 {
131 self.quality_score = (score / count as f64).min(1.0);
132 }
133 }
134
135 pub fn get_best_precision(&self, operation: &str) -> Option<QuantumPrecision> {
137 self.recommended_precisions.get(operation).copied()
138 }
139
140 pub fn get_overall_recommendation(&self) -> QuantumPrecision {
142 let mut precision_counts = HashMap::new();
144 for &precision in self.recommended_precisions.values() {
145 *precision_counts.entry(precision).or_insert(0) += 1;
146 }
147
148 precision_counts
149 .into_iter()
150 .max_by_key(|(_, count)| *count)
151 .map(|(precision, _)| precision)
152 .unwrap_or(QuantumPrecision::Single)
153 }
154
155 pub fn is_high_quality(&self) -> bool {
157 self.quality_score > 0.8
158 }
159
160 pub fn get_summary(&self) -> AnalysisSummary {
162 AnalysisSummary {
163 num_operations_analyzed: self.recommended_precisions.len(),
164 overall_precision: self.get_overall_recommendation(),
165 quality_score: self.quality_score,
166 total_error_estimate: self.error_estimates.values().sum(),
167 avg_execution_time: self
168 .performance_metrics
169 .values()
170 .map(|m| m.execution_time_ms)
171 .sum::<f64>()
172 / self.performance_metrics.len().max(1) as f64,
173 }
174 }
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct AnalysisSummary {
180 pub num_operations_analyzed: usize,
182 pub overall_precision: QuantumPrecision,
184 pub quality_score: f64,
186 pub total_error_estimate: f64,
188 pub avg_execution_time: f64,
190}
191
192impl PerformanceMetrics {
193 pub fn new(
195 execution_time_ms: f64,
196 memory_usage_bytes: usize,
197 throughput_ops_per_sec: f64,
198 energy_efficiency: f64,
199 ) -> Self {
200 Self {
201 execution_time_ms,
202 memory_usage_bytes,
203 throughput_ops_per_sec,
204 energy_efficiency,
205 }
206 }
207
208 pub fn from_time_and_memory(execution_time_ms: f64, memory_usage_bytes: usize) -> Self {
210 let throughput = if execution_time_ms > 0.0 {
211 1000.0 / execution_time_ms
212 } else {
213 0.0
214 };
215
216 let energy_efficiency = throughput / (memory_usage_bytes as f64 / 1e6);
218
219 Self::new(
220 execution_time_ms,
221 memory_usage_bytes,
222 throughput,
223 energy_efficiency,
224 )
225 }
226
227 pub fn performance_score(&self) -> f64 {
229 let time_score = 1.0 / (1.0 + self.execution_time_ms / 1000.0);
230 let memory_score = 1.0 / (1.0 + self.memory_usage_bytes as f64 / 1e9);
231 let throughput_score = self.throughput_ops_per_sec / 1000.0;
232 let energy_score = self.energy_efficiency / 1000.0;
233
234 (time_score + memory_score + throughput_score + energy_score) / 4.0
235 }
236
237 pub fn is_better_than(&self, other: &Self) -> bool {
239 self.performance_score() > other.performance_score()
240 }
241}
242
243impl PrecisionAnalyzer {
244 pub fn new() -> Self {
246 Self {
247 analysis_state: AnalysisState {
248 operations_count: 0,
249 total_time: 0.0,
250 current_precision: QuantumPrecision::Single,
251 },
252 benchmark_cache: HashMap::new(),
253 error_history: Vec::new(),
254 }
255 }
256
257 pub fn analyze_for_tolerance(&mut self, tolerance: f64) -> PrecisionAnalysis {
259 let mut analysis = PrecisionAnalysis::new();
260
261 for &precision in &[
263 QuantumPrecision::Half,
264 QuantumPrecision::Single,
265 QuantumPrecision::Double,
266 ] {
267 let error = precision.typical_error();
268 analysis.add_error_estimate(precision, error);
269
270 let metrics = self.create_synthetic_metrics(precision);
272 analysis.add_performance_metrics(precision, metrics);
273
274 if precision.is_sufficient_for_tolerance(tolerance) {
276 analysis.add_recommendation("default".to_string(), precision);
277 }
278 }
279
280 let rationale = format!(
282 "Analysis for tolerance {:.2e}: recommended precision based on error bounds and performance trade-offs",
283 tolerance
284 );
285 analysis.set_rationale(rationale);
286
287 analysis.calculate_quality_score();
289
290 analysis
291 }
292
293 pub fn benchmark_precision(&mut self, precision: QuantumPrecision) -> PerformanceMetrics {
295 if let Some(cached) = self.benchmark_cache.get(&precision) {
296 return cached.clone();
297 }
298
299 let start_time = Instant::now();
300
301 std::thread::sleep(std::time::Duration::from_millis(1));
303
304 let execution_time = start_time.elapsed().as_millis() as f64;
305 let memory_usage = self.estimate_memory_usage(precision);
306
307 let metrics = PerformanceMetrics::from_time_and_memory(execution_time, memory_usage);
308 self.benchmark_cache.insert(precision, metrics.clone());
309
310 metrics
311 }
312
313 pub fn record_error(
315 &mut self,
316 precision: QuantumPrecision,
317 error: f64,
318 operation_type: String,
319 ) {
320 self.error_history.push(ErrorSample {
321 precision,
322 error,
323 operation_type,
324 timestamp: Instant::now(),
325 });
326 }
327
328 pub fn get_average_error(&self, precision: QuantumPrecision) -> f64 {
330 let errors: Vec<f64> = self
331 .error_history
332 .iter()
333 .filter(|sample| sample.precision == precision)
334 .map(|sample| sample.error)
335 .collect();
336
337 if errors.is_empty() {
338 precision.typical_error()
339 } else {
340 errors.iter().sum::<f64>() / errors.len() as f64
341 }
342 }
343
344 pub fn reset(&mut self) {
346 self.analysis_state.operations_count = 0;
347 self.analysis_state.total_time = 0.0;
348 self.error_history.clear();
349 self.benchmark_cache.clear();
350 }
351
352 fn create_synthetic_metrics(&self, precision: QuantumPrecision) -> PerformanceMetrics {
354 let base_time = 100.0; let execution_time = base_time * precision.computation_factor();
356
357 let base_memory = 1024 * 1024; let memory_usage = (base_memory as f64 * precision.memory_factor()) as usize;
359
360 PerformanceMetrics::from_time_and_memory(execution_time, memory_usage)
361 }
362
363 fn estimate_memory_usage(&self, precision: QuantumPrecision) -> usize {
365 let base_memory = 1024 * 1024; (base_memory as f64 * precision.memory_factor()) as usize
367 }
368}
369
370impl Default for PrecisionAnalysis {
371 fn default() -> Self {
372 Self::new()
373 }
374}
375
376impl Default for PrecisionAnalyzer {
377 fn default() -> Self {
378 Self::new()
379 }
380}