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 / error.mul_add(1000.0, 1.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 += f64::midpoint(perf_score, mem_score);
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_or(QuantumPrecision::Single, |(precision, _)| precision)
152 }
153
154 pub fn is_high_quality(&self) -> bool {
156 self.quality_score > 0.8
157 }
158
159 pub fn get_summary(&self) -> AnalysisSummary {
161 AnalysisSummary {
162 num_operations_analyzed: self.recommended_precisions.len(),
163 overall_precision: self.get_overall_recommendation(),
164 quality_score: self.quality_score,
165 total_error_estimate: self.error_estimates.values().sum(),
166 avg_execution_time: self
167 .performance_metrics
168 .values()
169 .map(|m| m.execution_time_ms)
170 .sum::<f64>()
171 / self.performance_metrics.len().max(1) as f64,
172 }
173 }
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct AnalysisSummary {
179 pub num_operations_analyzed: usize,
181 pub overall_precision: QuantumPrecision,
183 pub quality_score: f64,
185 pub total_error_estimate: f64,
187 pub avg_execution_time: f64,
189}
190
191impl PerformanceMetrics {
192 pub const fn new(
194 execution_time_ms: f64,
195 memory_usage_bytes: usize,
196 throughput_ops_per_sec: f64,
197 energy_efficiency: f64,
198 ) -> Self {
199 Self {
200 execution_time_ms,
201 memory_usage_bytes,
202 throughput_ops_per_sec,
203 energy_efficiency,
204 }
205 }
206
207 pub fn from_time_and_memory(execution_time_ms: f64, memory_usage_bytes: usize) -> Self {
209 let throughput = if execution_time_ms > 0.0 {
210 1000.0 / execution_time_ms
211 } else {
212 0.0
213 };
214
215 let energy_efficiency = throughput / (memory_usage_bytes as f64 / 1e6);
217
218 Self::new(
219 execution_time_ms,
220 memory_usage_bytes,
221 throughput,
222 energy_efficiency,
223 )
224 }
225
226 pub fn performance_score(&self) -> f64 {
228 let time_score = 1.0 / (1.0 + self.execution_time_ms / 1000.0);
229 let memory_score = 1.0 / (1.0 + self.memory_usage_bytes as f64 / 1e9);
230 let throughput_score = self.throughput_ops_per_sec / 1000.0;
231 let energy_score = self.energy_efficiency / 1000.0;
232
233 (time_score + memory_score + throughput_score + energy_score) / 4.0
234 }
235
236 pub fn is_better_than(&self, other: &Self) -> bool {
238 self.performance_score() > other.performance_score()
239 }
240}
241
242impl PrecisionAnalyzer {
243 pub fn new() -> Self {
245 Self {
246 analysis_state: AnalysisState {
247 operations_count: 0,
248 total_time: 0.0,
249 current_precision: QuantumPrecision::Single,
250 },
251 benchmark_cache: HashMap::new(),
252 error_history: Vec::new(),
253 }
254 }
255
256 pub fn analyze_for_tolerance(&mut self, tolerance: f64) -> PrecisionAnalysis {
258 let mut analysis = PrecisionAnalysis::new();
259
260 for &precision in &[
262 QuantumPrecision::Half,
263 QuantumPrecision::Single,
264 QuantumPrecision::Double,
265 ] {
266 let error = precision.typical_error();
267 analysis.add_error_estimate(precision, error);
268
269 let metrics = self.create_synthetic_metrics(precision);
271 analysis.add_performance_metrics(precision, metrics);
272
273 if precision.is_sufficient_for_tolerance(tolerance) {
275 analysis.add_recommendation("default".to_string(), precision);
276 }
277 }
278
279 let rationale = format!(
281 "Analysis for tolerance {tolerance:.2e}: recommended precision based on error bounds and performance trade-offs"
282 );
283 analysis.set_rationale(rationale);
284
285 analysis.calculate_quality_score();
287
288 analysis
289 }
290
291 pub fn benchmark_precision(&mut self, precision: QuantumPrecision) -> PerformanceMetrics {
293 if let Some(cached) = self.benchmark_cache.get(&precision) {
294 return cached.clone();
295 }
296
297 let start_time = Instant::now();
298
299 std::thread::sleep(std::time::Duration::from_millis(1));
301
302 let execution_time = start_time.elapsed().as_millis() as f64;
303 let memory_usage = self.estimate_memory_usage(precision);
304
305 let metrics = PerformanceMetrics::from_time_and_memory(execution_time, memory_usage);
306 self.benchmark_cache.insert(precision, metrics.clone());
307
308 metrics
309 }
310
311 pub fn record_error(
313 &mut self,
314 precision: QuantumPrecision,
315 error: f64,
316 operation_type: String,
317 ) {
318 self.error_history.push(ErrorSample {
319 precision,
320 error,
321 operation_type,
322 timestamp: Instant::now(),
323 });
324 }
325
326 pub fn get_average_error(&self, precision: QuantumPrecision) -> f64 {
328 let errors: Vec<f64> = self
329 .error_history
330 .iter()
331 .filter(|sample| sample.precision == precision)
332 .map(|sample| sample.error)
333 .collect();
334
335 if errors.is_empty() {
336 precision.typical_error()
337 } else {
338 errors.iter().sum::<f64>() / errors.len() as f64
339 }
340 }
341
342 pub fn reset(&mut self) {
344 self.analysis_state.operations_count = 0;
345 self.analysis_state.total_time = 0.0;
346 self.error_history.clear();
347 self.benchmark_cache.clear();
348 }
349
350 fn create_synthetic_metrics(&self, precision: QuantumPrecision) -> PerformanceMetrics {
352 let base_time = 100.0; let execution_time = base_time * precision.computation_factor();
354
355 let base_memory = 1024 * 1024; let memory_usage = (base_memory as f64 * precision.memory_factor()) as usize;
357
358 PerformanceMetrics::from_time_and_memory(execution_time, memory_usage)
359 }
360
361 fn estimate_memory_usage(&self, precision: QuantumPrecision) -> usize {
363 let base_memory = 1024 * 1024; (base_memory as f64 * precision.memory_factor()) as usize
365 }
366}
367
368impl Default for PrecisionAnalysis {
369 fn default() -> Self {
370 Self::new()
371 }
372}
373
374impl Default for PrecisionAnalyzer {
375 fn default() -> Self {
376 Self::new()
377 }
378}