Skip to main content

quantrs2_sim/
visualization_hooks.rs

1//! Visualization hooks for quantum simulation debugging and analysis.
2//!
3//! This module provides comprehensive visualization capabilities for quantum
4//! simulations, including real-time state visualization, circuit diagrams,
5//! performance plots, and debugging interfaces. It integrates with various
6//! visualization frameworks and provides export capabilities for analysis.
7
8use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
9use scirs2_core::Complex64;
10use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, VecDeque};
12use std::fs::File;
13use std::io::Write as IoWrite;
14use std::path::Path;
15use std::sync::{Arc, Mutex};
16use std::time::{Duration, SystemTime, UNIX_EPOCH};
17
18use crate::circuit_interfaces::{InterfaceCircuit, InterfaceGate, InterfaceGateType};
19use crate::debugger::{ExecutionSnapshot, PerformanceMetrics};
20use crate::error::{Result, SimulatorError};
21
22use std::fmt::Write;
23/// Visualization framework types
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25pub enum VisualizationFramework {
26    /// Matplotlib-compatible output
27    Matplotlib,
28    /// Plotly interactive plots
29    Plotly,
30    /// D3.js web visualization
31    D3JS,
32    /// Custom SVG output
33    SVG,
34    /// ASCII art for terminal
35    ASCII,
36    /// LaTeX/TikZ for publications
37    LaTeX,
38    /// JSON data export
39    JSON,
40}
41
42/// Visualization configuration
43#[derive(Debug, Clone)]
44pub struct VisualizationConfig {
45    /// Target visualization framework
46    pub framework: VisualizationFramework,
47    /// Enable real-time visualization
48    pub real_time: bool,
49    /// Maximum data points to keep in memory
50    pub max_data_points: usize,
51    /// Export directory for plots
52    pub export_directory: String,
53    /// Color scheme for plots
54    pub color_scheme: ColorScheme,
55    /// Enable interactive features
56    pub interactive: bool,
57    /// Plot dimensions (width, height)
58    pub plot_dimensions: (usize, usize),
59    /// Enable animation for time series
60    pub enable_animation: bool,
61}
62
63impl Default for VisualizationConfig {
64    fn default() -> Self {
65        Self {
66            framework: VisualizationFramework::JSON,
67            real_time: false,
68            max_data_points: 10_000,
69            export_directory: "./visualization_output".to_string(),
70            color_scheme: ColorScheme::Default,
71            interactive: false,
72            plot_dimensions: (800, 600),
73            enable_animation: false,
74        }
75    }
76}
77
78/// Color schemes for visualization
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum ColorScheme {
81    Default,
82    Dark,
83    Light,
84    Scientific,
85    Quantum,
86    Accessibility,
87}
88
89/// Visualization data types
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub enum VisualizationData {
92    /// Quantum state vector amplitudes
93    StateVector {
94        amplitudes: Vec<Complex64>,
95        basis_labels: Vec<String>,
96        timestamp: f64,
97    },
98    /// Circuit diagram data
99    CircuitDiagram {
100        gates: Vec<GateVisualizationData>,
101        num_qubits: usize,
102        circuit_depth: usize,
103    },
104    /// Performance metrics over time
105    PerformanceTimeSeries {
106        timestamps: Vec<f64>,
107        execution_times: Vec<f64>,
108        memory_usage: Vec<f64>,
109        gate_counts: Vec<HashMap<String, usize>>,
110    },
111    /// Entanglement structure
112    EntanglementVisualization {
113        entanglement_matrix: Array2<f64>,
114        qubit_labels: Vec<String>,
115        bipartite_entropies: Vec<f64>,
116    },
117    /// Error correction syndrome patterns
118    SyndromePattern {
119        syndrome_data: Vec<Vec<bool>>,
120        error_locations: Vec<usize>,
121        correction_success: bool,
122        timestamp: f64,
123    },
124    /// Optimization landscape
125    OptimizationLandscape {
126        parameter_values: Vec<Vec<f64>>,
127        cost_values: Vec<f64>,
128        gradient_norms: Vec<f64>,
129        parameter_names: Vec<String>,
130    },
131}
132
133/// Gate visualization data
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct GateVisualizationData {
136    pub gate_type: String,
137    pub qubits: Vec<usize>,
138    pub parameters: Vec<f64>,
139    pub position: usize,
140    pub execution_time: Option<f64>,
141    pub label: Option<String>,
142}
143
144/// Visualization hook interface
145pub trait VisualizationHook: Send + Sync {
146    /// Process visualization data
147    fn process_data(&mut self, data: VisualizationData) -> Result<()>;
148
149    /// Export accumulated data
150    fn export(&self, path: &str) -> Result<()>;
151
152    /// Clear accumulated data
153    fn clear(&mut self);
154
155    /// Get visualization framework name
156    fn framework(&self) -> VisualizationFramework;
157}
158
159/// Main visualization manager
160pub struct VisualizationManager {
161    /// Configuration
162    config: VisualizationConfig,
163    /// Registered visualization hooks
164    hooks: Vec<Box<dyn VisualizationHook>>,
165    /// Data buffer for real-time visualization
166    data_buffer: Arc<Mutex<VecDeque<VisualizationData>>>,
167    /// Performance metrics history
168    performance_history: Arc<Mutex<Vec<PerformanceMetrics>>>,
169    /// Active visualizations
170    active_visualizations: HashMap<String, Box<dyn VisualizationHook>>,
171}
172
173impl VisualizationManager {
174    /// Create new visualization manager
175    #[must_use]
176    pub fn new(config: VisualizationConfig) -> Self {
177        Self {
178            config,
179            hooks: Vec::new(),
180            data_buffer: Arc::new(Mutex::new(VecDeque::with_capacity(1000))),
181            performance_history: Arc::new(Mutex::new(Vec::new())),
182            active_visualizations: HashMap::new(),
183        }
184    }
185
186    /// Register a visualization hook
187    pub fn register_hook(&mut self, hook: Box<dyn VisualizationHook>) {
188        self.hooks.push(hook);
189    }
190
191    /// Visualize quantum state
192    pub fn visualize_state(
193        &mut self,
194        state: &Array1<Complex64>,
195        label: Option<String>,
196    ) -> Result<()> {
197        let amplitudes = state.to_vec();
198        let basis_labels = self.generate_basis_labels(state.len());
199        let timestamp = SystemTime::now()
200            .duration_since(UNIX_EPOCH)
201            .map_err(|e| SimulatorError::InvalidOperation(format!("System time error: {e}")))?
202            .as_secs_f64();
203
204        let data = VisualizationData::StateVector {
205            amplitudes,
206            basis_labels,
207            timestamp,
208        };
209
210        self.process_visualization_data(data)?;
211        Ok(())
212    }
213
214    /// Visualize circuit structure
215    pub fn visualize_circuit(&mut self, circuit: &InterfaceCircuit) -> Result<()> {
216        let gates = circuit
217            .gates
218            .iter()
219            .enumerate()
220            .map(|(pos, gate)| GateVisualizationData {
221                gate_type: format!("{:?}", gate.gate_type),
222                qubits: gate.qubits.clone(),
223                parameters: self.extract_gate_parameters(&gate.gate_type),
224                position: pos,
225                execution_time: None,
226                label: gate.label.clone(),
227            })
228            .collect();
229
230        let data = VisualizationData::CircuitDiagram {
231            gates,
232            num_qubits: circuit.num_qubits,
233            circuit_depth: circuit.gates.len(),
234        };
235
236        self.process_visualization_data(data)?;
237        Ok(())
238    }
239
240    /// Visualize performance metrics
241    pub fn visualize_performance(&mut self, metrics: &PerformanceMetrics) -> Result<()> {
242        {
243            let mut history = self
244                .performance_history
245                .lock()
246                .map_err(|e| SimulatorError::InvalidOperation(format!("Lock poisoned: {e}")))?;
247            history.push(metrics.clone());
248
249            // Keep only recent data
250            if history.len() > self.config.max_data_points {
251                history.remove(0);
252            }
253        }
254
255        // Create time series data
256        let data = {
257            let history = self
258                .performance_history
259                .lock()
260                .map_err(|e| SimulatorError::InvalidOperation(format!("Lock poisoned: {e}")))?;
261            let timestamps: Vec<f64> = (0..history.len()).map(|i| i as f64).collect();
262            let execution_times: Vec<f64> =
263                history.iter().map(|m| m.total_time.as_secs_f64()).collect();
264            let memory_usage: Vec<f64> = history
265                .iter()
266                .map(|m| m.memory_usage.peak_statevector_memory as f64)
267                .collect();
268            let gate_counts: Vec<HashMap<String, usize>> =
269                history.iter().map(|m| m.gate_counts.clone()).collect();
270
271            VisualizationData::PerformanceTimeSeries {
272                timestamps,
273                execution_times,
274                memory_usage,
275                gate_counts,
276            }
277        };
278
279        self.process_visualization_data(data)?;
280        Ok(())
281    }
282
283    /// Visualize entanglement structure
284    pub fn visualize_entanglement(
285        &mut self,
286        state: &Array1<Complex64>,
287        qubit_labels: Option<Vec<String>>,
288    ) -> Result<()> {
289        let num_qubits = (state.len() as f64).log2().round() as usize;
290        let labels =
291            qubit_labels.unwrap_or_else(|| (0..num_qubits).map(|i| format!("q{i}")).collect());
292
293        // Calculate entanglement matrix (simplified)
294        let entanglement_matrix = self.calculate_entanglement_matrix(state, num_qubits)?;
295
296        // Calculate bipartite entropies
297        let bipartite_entropies = self.calculate_bipartite_entropies(state, num_qubits)?;
298
299        let data = VisualizationData::EntanglementVisualization {
300            entanglement_matrix,
301            qubit_labels: labels,
302            bipartite_entropies,
303        };
304
305        self.process_visualization_data(data)?;
306        Ok(())
307    }
308
309    /// Visualize syndrome patterns (for error correction)
310    pub fn visualize_syndrome_pattern(
311        &mut self,
312        syndrome_data: Vec<Vec<bool>>,
313        error_locations: Vec<usize>,
314        correction_success: bool,
315    ) -> Result<()> {
316        let timestamp = SystemTime::now()
317            .duration_since(UNIX_EPOCH)
318            .map_err(|e| SimulatorError::InvalidOperation(format!("System time error: {e}")))?
319            .as_secs_f64();
320
321        let data = VisualizationData::SyndromePattern {
322            syndrome_data,
323            error_locations,
324            correction_success,
325            timestamp,
326        };
327
328        self.process_visualization_data(data)?;
329        Ok(())
330    }
331
332    /// Visualize optimization landscape
333    pub fn visualize_optimization_landscape(
334        &mut self,
335        parameter_values: Vec<Vec<f64>>,
336        cost_values: Vec<f64>,
337        gradient_norms: Vec<f64>,
338        parameter_names: Vec<String>,
339    ) -> Result<()> {
340        let data = VisualizationData::OptimizationLandscape {
341            parameter_values,
342            cost_values,
343            gradient_norms,
344            parameter_names,
345        };
346
347        self.process_visualization_data(data)?;
348        Ok(())
349    }
350
351    /// Process visualization data through all hooks
352    fn process_visualization_data(&mut self, data: VisualizationData) -> Result<()> {
353        // Add to buffer for real-time processing
354        if self.config.real_time {
355            let mut buffer = self
356                .data_buffer
357                .lock()
358                .map_err(|e| SimulatorError::InvalidOperation(format!("Lock poisoned: {e}")))?;
359            buffer.push_back(data.clone());
360            if buffer.len() > self.config.max_data_points {
361                buffer.pop_front();
362            }
363        }
364
365        // Process through all hooks
366        for hook in &mut self.hooks {
367            hook.process_data(data.clone())?;
368        }
369
370        Ok(())
371    }
372
373    /// Export all visualization data
374    pub fn export_all(&self, base_path: &str) -> Result<()> {
375        std::fs::create_dir_all(base_path).map_err(|e| {
376            SimulatorError::InvalidInput(format!("Failed to create export directory: {e}"))
377        })?;
378
379        for (i, hook) in self.hooks.iter().enumerate() {
380            let export_path = format!(
381                "{}/visualization_{}.{}",
382                base_path,
383                i,
384                self.get_file_extension(hook.framework())
385            );
386            hook.export(&export_path)?;
387        }
388
389        Ok(())
390    }
391
392    /// Clear all visualization data
393    pub fn clear_all(&mut self) {
394        for hook in &mut self.hooks {
395            hook.clear();
396        }
397
398        if let Ok(mut buffer) = self.data_buffer.lock() {
399            buffer.clear();
400        }
401        if let Ok(mut history) = self.performance_history.lock() {
402            history.clear();
403        }
404    }
405
406    /// Generate basis state labels
407    fn generate_basis_labels(&self, state_size: usize) -> Vec<String> {
408        let num_qubits = (state_size as f64).log2().round() as usize;
409        (0..state_size)
410            .map(|i| format!("|{i:0num_qubits$b}⟩"))
411            .collect()
412    }
413
414    /// Extract parameters from gate type
415    fn extract_gate_parameters(&self, gate_type: &InterfaceGateType) -> Vec<f64> {
416        match gate_type {
417            InterfaceGateType::Phase(angle) => vec![*angle],
418            InterfaceGateType::RX(angle) => vec![*angle],
419            InterfaceGateType::RY(angle) => vec![*angle],
420            InterfaceGateType::RZ(angle) => vec![*angle],
421            InterfaceGateType::U1(angle) => vec![*angle],
422            InterfaceGateType::U2(theta, phi) => vec![*theta, *phi],
423            InterfaceGateType::U3(theta, phi, lambda) => vec![*theta, *phi, *lambda],
424            InterfaceGateType::CRX(angle) => vec![*angle],
425            InterfaceGateType::CRY(angle) => vec![*angle],
426            InterfaceGateType::CRZ(angle) => vec![*angle],
427            InterfaceGateType::CPhase(angle) => vec![*angle],
428            _ => Vec::new(),
429        }
430    }
431
432    /// Calculate entanglement matrix between qubits
433    fn calculate_entanglement_matrix(
434        &self,
435        state: &Array1<Complex64>,
436        num_qubits: usize,
437    ) -> Result<Array2<f64>> {
438        let mut entanglement_matrix = Array2::zeros((num_qubits, num_qubits));
439
440        // Simplified entanglement measure (mutual information approximation)
441        for i in 0..num_qubits {
442            for j in i..num_qubits {
443                if i == j {
444                    entanglement_matrix[[i, j]] = 1.0;
445                } else {
446                    // Calculate mutual information between qubits i and j
447                    let mutual_info = self.calculate_mutual_information(state, i, j, num_qubits)?;
448                    entanglement_matrix[[i, j]] = mutual_info;
449                    entanglement_matrix[[j, i]] = mutual_info;
450                }
451            }
452        }
453
454        Ok(entanglement_matrix)
455    }
456
457    /// Calculate bipartite entropies for different cuts
458    fn calculate_bipartite_entropies(
459        &self,
460        state: &Array1<Complex64>,
461        num_qubits: usize,
462    ) -> Result<Vec<f64>> {
463        let mut entropies = Vec::new();
464
465        for cut in 1..num_qubits {
466            let entropy = self.calculate_bipartite_entropy(state, cut, num_qubits)?;
467            entropies.push(entropy);
468        }
469
470        Ok(entropies)
471    }
472
473    /// Calculate mutual information `I(i:j) = S(rho_i) + S(rho_j) - S(rho_ij)`
474    /// between two qubits from the true reduced density matrices of the
475    /// given state (not a fixed placeholder): traces out every other qubit
476    /// to get the single-qubit marginals `rho_i`/`rho_j` and the two-qubit
477    /// marginal `rho_ij`, then computes the real von Neumann entropy of
478    /// each via Hermitian eigendecomposition.
479    fn calculate_mutual_information(
480        &self,
481        state: &Array1<Complex64>,
482        qubit_i: usize,
483        qubit_j: usize,
484        num_qubits: usize,
485    ) -> Result<f64> {
486        if qubit_i >= num_qubits || qubit_j >= num_qubits {
487            return Err(SimulatorError::InvalidQubitIndex {
488                index: qubit_i.max(qubit_j),
489                num_qubits,
490            });
491        }
492
493        let rho_i = Self::reduced_density_matrix(state, &[qubit_i], num_qubits);
494        let rho_j = Self::reduced_density_matrix(state, &[qubit_j], num_qubits);
495        let rho_ij = Self::reduced_density_matrix(state, &[qubit_i, qubit_j], num_qubits);
496
497        let s_i = Self::von_neumann_entropy(&rho_i)?;
498        let s_j = Self::von_neumann_entropy(&rho_j)?;
499        let s_ij = Self::von_neumann_entropy(&rho_ij)?;
500
501        // Subadditivity guarantees I(i:j) >= 0 in theory; clamp away tiny
502        // negative values coming from floating-point noise in the
503        // eigendecomposition rather than reporting a spurious negative.
504        Ok((s_i + s_j - s_ij).max(0.0))
505    }
506
507    /// Compute the reduced density matrix for `subset_qubits` by tracing
508    /// out every other qubit of `state`. `subset_qubits[0]` is the
509    /// most-significant index bit of the returned `2^k x 2^k` matrix,
510    /// where `k = subset_qubits.len()`.
511    fn reduced_density_matrix(
512        state: &Array1<Complex64>,
513        subset_qubits: &[usize],
514        num_qubits: usize,
515    ) -> Array2<Complex64> {
516        let k = subset_qubits.len();
517        let dim = 1usize << k;
518        let other_qubits: Vec<usize> = (0..num_qubits)
519            .filter(|q| !subset_qubits.contains(q))
520            .collect();
521        let num_other = other_qubits.len();
522
523        let mut rho = Array2::zeros((dim, dim));
524        let mut amps = vec![Complex64::new(0.0, 0.0); dim];
525
526        for other_bits in 0..(1usize << num_other) {
527            let mut base = 0usize;
528            for (bit_pos, &q) in other_qubits.iter().enumerate() {
529                if (other_bits >> bit_pos) & 1 == 1 {
530                    base |= 1 << q;
531                }
532            }
533
534            for combo in 0..dim {
535                let mut idx = base;
536                for (i, &q) in subset_qubits.iter().enumerate() {
537                    if (combo >> (k - 1 - i)) & 1 == 1 {
538                        idx |= 1 << q;
539                    }
540                }
541                amps[combo] = if idx < state.len() {
542                    state[idx]
543                } else {
544                    Complex64::new(0.0, 0.0)
545                };
546            }
547
548            for a in 0..dim {
549                for b in 0..dim {
550                    rho[[a, b]] += amps[a] * amps[b].conj();
551                }
552            }
553        }
554
555        rho
556    }
557
558    /// Von Neumann entropy `S(rho) = -sum_k lambda_k ln(lambda_k)` of a
559    /// density matrix, computed from a real Hermitian eigendecomposition
560    /// (not approximated from the diagonal alone).
561    fn von_neumann_entropy(rho: &Array2<Complex64>) -> Result<f64> {
562        let dim = rho.nrows();
563
564        // Symmetrize to guard against floating-point asymmetry tripping
565        // the eigensolver's strict Hermiticity check.
566        let mut symmetrized = Array2::zeros((dim, dim));
567        for a in 0..dim {
568            for b in 0..dim {
569                symmetrized[[a, b]] = (rho[[a, b]] + rho[[b, a]].conj()) * Complex64::new(0.5, 0.0);
570            }
571        }
572
573        let decomposition =
574            scirs2_linalg::complex::complex_eigh(&symmetrized.view()).map_err(|e| {
575                SimulatorError::InvalidInput(format!(
576                    "reduced-density-matrix eigendecomposition failed: {e}"
577                ))
578            })?;
579
580        let mut entropy = 0.0;
581        for &eigenvalue in decomposition.eigenvalues.iter() {
582            // A Hermitian density matrix has real eigenvalues; `complex_eigh`
583            // still returns them as `Complex<f64>` with (up to floating-point
584            // noise) zero imaginary part, so take the real part. Eigenvalues
585            // can also be slightly negative/greater-than-one due to floating
586            // point noise; clamp into the physical [0, 1] range.
587            let p = eigenvalue.re.clamp(0.0, 1.0);
588            if p > 1e-12 {
589                entropy -= p * p.ln();
590            }
591        }
592        Ok(entropy)
593    }
594
595    /// Calculate bipartite entropy for a given cut
596    fn calculate_bipartite_entropy(
597        &self,
598        state: &Array1<Complex64>,
599        cut: usize,
600        num_qubits: usize,
601    ) -> Result<f64> {
602        // Simplified von Neumann entropy calculation
603        let left_size = 1 << cut;
604        let right_size = 1 << (num_qubits - cut);
605
606        // Calculate reduced density matrix for left subsystem (simplified)
607        let mut reduced_dm = Array2::zeros((left_size, left_size));
608
609        for i in 0..left_size {
610            for j in 0..left_size {
611                let mut trace_val = Complex64::new(0.0, 0.0);
612                for k in 0..right_size {
613                    let idx1 = i * right_size + k;
614                    let idx2 = j * right_size + k;
615                    if idx1 < state.len() && idx2 < state.len() {
616                        trace_val += state[idx1] * state[idx2].conj();
617                    }
618                }
619                reduced_dm[[i, j]] = trace_val;
620            }
621        }
622
623        // Calculate von Neumann entropy (simplified)
624        let mut entropy = 0.0;
625        for i in 0..left_size {
626            let eigenval = reduced_dm[[i, i]].norm();
627            if eigenval > 1e-10 {
628                entropy -= eigenval * eigenval.ln();
629            }
630        }
631
632        Ok(entropy)
633    }
634
635    /// Get file extension for visualization framework
636    const fn get_file_extension(&self, framework: VisualizationFramework) -> &str {
637        match framework {
638            VisualizationFramework::Matplotlib => "py",
639            VisualizationFramework::Plotly => "html",
640            VisualizationFramework::D3JS => "html",
641            VisualizationFramework::SVG => "svg",
642            VisualizationFramework::ASCII => "txt",
643            VisualizationFramework::LaTeX => "tex",
644            VisualizationFramework::JSON => "json",
645        }
646    }
647}
648
649/// JSON export visualization hook
650pub struct JSONVisualizationHook {
651    /// Accumulated visualization data
652    data: Vec<VisualizationData>,
653    /// Export configuration
654    config: VisualizationConfig,
655}
656
657impl JSONVisualizationHook {
658    #[must_use]
659    pub const fn new(config: VisualizationConfig) -> Self {
660        Self {
661            data: Vec::new(),
662            config,
663        }
664    }
665}
666
667impl VisualizationHook for JSONVisualizationHook {
668    fn process_data(&mut self, data: VisualizationData) -> Result<()> {
669        self.data.push(data);
670
671        // Keep only recent data
672        if self.data.len() > self.config.max_data_points {
673            self.data.remove(0);
674        }
675
676        Ok(())
677    }
678
679    fn export(&self, path: &str) -> Result<()> {
680        let json_data = serde_json::to_string_pretty(&self.data)
681            .map_err(|e| SimulatorError::InvalidInput(format!("Failed to serialize data: {e}")))?;
682
683        let mut file = File::create(path)
684            .map_err(|e| SimulatorError::InvalidInput(format!("Failed to create file: {e}")))?;
685
686        file.write_all(json_data.as_bytes())
687            .map_err(|e| SimulatorError::InvalidInput(format!("Failed to write file: {e}")))?;
688
689        Ok(())
690    }
691
692    fn clear(&mut self) {
693        self.data.clear();
694    }
695
696    fn framework(&self) -> VisualizationFramework {
697        VisualizationFramework::JSON
698    }
699}
700
701/// ASCII visualization hook for terminal output
702pub struct ASCIIVisualizationHook {
703    /// Recent state vectors for display
704    recent_states: VecDeque<Array1<Complex64>>,
705    /// Configuration
706    config: VisualizationConfig,
707}
708
709impl ASCIIVisualizationHook {
710    #[must_use]
711    pub fn new(config: VisualizationConfig) -> Self {
712        Self {
713            recent_states: VecDeque::with_capacity(100),
714            config,
715        }
716    }
717
718    /// Generate ASCII representation of quantum state
719    fn state_to_ascii(&self, state: &Array1<Complex64>) -> String {
720        let mut output = String::new();
721        output.push_str("Quantum State Visualization:\n");
722        output.push_str("==========================\n");
723
724        for (i, amplitude) in state.iter().enumerate().take(16) {
725            let magnitude = amplitude.norm();
726            let phase = amplitude.arg();
727
728            let bar_length = (magnitude * 20.0) as usize;
729            let bar = "█".repeat(bar_length) + &"░".repeat(20 - bar_length);
730
731            writeln!(
732                output,
733                "|{:02}⟩: {} {:.4} ∠{:.2}π",
734                i,
735                bar,
736                magnitude,
737                phase / std::f64::consts::PI
738            )
739            .expect("Failed to write to string buffer");
740        }
741
742        if state.len() > 16 {
743            writeln!(output, "... ({} more states)", state.len() - 16)
744                .expect("Failed to write to string buffer");
745        }
746
747        output
748    }
749}
750
751impl VisualizationHook for ASCIIVisualizationHook {
752    fn process_data(&mut self, data: VisualizationData) -> Result<()> {
753        match data {
754            VisualizationData::StateVector { amplitudes, .. } => {
755                let state = Array1::from_vec(amplitudes);
756
757                if self.config.real_time {
758                    println!("{}", self.state_to_ascii(&state));
759                }
760
761                self.recent_states.push_back(state);
762                if self.recent_states.len() > 100 {
763                    self.recent_states.pop_front();
764                }
765            }
766            VisualizationData::CircuitDiagram {
767                gates, num_qubits, ..
768            } => {
769                if self.config.real_time {
770                    println!(
771                        "Circuit Diagram ({} qubits, {} gates):",
772                        num_qubits,
773                        gates.len()
774                    );
775                    for gate in gates.iter().take(10) {
776                        println!("  {} on qubits {:?}", gate.gate_type, gate.qubits);
777                    }
778                    if gates.len() > 10 {
779                        println!("  ... ({} more gates)", gates.len() - 10);
780                    }
781                }
782            }
783            _ => {
784                // Handle other data types
785            }
786        }
787
788        Ok(())
789    }
790
791    fn export(&self, path: &str) -> Result<()> {
792        let mut output = String::new();
793        output.push_str("ASCII Visualization Export\n");
794        output.push_str("==========================\n\n");
795
796        for (i, state) in self.recent_states.iter().enumerate() {
797            writeln!(output, "State {i}:").expect("Failed to write to string buffer");
798            output.push_str(&self.state_to_ascii(state));
799            output.push('\n');
800        }
801
802        let mut file = File::create(path)
803            .map_err(|e| SimulatorError::InvalidInput(format!("Failed to create file: {e}")))?;
804
805        file.write_all(output.as_bytes())
806            .map_err(|e| SimulatorError::InvalidInput(format!("Failed to write file: {e}")))?;
807
808        Ok(())
809    }
810
811    fn clear(&mut self) {
812        self.recent_states.clear();
813    }
814
815    fn framework(&self) -> VisualizationFramework {
816        VisualizationFramework::ASCII
817    }
818}
819
820/// Benchmark visualization performance
821pub fn benchmark_visualization() -> Result<HashMap<String, f64>> {
822    let mut results = HashMap::new();
823
824    // Test JSON hook performance
825    let start = std::time::Instant::now();
826    let mut json_hook = JSONVisualizationHook::new(VisualizationConfig::default());
827
828    for i in 0..1000 {
829        let test_state = Array1::from_vec(vec![
830            Complex64::new(0.5, 0.0),
831            Complex64::new(0.5, 0.0),
832            Complex64::new(0.5, 0.0),
833            Complex64::new(0.5, 0.0),
834        ]);
835
836        let data = VisualizationData::StateVector {
837            amplitudes: test_state.to_vec(),
838            basis_labels: vec![
839                "00".to_string(),
840                "01".to_string(),
841                "10".to_string(),
842                "11".to_string(),
843            ],
844            timestamp: f64::from(i),
845        };
846
847        json_hook.process_data(data)?;
848    }
849
850    let json_time = start.elapsed().as_millis() as f64;
851    results.insert("json_hook_1000_states".to_string(), json_time);
852
853    // Test ASCII hook performance
854    let start = std::time::Instant::now();
855    let mut ascii_hook = ASCIIVisualizationHook::new(VisualizationConfig {
856        real_time: false,
857        ..Default::default()
858    });
859
860    for i in 0..100 {
861        let test_state = Array1::from_vec(vec![
862            Complex64::new(0.5, 0.0),
863            Complex64::new(0.5, 0.0),
864            Complex64::new(0.5, 0.0),
865            Complex64::new(0.5, 0.0),
866        ]);
867
868        let data = VisualizationData::StateVector {
869            amplitudes: test_state.to_vec(),
870            basis_labels: vec![
871                "00".to_string(),
872                "01".to_string(),
873                "10".to_string(),
874                "11".to_string(),
875            ],
876            timestamp: f64::from(i),
877        };
878
879        ascii_hook.process_data(data)?;
880    }
881
882    let ascii_time = start.elapsed().as_millis() as f64;
883    results.insert("ascii_hook_100_states".to_string(), ascii_time);
884
885    Ok(results)
886}
887
888#[cfg(test)]
889mod tests {
890    use super::*;
891    use approx::assert_abs_diff_eq;
892
893    #[test]
894    fn test_visualization_manager_creation() {
895        let config = VisualizationConfig::default();
896        let manager = VisualizationManager::new(config);
897        assert_eq!(manager.hooks.len(), 0);
898    }
899
900    #[test]
901    fn test_json_hook() {
902        let mut hook = JSONVisualizationHook::new(VisualizationConfig::default());
903
904        let test_data = VisualizationData::StateVector {
905            amplitudes: vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
906            basis_labels: vec!["0".to_string(), "1".to_string()],
907            timestamp: 0.0,
908        };
909
910        assert!(hook.process_data(test_data).is_ok());
911        assert_eq!(hook.data.len(), 1);
912    }
913
914    #[test]
915    fn test_ascii_hook() {
916        let mut hook = ASCIIVisualizationHook::new(VisualizationConfig {
917            real_time: false,
918            ..Default::default()
919        });
920
921        let test_data = VisualizationData::StateVector {
922            amplitudes: vec![Complex64::new(0.707, 0.0), Complex64::new(0.707, 0.0)],
923            basis_labels: vec!["0".to_string(), "1".to_string()],
924            timestamp: 0.0,
925        };
926
927        assert!(hook.process_data(test_data).is_ok());
928        assert_eq!(hook.recent_states.len(), 1);
929    }
930
931    #[test]
932    fn test_state_visualization() {
933        let config = VisualizationConfig::default();
934        let mut manager = VisualizationManager::new(config);
935
936        let test_state = Array1::from_vec(vec![
937            Complex64::new(0.5, 0.0),
938            Complex64::new(0.5, 0.0),
939            Complex64::new(0.5, 0.0),
940            Complex64::new(0.5, 0.0),
941        ]);
942
943        assert!(manager.visualize_state(&test_state, None).is_ok());
944    }
945
946    #[test]
947    fn test_circuit_visualization() {
948        let config = VisualizationConfig::default();
949        let mut manager = VisualizationManager::new(config);
950
951        let mut circuit = InterfaceCircuit::new(2, 0);
952        circuit.add_gate(InterfaceGate::new(InterfaceGateType::Hadamard, vec![0]));
953        circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![0, 1]));
954
955        assert!(manager.visualize_circuit(&circuit).is_ok());
956    }
957
958    #[test]
959    fn test_parameter_extraction() {
960        let config = VisualizationConfig::default();
961        let manager = VisualizationManager::new(config);
962
963        let params = manager.extract_gate_parameters(&InterfaceGateType::RX(1.5));
964        assert_eq!(params, vec![1.5]);
965
966        let params = manager.extract_gate_parameters(&InterfaceGateType::U3(1.0, 2.0, 3.0));
967        assert_eq!(params, vec![1.0, 2.0, 3.0]);
968
969        let params = manager.extract_gate_parameters(&InterfaceGateType::Hadamard);
970        assert_eq!(params.len(), 0);
971    }
972
973    #[test]
974    fn test_basis_label_generation() {
975        let config = VisualizationConfig::default();
976        let manager = VisualizationManager::new(config);
977
978        let labels = manager.generate_basis_labels(4);
979        assert_eq!(labels, vec!["|00⟩", "|01⟩", "|10⟩", "|11⟩"]);
980
981        let labels = manager.generate_basis_labels(8);
982        assert_eq!(labels.len(), 8);
983        assert_eq!(labels[0], "|000⟩");
984        assert_eq!(labels[7], "|111⟩");
985    }
986
987    #[test]
988    fn test_entanglement_calculation() {
989        let config = VisualizationConfig::default();
990        let manager = VisualizationManager::new(config);
991
992        let bell_state = Array1::from_vec(vec![
993            Complex64::new(0.707, 0.0),
994            Complex64::new(0.0, 0.0),
995            Complex64::new(0.0, 0.0),
996            Complex64::new(0.707, 0.0),
997        ]);
998
999        let entanglement_matrix = manager
1000            .calculate_entanglement_matrix(&bell_state, 2)
1001            .expect("Entanglement calculation should succeed in test");
1002        assert_eq!(entanglement_matrix.shape(), [2, 2]);
1003
1004        let entropies = manager
1005            .calculate_bipartite_entropies(&bell_state, 2)
1006            .expect("Entropy calculation should succeed in test");
1007        assert_eq!(entropies.len(), 1);
1008    }
1009}