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 #[must_use]
77 pub fn new() -> Self {
78 Self {
79 recommended_precisions: HashMap::new(),
80 error_estimates: HashMap::new(),
81 performance_metrics: HashMap::new(),
82 selection_rationale: String::new(),
83 quality_score: 0.0,
84 }
85 }
86
87 pub fn add_recommendation(&mut self, operation: String, precision: QuantumPrecision) {
89 self.recommended_precisions.insert(operation, precision);
90 }
91
92 pub fn add_error_estimate(&mut self, precision: QuantumPrecision, error: f64) {
94 self.error_estimates.insert(precision, error);
95 }
96
97 pub fn add_performance_metrics(
99 &mut self,
100 precision: QuantumPrecision,
101 metrics: PerformanceMetrics,
102 ) {
103 self.performance_metrics.insert(precision, metrics);
104 }
105
106 pub fn set_rationale(&mut self, rationale: String) {
108 self.selection_rationale = rationale;
109 }
110
111 pub fn calculate_quality_score(&mut self) {
113 let mut score = 0.0;
114 let mut count = 0;
115
116 for (&precision, &error) in &self.error_estimates {
118 let error_score = 1.0 / error.mul_add(1000.0, 1.0); score += error_score;
120 count += 1;
121 }
122
123 for (precision, metrics) in &self.performance_metrics {
125 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);
128 count += 1;
129 }
130
131 if count > 0 {
132 self.quality_score = (score / f64::from(count)).min(1.0);
133 }
134 }
135
136 #[must_use]
138 pub fn get_best_precision(&self, operation: &str) -> Option<QuantumPrecision> {
139 self.recommended_precisions.get(operation).copied()
140 }
141
142 #[must_use]
144 pub fn get_overall_recommendation(&self) -> QuantumPrecision {
145 let mut precision_counts = HashMap::new();
147 for &precision in self.recommended_precisions.values() {
148 *precision_counts.entry(precision).or_insert(0) += 1;
149 }
150
151 precision_counts
152 .into_iter()
153 .max_by_key(|(_, count)| *count)
154 .map_or(QuantumPrecision::Single, |(precision, _)| precision)
155 }
156
157 #[must_use]
159 pub fn is_high_quality(&self) -> bool {
160 self.quality_score > 0.8
161 }
162
163 #[must_use]
165 pub fn get_summary(&self) -> AnalysisSummary {
166 AnalysisSummary {
167 num_operations_analyzed: self.recommended_precisions.len(),
168 overall_precision: self.get_overall_recommendation(),
169 quality_score: self.quality_score,
170 total_error_estimate: self.error_estimates.values().sum(),
171 avg_execution_time: self
172 .performance_metrics
173 .values()
174 .map(|m| m.execution_time_ms)
175 .sum::<f64>()
176 / self.performance_metrics.len().max(1) as f64,
177 }
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct AnalysisSummary {
184 pub num_operations_analyzed: usize,
186 pub overall_precision: QuantumPrecision,
188 pub quality_score: f64,
190 pub total_error_estimate: f64,
192 pub avg_execution_time: f64,
194}
195
196impl PerformanceMetrics {
197 #[must_use]
199 pub const fn new(
200 execution_time_ms: f64,
201 memory_usage_bytes: usize,
202 throughput_ops_per_sec: f64,
203 energy_efficiency: f64,
204 ) -> Self {
205 Self {
206 execution_time_ms,
207 memory_usage_bytes,
208 throughput_ops_per_sec,
209 energy_efficiency,
210 }
211 }
212
213 #[must_use]
215 pub fn from_time_and_memory(execution_time_ms: f64, memory_usage_bytes: usize) -> Self {
216 let throughput = if execution_time_ms > 0.0 {
217 1000.0 / execution_time_ms
218 } else {
219 0.0
220 };
221
222 let energy_efficiency = throughput / (memory_usage_bytes as f64 / 1e6);
224
225 Self::new(
226 execution_time_ms,
227 memory_usage_bytes,
228 throughput,
229 energy_efficiency,
230 )
231 }
232
233 #[must_use]
235 pub fn performance_score(&self) -> f64 {
236 let time_score = 1.0 / (1.0 + self.execution_time_ms / 1000.0);
237 let memory_score = 1.0 / (1.0 + self.memory_usage_bytes as f64 / 1e9);
238 let throughput_score = self.throughput_ops_per_sec / 1000.0;
239 let energy_score = self.energy_efficiency / 1000.0;
240
241 (time_score + memory_score + throughput_score + energy_score) / 4.0
242 }
243
244 #[must_use]
246 pub fn is_better_than(&self, other: &Self) -> bool {
247 self.performance_score() > other.performance_score()
248 }
249}
250
251impl PrecisionAnalyzer {
252 #[must_use]
254 pub fn new() -> Self {
255 Self {
256 analysis_state: AnalysisState {
257 operations_count: 0,
258 total_time: 0.0,
259 current_precision: QuantumPrecision::Single,
260 },
261 benchmark_cache: HashMap::new(),
262 error_history: Vec::new(),
263 }
264 }
265
266 pub fn analyze_for_tolerance(&mut self, tolerance: f64) -> PrecisionAnalysis {
268 let mut analysis = PrecisionAnalysis::new();
269
270 for &precision in &[
272 QuantumPrecision::Half,
273 QuantumPrecision::Single,
274 QuantumPrecision::Double,
275 ] {
276 let error = precision.typical_error();
277 analysis.add_error_estimate(precision, error);
278
279 let metrics = self.create_synthetic_metrics(precision);
281 analysis.add_performance_metrics(precision, metrics);
282
283 if precision.is_sufficient_for_tolerance(tolerance) {
285 analysis.add_recommendation("default".to_string(), precision);
286 }
287 }
288
289 let rationale = format!(
291 "Analysis for tolerance {tolerance:.2e}: recommended precision based on error bounds and performance trade-offs"
292 );
293 analysis.set_rationale(rationale);
294
295 analysis.calculate_quality_score();
297
298 analysis
299 }
300
301 pub fn benchmark_precision(&mut self, precision: QuantumPrecision) -> PerformanceMetrics {
303 if let Some(cached) = self.benchmark_cache.get(&precision) {
304 return cached.clone();
305 }
306
307 let start_time = Instant::now();
308
309 std::thread::sleep(std::time::Duration::from_millis(1));
311
312 let execution_time = start_time.elapsed().as_millis() as f64;
313 let memory_usage = self.estimate_memory_usage(precision);
314
315 let metrics = PerformanceMetrics::from_time_and_memory(execution_time, memory_usage);
316 self.benchmark_cache.insert(precision, metrics.clone());
317
318 metrics
319 }
320
321 pub fn record_error(
323 &mut self,
324 precision: QuantumPrecision,
325 error: f64,
326 operation_type: String,
327 ) {
328 self.error_history.push(ErrorSample {
329 precision,
330 error,
331 operation_type,
332 timestamp: Instant::now(),
333 });
334 }
335
336 #[must_use]
338 pub fn get_average_error(&self, precision: QuantumPrecision) -> f64 {
339 let errors: Vec<f64> = self
340 .error_history
341 .iter()
342 .filter(|sample| sample.precision == precision)
343 .map(|sample| sample.error)
344 .collect();
345
346 if errors.is_empty() {
347 precision.typical_error()
348 } else {
349 errors.iter().sum::<f64>() / errors.len() as f64
350 }
351 }
352
353 pub fn reset(&mut self) {
355 self.analysis_state.operations_count = 0;
356 self.analysis_state.total_time = 0.0;
357 self.error_history.clear();
358 self.benchmark_cache.clear();
359 }
360
361 fn create_synthetic_metrics(&self, precision: QuantumPrecision) -> PerformanceMetrics {
363 let base_time = 100.0; let execution_time = base_time * precision.computation_factor();
365
366 let base_memory = 1024 * 1024; let memory_usage = (f64::from(base_memory) * precision.memory_factor()) as usize;
368
369 PerformanceMetrics::from_time_and_memory(execution_time, memory_usage)
370 }
371
372 fn estimate_memory_usage(&self, precision: QuantumPrecision) -> usize {
374 let base_memory = 1024 * 1024; (f64::from(base_memory) * precision.memory_factor()) as usize
376 }
377}
378
379impl Default for PrecisionAnalysis {
380 fn default() -> Self {
381 Self::new()
382 }
383}
384
385impl Default for PrecisionAnalyzer {
386 fn default() -> Self {
387 Self::new()
388 }
389}