Skip to main content

quantrs2_sim/
debugger.rs

1//! Quantum algorithm debugger interface.
2//!
3//! This module provides comprehensive debugging capabilities for quantum algorithms,
4//! including step-by-step execution, state inspection, breakpoints, and analysis tools.
5
6use scirs2_core::ndarray::{Array1, Array2};
7use scirs2_core::Complex64;
8use serde::{Deserialize, Serialize};
9use std::collections::{HashMap, HashSet, VecDeque};
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12
13use crate::error::{Result, SimulatorError};
14#[cfg(feature = "mps")]
15use crate::mps_enhanced::{EnhancedMPS, MPSConfig};
16use crate::statevector::StateVectorSimulator;
17use quantrs2_circuit::builder::{Circuit, Simulator};
18use quantrs2_core::gate::GateOp;
19
20// Placeholder for MPSConfig when MPS feature is disabled
21#[cfg(not(feature = "mps"))]
22#[derive(Debug, Clone, Default)]
23pub struct MPSConfig {
24    pub max_bond_dim: usize,
25    pub tolerance: f64,
26}
27
28/// Breakpoint condition types
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub enum BreakCondition {
31    /// Break at specific gate index
32    GateIndex(usize),
33    /// Break when a qubit reaches a certain state
34    QubitState { qubit: usize, state: bool },
35    /// Break when entanglement entropy exceeds threshold
36    EntanglementThreshold { cut: usize, threshold: f64 },
37    /// Break when fidelity with target state drops below threshold
38    FidelityThreshold {
39        target_state: Vec<Complex64>,
40        threshold: f64,
41    },
42    /// Break when a Pauli observable expectation value crosses threshold
43    ObservableThreshold {
44        observable: String,
45        threshold: f64,
46        direction: ThresholdDirection,
47    },
48    /// Break when circuit depth exceeds limit
49    CircuitDepth(usize),
50    /// Break when execution time exceeds limit
51    ExecutionTime(Duration),
52}
53
54/// Threshold crossing direction
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub enum ThresholdDirection {
57    Above,
58    Below,
59    Either,
60}
61
62/// Execution snapshot at a specific point
63#[derive(Debug, Clone)]
64pub struct ExecutionSnapshot {
65    /// Gate index in the circuit
66    pub gate_index: usize,
67    /// Current quantum state
68    pub state: Array1<Complex64>,
69    /// Timestamp
70    pub timestamp: Instant,
71    /// Gate that was just executed (None for initial state)
72    pub last_gate: Option<Arc<dyn GateOp + Send + Sync>>,
73    /// Cumulative gate count by type
74    pub gate_counts: HashMap<String, usize>,
75    /// Entanglement entropies at different cuts
76    pub entanglement_entropies: Vec<f64>,
77    /// Circuit depth so far
78    pub circuit_depth: usize,
79}
80
81/// Performance metrics during execution
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct PerformanceMetrics {
84    /// Total execution time
85    pub total_time: Duration,
86    /// Time per gate type
87    pub gate_times: HashMap<String, Duration>,
88    /// Memory usage statistics
89    pub memory_usage: MemoryUsage,
90    /// Gate execution counts
91    pub gate_counts: HashMap<String, usize>,
92    /// Average entanglement entropy
93    pub avg_entanglement: f64,
94    /// Maximum entanglement entropy reached
95    pub max_entanglement: f64,
96    /// Number of snapshots taken
97    pub snapshot_count: usize,
98}
99
100/// Memory usage tracking
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct MemoryUsage {
103    /// Peak state vector memory (bytes)
104    pub peak_statevector_memory: usize,
105    /// Current MPS bond dimensions
106    pub mps_bond_dims: Vec<usize>,
107    /// Peak MPS memory (bytes)
108    pub peak_mps_memory: usize,
109    /// Debugger overhead (bytes)
110    pub debugger_overhead: usize,
111}
112
113/// Watchpoint for monitoring specific properties
114#[derive(Debug, Clone)]
115pub struct Watchpoint {
116    /// Unique identifier
117    pub id: String,
118    /// Description
119    pub description: String,
120    /// Property to watch
121    pub property: WatchProperty,
122    /// Logging frequency
123    pub frequency: WatchFrequency,
124    /// History of watched values
125    pub history: VecDeque<(usize, f64)>, // (gate_index, value)
126}
127
128/// Properties that can be watched
129#[derive(Debug, Clone)]
130pub enum WatchProperty {
131    /// Total probability (should be 1)
132    Normalization,
133    /// Entanglement entropy at specific cut
134    EntanglementEntropy(usize),
135    /// Expectation value of Pauli observable
136    PauliExpectation(String),
137    /// Fidelity with reference state
138    Fidelity(Array1<Complex64>),
139    /// Average gate fidelity
140    GateFidelity,
141    /// Circuit depth
142    CircuitDepth,
143    /// MPS bond dimension
144    MPSBondDimension,
145}
146
147/// Watch frequency
148#[derive(Debug, Clone)]
149pub enum WatchFrequency {
150    /// Watch at every gate
151    EveryGate,
152    /// Watch every N gates
153    EveryNGates(usize),
154    /// Watch at specific gate indices
155    AtGates(HashSet<usize>),
156}
157
158/// Debugging session configuration
159#[derive(Debug, Clone)]
160pub struct DebugConfig {
161    /// Whether to store full state snapshots
162    pub store_snapshots: bool,
163    /// Maximum number of snapshots to keep
164    pub max_snapshots: usize,
165    /// Whether to track performance metrics
166    pub track_performance: bool,
167    /// Whether to enable automatic state validation
168    pub validate_state: bool,
169    /// Entanglement entropy cut positions to monitor
170    pub entropy_cuts: Vec<usize>,
171    /// Use MPS representation for large systems
172    pub use_mps: bool,
173    /// MPS configuration if used
174    pub mps_config: Option<MPSConfig>,
175}
176
177impl Default for DebugConfig {
178    fn default() -> Self {
179        Self {
180            store_snapshots: true,
181            max_snapshots: 100,
182            track_performance: true,
183            validate_state: true,
184            entropy_cuts: vec![],
185            use_mps: false,
186            mps_config: None,
187        }
188    }
189}
190
191/// Main quantum algorithm debugger
192pub struct QuantumDebugger<const N: usize> {
193    /// Configuration
194    config: DebugConfig,
195    /// Current circuit being debugged
196    circuit: Option<Circuit<N>>,
197    /// Active breakpoints
198    breakpoints: Vec<BreakCondition>,
199    /// Active watchpoints
200    watchpoints: HashMap<String, Watchpoint>,
201    /// Execution snapshots
202    snapshots: VecDeque<ExecutionSnapshot>,
203    /// Performance metrics
204    metrics: PerformanceMetrics,
205    /// Current execution state
206    execution_state: ExecutionState,
207    /// State vector simulator
208    simulator: StateVectorSimulator,
209    /// MPS simulator (if enabled)
210    #[cfg(feature = "mps")]
211    mps_simulator: Option<EnhancedMPS>,
212    /// Current gate index
213    current_gate: usize,
214    /// Execution start time
215    start_time: Option<Instant>,
216}
217
218/// Current execution state
219#[derive(Debug, Clone)]
220enum ExecutionState {
221    /// Not running
222    Idle,
223    /// Running normally
224    Running,
225    /// Paused at breakpoint
226    Paused { reason: String },
227    /// Finished execution
228    Finished,
229    /// Error occurred
230    Error { message: String },
231}
232
233impl<const N: usize> QuantumDebugger<N> {
234    /// Create a new quantum debugger
235    pub fn new(config: DebugConfig) -> Result<Self> {
236        let simulator = StateVectorSimulator::new();
237
238        #[cfg(feature = "mps")]
239        let mps_simulator = if config.use_mps {
240            Some(EnhancedMPS::new(
241                N,
242                config.mps_config.clone().unwrap_or_default(),
243            ))
244        } else {
245            None
246        };
247
248        Ok(Self {
249            config,
250            circuit: None,
251            breakpoints: Vec::new(),
252            watchpoints: HashMap::new(),
253            snapshots: VecDeque::new(),
254            metrics: PerformanceMetrics {
255                total_time: Duration::new(0, 0),
256                gate_times: HashMap::new(),
257                memory_usage: MemoryUsage {
258                    peak_statevector_memory: 0,
259                    mps_bond_dims: vec![],
260                    peak_mps_memory: 0,
261                    debugger_overhead: 0,
262                },
263                gate_counts: HashMap::new(),
264                avg_entanglement: 0.0,
265                max_entanglement: 0.0,
266                snapshot_count: 0,
267            },
268            execution_state: ExecutionState::Idle,
269            simulator,
270            #[cfg(feature = "mps")]
271            mps_simulator,
272            current_gate: 0,
273            start_time: None,
274        })
275    }
276
277    /// Load a circuit for debugging
278    pub fn load_circuit(&mut self, circuit: Circuit<N>) -> Result<()> {
279        self.circuit = Some(circuit);
280        self.reset();
281        Ok(())
282    }
283
284    /// Reset debugger state
285    pub fn reset(&mut self) {
286        self.snapshots.clear();
287        self.metrics = PerformanceMetrics {
288            total_time: Duration::new(0, 0),
289            gate_times: HashMap::new(),
290            memory_usage: MemoryUsage {
291                peak_statevector_memory: 0,
292                mps_bond_dims: vec![],
293                peak_mps_memory: 0,
294                debugger_overhead: 0,
295            },
296            gate_counts: HashMap::new(),
297            avg_entanglement: 0.0,
298            max_entanglement: 0.0,
299            snapshot_count: 0,
300        };
301        self.execution_state = ExecutionState::Idle;
302        self.current_gate = 0;
303        self.start_time = None;
304
305        // Reset simulator to |0...0> state
306        self.simulator = StateVectorSimulator::new();
307        #[cfg(feature = "mps")]
308        if let Some(ref mut mps) = self.mps_simulator {
309            *mps = EnhancedMPS::new(N, self.config.mps_config.clone().unwrap_or_default());
310        }
311
312        // Clear watchpoint histories
313        for watchpoint in self.watchpoints.values_mut() {
314            watchpoint.history.clear();
315        }
316    }
317
318    /// Add a breakpoint
319    pub fn add_breakpoint(&mut self, condition: BreakCondition) {
320        self.breakpoints.push(condition);
321    }
322
323    /// Remove a breakpoint
324    pub fn remove_breakpoint(&mut self, index: usize) -> Result<()> {
325        if index >= self.breakpoints.len() {
326            return Err(SimulatorError::IndexOutOfBounds(index));
327        }
328        self.breakpoints.remove(index);
329        Ok(())
330    }
331
332    /// Add a watchpoint
333    pub fn add_watchpoint(&mut self, watchpoint: Watchpoint) {
334        self.watchpoints.insert(watchpoint.id.clone(), watchpoint);
335    }
336
337    /// Remove a watchpoint
338    pub fn remove_watchpoint(&mut self, id: &str) -> Result<()> {
339        if self.watchpoints.remove(id).is_none() {
340            return Err(SimulatorError::InvalidInput(format!(
341                "Watchpoint '{id}' not found"
342            )));
343        }
344        Ok(())
345    }
346
347    /// Execute the circuit step by step
348    pub fn step(&mut self) -> Result<StepResult> {
349        let circuit = self
350            .circuit
351            .as_ref()
352            .ok_or_else(|| SimulatorError::InvalidOperation("No circuit loaded".to_string()))?;
353
354        if self.current_gate >= circuit.gates().len() {
355            self.execution_state = ExecutionState::Finished;
356            return Ok(StepResult::Finished);
357        }
358
359        // Check if we're paused
360        if let ExecutionState::Paused { .. } = self.execution_state {
361            // Continue from pause
362            self.execution_state = ExecutionState::Running;
363        }
364
365        // Start timing if first step
366        if self.start_time.is_none() {
367            self.start_time = Some(Instant::now());
368            self.execution_state = ExecutionState::Running;
369        }
370
371        // Get gate information before borrowing mutably
372        let gate_name = circuit.gates()[self.current_gate].name().to_string();
373        let total_gates = circuit.gates().len();
374
375        // Execute the current gate
376        let gate_start = Instant::now();
377
378        // Apply gate to appropriate simulator.
379        //
380        // The MPS backend evolves incrementally and is mutated in place here. The
381        // state-vector backend, by contrast, does not need per-gate mutation: its
382        // current amplitudes are reconstructed on demand by replaying the executed
383        // prefix (gates `0..current_gate`) in `get_current_state`. Advancing
384        // `self.current_gate` below is therefore sufficient to expose the correct
385        // state for the state-vector path.
386        #[cfg(feature = "mps")]
387        if let Some(ref mut mps) = self.mps_simulator {
388            mps.apply_gate(circuit.gates()[self.current_gate].as_ref())?;
389        }
390
391        let gate_time = gate_start.elapsed();
392
393        // Update metrics
394        *self
395            .metrics
396            .gate_times
397            .entry(gate_name.clone())
398            .or_insert(Duration::new(0, 0)) += gate_time;
399        *self.metrics.gate_counts.entry(gate_name).or_insert(0) += 1;
400
401        // Check watchpoints
402        self.update_watchpoints()?;
403
404        // Take snapshot if configured
405        if self.config.store_snapshots {
406            self.take_snapshot()?;
407        }
408
409        // Check breakpoints
410        if let Some(reason) = self.check_breakpoints()? {
411            self.execution_state = ExecutionState::Paused {
412                reason: reason.clone(),
413            };
414            return Ok(StepResult::BreakpointHit { reason });
415        }
416
417        self.current_gate += 1;
418
419        if self.current_gate >= total_gates {
420            self.execution_state = ExecutionState::Finished;
421            if let Some(start) = self.start_time {
422                self.metrics.total_time = start.elapsed();
423            }
424            Ok(StepResult::Finished)
425        } else {
426            Ok(StepResult::Continue)
427        }
428    }
429
430    /// Run until next breakpoint or completion
431    pub fn run(&mut self) -> Result<StepResult> {
432        loop {
433            match self.step()? {
434                StepResult::Continue => {}
435                result => return Ok(result),
436            }
437        }
438    }
439
440    /// Get current quantum state
441    ///
442    /// Returns the full `2^N` amplitude vector after the gates that have been
443    /// executed so far (`self.current_gate` gates from the loaded circuit).
444    ///
445    /// The state is reconstructed by replaying the executed prefix of the
446    /// circuit through the embedded [`StateVectorSimulator`]. This always
447    /// reflects the real amplitudes; if no circuit is loaded, the simulator
448    /// is in the initial `|0…0⟩` state, which is returned as a genuine state
449    /// vector (amplitude 1 on the zero basis state) rather than a fabricated
450    /// all-zero vector.
451    pub fn get_current_state(&self) -> Result<Array1<Complex64>> {
452        #[cfg(feature = "mps")]
453        if let Some(ref mps) = self.mps_simulator {
454            return mps
455                .to_statevector()
456                .map_err(|e| SimulatorError::UnsupportedOperation(format!("MPS error: {e}")));
457        }
458
459        self.compute_statevector_prefix()
460    }
461
462    /// Reconstruct the state-vector amplitudes for the executed circuit prefix.
463    ///
464    /// Builds a circuit containing only the first `self.current_gate` gates and
465    /// runs it through the embedded [`StateVectorSimulator`], returning the real
466    /// amplitudes. When no circuit is loaded the result is the initial `|0…0⟩`
467    /// state.
468    fn compute_statevector_prefix(&self) -> Result<Array1<Complex64>> {
469        let dim = 1_usize << N;
470
471        let Some(circuit) = self.circuit.as_ref() else {
472            // No circuit loaded: the simulator sits in the |0…0⟩ state.
473            let mut amplitudes = Array1::zeros(dim);
474            amplitudes[0] = Complex64::new(1.0, 0.0);
475            return Ok(amplitudes);
476        };
477
478        // Replay only the gates that have already been executed. We clone the
479        // shared `Arc` handles (cheap, no gate cloning) into a fresh prefix
480        // circuit so the simulator can evolve |0…0⟩ up to the current step.
481        let gates = circuit.gates();
482        let executed = self.current_gate.min(gates.len());
483
484        let mut prefix: Circuit<N> = Circuit::with_capacity(executed);
485        for gate in &gates[..executed] {
486            prefix.add_gate_arc(Arc::clone(gate))?;
487        }
488
489        let register = self.simulator.run(&prefix)?;
490        Ok(Array1::from(register.amplitudes().to_vec()))
491    }
492
493    /// Get entanglement entropy at the specified bipartition cut.
494    ///
495    /// The cut splits the qubits into two contiguous groups; `cut` is the number
496    /// of qubits on one side of the partition. The von Neumann entropy (in nats,
497    /// consistent with the MPS path) of the reduced density matrix is returned.
498    ///
499    /// Both the state-vector and MPS backends are handled through
500    /// [`Self::get_current_state`], which yields the genuine amplitudes for the
501    /// current step (the MPS backend is contracted to a state vector via its
502    /// `to_statevector` method). This computes the real entropy in every case
503    /// rather than returning a placeholder.
504    pub fn get_entanglement_entropy(&self, cut: usize) -> Result<f64> {
505        let state = self.get_current_state()?;
506        compute_entanglement_entropy(&state, cut, N)
507    }
508
509    /// Get expectation value of Pauli observable
510    pub fn get_pauli_expectation(&self, pauli_string: &str) -> Result<Complex64> {
511        #[cfg(feature = "mps")]
512        if let Some(ref mps) = self.mps_simulator {
513            return mps
514                .expectation_value_pauli(pauli_string)
515                .map_err(|e| SimulatorError::UnsupportedOperation(format!("MPS error: {e}")));
516        }
517
518        let state = self.get_current_state()?;
519        compute_pauli_expectation(&state, pauli_string)
520    }
521
522    /// Get performance metrics
523    pub const fn get_metrics(&self) -> &PerformanceMetrics {
524        &self.metrics
525    }
526
527    /// Get all snapshots
528    pub const fn get_snapshots(&self) -> &VecDeque<ExecutionSnapshot> {
529        &self.snapshots
530    }
531
532    /// Get watchpoint by ID
533    pub fn get_watchpoint(&self, id: &str) -> Option<&Watchpoint> {
534        self.watchpoints.get(id)
535    }
536
537    /// Get all watchpoints
538    pub const fn get_watchpoints(&self) -> &HashMap<String, Watchpoint> {
539        &self.watchpoints
540    }
541
542    /// Check if execution is finished
543    pub const fn is_finished(&self) -> bool {
544        matches!(self.execution_state, ExecutionState::Finished)
545    }
546
547    /// Check if execution is paused
548    pub const fn is_paused(&self) -> bool {
549        matches!(self.execution_state, ExecutionState::Paused { .. })
550    }
551
552    /// Get current execution state
553    pub const fn get_execution_state(&self) -> &ExecutionState {
554        &self.execution_state
555    }
556
557    /// Generate debugging report
558    pub fn generate_report(&self) -> DebugReport {
559        DebugReport {
560            circuit_summary: self.circuit.as_ref().map(|c| CircuitSummary {
561                total_gates: c.gates().len(),
562                gate_types: self.metrics.gate_counts.clone(),
563                estimated_depth: estimate_circuit_depth(c),
564            }),
565            performance: self.metrics.clone(),
566            entanglement_analysis: self.analyze_entanglement(),
567            state_analysis: self.analyze_state(),
568            recommendations: self.generate_recommendations(),
569        }
570    }
571
572    // Private helper methods
573
574    fn take_snapshot(&mut self) -> Result<()> {
575        if self.snapshots.len() >= self.config.max_snapshots {
576            self.snapshots.pop_front();
577        }
578
579        let circuit = self.circuit.as_ref().ok_or_else(|| {
580            SimulatorError::InvalidOperation("No circuit loaded for snapshot".to_string())
581        })?;
582        let state = self.get_current_state()?;
583
584        let snapshot = ExecutionSnapshot {
585            gate_index: self.current_gate,
586            state,
587            timestamp: Instant::now(),
588            last_gate: if self.current_gate > 0 {
589                Some(circuit.gates()[self.current_gate - 1].clone())
590            } else {
591                None
592            },
593            gate_counts: self.metrics.gate_counts.clone(),
594            entanglement_entropies: self.compute_all_entanglement_entropies()?,
595            circuit_depth: self.current_gate, // Simplified
596        };
597
598        self.snapshots.push_back(snapshot);
599        self.metrics.snapshot_count += 1;
600        Ok(())
601    }
602
603    fn check_breakpoints(&self) -> Result<Option<String>> {
604        for breakpoint in &self.breakpoints {
605            match breakpoint {
606                BreakCondition::GateIndex(target) => {
607                    if self.current_gate == *target {
608                        return Ok(Some(format!("Reached gate index {target}")));
609                    }
610                }
611                BreakCondition::EntanglementThreshold { cut, threshold } => {
612                    let entropy = self.get_entanglement_entropy(*cut)?;
613                    if entropy > *threshold {
614                        return Ok(Some(format!(
615                            "Entanglement entropy {entropy:.4} > {threshold:.4} at cut {cut}"
616                        )));
617                    }
618                }
619                BreakCondition::ObservableThreshold {
620                    observable,
621                    threshold,
622                    direction,
623                } => {
624                    let expectation = self.get_pauli_expectation(observable)?.re;
625                    let hit = match direction {
626                        ThresholdDirection::Above => expectation > *threshold,
627                        ThresholdDirection::Below => expectation < *threshold,
628                        ThresholdDirection::Either => (expectation - threshold).abs() < 1e-10,
629                    };
630                    if hit {
631                        return Ok(Some(format!(
632                            "Observable {observable} = {expectation:.4} crossed threshold {threshold:.4}"
633                        )));
634                    }
635                }
636                _ => {
637                    // Other breakpoint types would be implemented here
638                }
639            }
640        }
641        Ok(None)
642    }
643
644    fn update_watchpoints(&mut self) -> Result<()> {
645        let current_gate = self.current_gate;
646
647        // Collect watchpoint updates to avoid borrowing issues
648        let mut updates = Vec::new();
649
650        for (id, watchpoint) in &self.watchpoints {
651            let should_update = match &watchpoint.frequency {
652                WatchFrequency::EveryGate => true,
653                WatchFrequency::EveryNGates(n) => current_gate % n == 0,
654                WatchFrequency::AtGates(gates) => gates.contains(&current_gate),
655            };
656
657            if should_update {
658                let value = match &watchpoint.property {
659                    WatchProperty::EntanglementEntropy(cut) => {
660                        self.get_entanglement_entropy(*cut)?
661                    }
662                    WatchProperty::PauliExpectation(observable) => {
663                        self.get_pauli_expectation(observable)?.re
664                    }
665                    WatchProperty::Normalization => {
666                        let state = self.get_current_state()?;
667                        state
668                            .iter()
669                            .map(scirs2_core::Complex::norm_sqr)
670                            .sum::<f64>()
671                    }
672                    _ => 0.0, // Other properties would be implemented
673                };
674
675                updates.push((id.clone(), current_gate, value));
676            }
677        }
678
679        // Apply updates
680        for (id, gate, value) in updates {
681            if let Some(watchpoint) = self.watchpoints.get_mut(&id) {
682                watchpoint.history.push_back((gate, value));
683
684                // Keep history size manageable
685                if watchpoint.history.len() > 1000 {
686                    watchpoint.history.pop_front();
687                }
688            }
689        }
690
691        Ok(())
692    }
693
694    fn compute_all_entanglement_entropies(&self) -> Result<Vec<f64>> {
695        let mut entropies = Vec::new();
696        for &cut in &self.config.entropy_cuts {
697            // Only evaluate genuine bipartitions: `cut` qubits on the left and
698            // `N - cut` on the right, both non-empty (1 <= cut <= N - 1).
699            if cut >= 1 && cut < N {
700                entropies.push(self.get_entanglement_entropy(cut)?);
701            }
702        }
703        Ok(entropies)
704    }
705
706    const fn analyze_entanglement(&self) -> EntanglementAnalysis {
707        // Analyze entanglement patterns from snapshots and watchpoints
708        EntanglementAnalysis {
709            max_entropy: self.metrics.max_entanglement,
710            avg_entropy: self.metrics.avg_entanglement,
711            entropy_evolution: Vec::new(), // Would be filled from watchpoint histories
712        }
713    }
714
715    const fn analyze_state(&self) -> StateAnalysis {
716        // Analyze quantum state properties
717        StateAnalysis {
718            is_separable: false,      // Would compute this
719            schmidt_rank: 1,          // Would compute this
720            participation_ratio: 1.0, // Would compute this
721        }
722    }
723
724    fn generate_recommendations(&self) -> Vec<String> {
725        let mut recommendations = Vec::new();
726
727        // Analyze performance and suggest optimizations
728        if self.metrics.max_entanglement > 3.0 {
729            recommendations.push(
730                "High entanglement detected. Consider using MPS simulation for better scaling."
731                    .to_string(),
732            );
733        }
734
735        if self.metrics.gate_counts.get("CNOT").unwrap_or(&0) > &50 {
736            recommendations
737                .push("Many CNOT gates detected. Consider gate optimization.".to_string());
738        }
739
740        recommendations
741    }
742}
743
744/// Result of a debugging step
745#[derive(Debug, Clone)]
746pub enum StepResult {
747    /// Continue execution
748    Continue,
749    /// Breakpoint was hit
750    BreakpointHit { reason: String },
751    /// Execution finished
752    Finished,
753}
754
755/// Circuit summary for debugging
756#[derive(Debug, Clone, Serialize, Deserialize)]
757pub struct CircuitSummary {
758    pub total_gates: usize,
759    pub gate_types: HashMap<String, usize>,
760    pub estimated_depth: usize,
761}
762
763/// Entanglement analysis results
764#[derive(Debug, Clone, Serialize, Deserialize)]
765pub struct EntanglementAnalysis {
766    pub max_entropy: f64,
767    pub avg_entropy: f64,
768    pub entropy_evolution: Vec<(usize, f64)>,
769}
770
771/// State analysis results
772#[derive(Debug, Clone, Serialize, Deserialize)]
773pub struct StateAnalysis {
774    pub is_separable: bool,
775    pub schmidt_rank: usize,
776    pub participation_ratio: f64,
777}
778
779/// Complete debugging report
780#[derive(Debug, Clone, Serialize, Deserialize)]
781pub struct DebugReport {
782    pub circuit_summary: Option<CircuitSummary>,
783    pub performance: PerformanceMetrics,
784    pub entanglement_analysis: EntanglementAnalysis,
785    pub state_analysis: StateAnalysis,
786    pub recommendations: Vec<String>,
787}
788
789// Helper functions
790
791/// Compute the bipartite von Neumann entanglement entropy from a state vector.
792///
793/// The amplitude vector `|ψ⟩` is reshaped into a `2^cut × 2^(num_qubits - cut)`
794/// matrix `M`. The Schmidt coefficients of the bipartition are the singular
795/// values `σ_i` of `M`, and the reduced-density-matrix eigenvalues are `σ_i²`.
796/// The von Neumann entropy is therefore `S = -Σ_i σ_i² ln σ_i²` (natural log,
797/// matching the convention used by the MPS backend's `entanglement_entropy`).
798///
799/// The singular value decomposition is computed with [`scirs2_linalg`] (the
800/// SciRS2 complex SVD, which diagonalizes `MᴴM`); `ndarray-linalg` is not used.
801fn compute_entanglement_entropy(
802    state: &Array1<Complex64>,
803    cut: usize,
804    num_qubits: usize,
805) -> Result<f64> {
806    // A valid bipartition needs at least one qubit on each side: `cut` qubits on
807    // the left (1..=num_qubits-1) and `num_qubits - cut` on the right. Reject
808    // cuts that would leave an empty subsystem or exceed the register.
809    if num_qubits < 2 || cut == 0 || cut >= num_qubits {
810        return Err(SimulatorError::IndexOutOfBounds(cut));
811    }
812
813    let left_dim = 1usize << cut;
814    let right_dim = 1usize << (num_qubits - cut);
815
816    // Reshape the state into the bipartite amplitude matrix M[i_left, i_right].
817    let state_matrix =
818        Array2::from_shape_vec((left_dim, right_dim), state.to_vec()).map_err(|_| {
819            SimulatorError::DimensionMismatch("Invalid state vector dimension".to_string())
820        })?;
821
822    // Singular values via SciRS2 complex SVD. The squared singular values are the
823    // Schmidt probabilities p_i = σ_i².
824    let svd = scirs2_linalg::complex::decompositions::complex_svd(&state_matrix.view(), false)
825        .map_err(|e| SimulatorError::LinalgError(format!("complex SVD failed: {e}")))?;
826
827    let mut entropy = 0.0_f64;
828    for &sigma in &svd.s {
829        let p = sigma * sigma;
830        // Skip vanishing Schmidt coefficients; lim_{p->0} p ln p = 0.
831        if p > 1e-12 {
832            entropy -= p * p.ln();
833        }
834    }
835
836    // Guard against tiny negative values from floating-point round-off.
837    Ok(entropy.max(0.0))
838}
839
840/// Compute the expectation value `⟨ψ| P |ψ⟩` of a Pauli string from a state vector.
841///
842/// `pauli_string` is a sequence of `I`, `X`, `Y`, `Z` characters, one per qubit.
843/// The leftmost character corresponds to the highest-index qubit (little-endian
844/// basis ordering), matching the convention of the MPS backend's
845/// `expectation_value_pauli`. The string length must equal the number of qubits.
846///
847/// This evaluates the real expectation value by applying the tensor-product
848/// Pauli operator to the amplitude vector; it does not return a placeholder.
849fn compute_pauli_expectation(state: &Array1<Complex64>, pauli_string: &str) -> Result<Complex64> {
850    let dim = state.len();
851    let num_qubits = dim.trailing_zeros() as usize;
852
853    if dim != 1usize << num_qubits {
854        return Err(SimulatorError::DimensionMismatch(format!(
855            "State vector length {dim} is not a power of two"
856        )));
857    }
858
859    if pauli_string.len() != num_qubits {
860        return Err(SimulatorError::InvalidInput(format!(
861            "Pauli string length {} doesn't match qubit count {num_qubits}",
862            pauli_string.len()
863        )));
864    }
865
866    let mut result = Complex64::new(0.0, 0.0);
867
868    for (i, amplitude) in state.iter().enumerate() {
869        let mut coeff = Complex64::new(1.0, 0.0);
870        let mut target_state = i;
871
872        // Leftmost character maps to the highest qubit, so iterate reversed to
873        // pair character position with qubit index 0, 1, 2, …
874        for (qubit, pauli_char) in pauli_string.chars().rev().enumerate() {
875            let bit = (i >> qubit) & 1;
876            match pauli_char {
877                'I' => {}
878                'X' => {
879                    target_state ^= 1 << qubit;
880                }
881                'Y' => {
882                    target_state ^= 1 << qubit;
883                    coeff *= if bit == 0 {
884                        Complex64::new(0.0, 1.0)
885                    } else {
886                        Complex64::new(0.0, -1.0)
887                    };
888                }
889                'Z' => {
890                    if bit == 1 {
891                        coeff = -coeff;
892                    }
893                }
894                other => {
895                    return Err(SimulatorError::InvalidInput(format!(
896                        "Invalid Pauli operator: {other}"
897                    )));
898                }
899            }
900        }
901
902        result += amplitude.conj() * coeff * state[target_state];
903    }
904
905    Ok(result)
906}
907
908/// Estimate circuit depth
909fn estimate_circuit_depth<const N: usize>(circuit: &Circuit<N>) -> usize {
910    // Simplified depth estimation - would need proper dependency analysis
911    circuit.gates().len()
912}
913
914#[cfg(test)]
915mod tests {
916    use super::*;
917
918    #[test]
919    fn test_debugger_creation() {
920        let config = DebugConfig::default();
921        let debugger: QuantumDebugger<3> =
922            QuantumDebugger::new(config).expect("Failed to create debugger");
923        assert!(matches!(debugger.execution_state, ExecutionState::Idle));
924    }
925
926    #[test]
927    fn test_breakpoint_management() {
928        let config = DebugConfig::default();
929        let mut debugger: QuantumDebugger<3> =
930            QuantumDebugger::new(config).expect("Failed to create debugger");
931
932        debugger.add_breakpoint(BreakCondition::GateIndex(5));
933        assert_eq!(debugger.breakpoints.len(), 1);
934
935        debugger
936            .remove_breakpoint(0)
937            .expect("Failed to remove breakpoint");
938        assert_eq!(debugger.breakpoints.len(), 0);
939    }
940
941    #[test]
942    fn test_watchpoint_management() {
943        let config = DebugConfig::default();
944        let mut debugger: QuantumDebugger<3> =
945            QuantumDebugger::new(config).expect("Failed to create debugger");
946
947        let watchpoint = Watchpoint {
948            id: "test".to_string(),
949            description: "Test watchpoint".to_string(),
950            property: WatchProperty::Normalization,
951            frequency: WatchFrequency::EveryGate,
952            history: VecDeque::new(),
953        };
954
955        debugger.add_watchpoint(watchpoint);
956        assert!(debugger.get_watchpoint("test").is_some());
957
958        debugger
959            .remove_watchpoint("test")
960            .expect("Failed to remove watchpoint");
961        assert!(debugger.get_watchpoint("test").is_none());
962    }
963
964    /// Build a debugger with snapshots disabled and a Bell circuit loaded.
965    fn bell_debugger() -> QuantumDebugger<2> {
966        let config = DebugConfig {
967            store_snapshots: false,
968            ..DebugConfig::default()
969        };
970        let mut debugger: QuantumDebugger<2> =
971            QuantumDebugger::new(config).expect("Failed to create debugger");
972
973        let mut circuit: Circuit<2> = Circuit::new();
974        circuit
975            .bell_state(0, 1)
976            .expect("Failed to build Bell state");
977        debugger
978            .load_circuit(circuit)
979            .expect("Failed to load circuit");
980        debugger
981    }
982
983    #[test]
984    fn test_get_current_state_initial_is_zero_ket() {
985        // With no gates executed yet, the state must be a *genuine* |00> state
986        // (amplitude 1 on basis 0), not a fabricated all-zero vector.
987        let debugger = bell_debugger();
988        let state = debugger
989            .get_current_state()
990            .expect("Failed to get current state");
991
992        assert_eq!(state.len(), 4);
993        assert!((state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-12);
994        for amp in state.iter().skip(1) {
995            assert!(amp.norm() < 1e-12);
996        }
997
998        // It must NOT be the old fabricated all-zero "dummy" vector.
999        let dummy: Array1<Complex64> = Array1::zeros(4);
1000        assert!(
1001            state != dummy,
1002            "state must not be the fabricated all-zero vector"
1003        );
1004    }
1005
1006    #[test]
1007    fn test_get_current_state_bell_amplitudes() {
1008        // Execute H(0) then CNOT(0,1); the real Bell state is
1009        // (|00> + |11>)/sqrt(2) = (1/sqrt2, 0, 0, 1/sqrt2).
1010        let mut debugger = bell_debugger();
1011        debugger.step().expect("step 1 failed"); // apply H
1012        debugger.step().expect("step 2 failed"); // apply CNOT
1013
1014        let state = debugger
1015            .get_current_state()
1016            .expect("Failed to get current state");
1017
1018        let inv_sqrt2 = 1.0 / std::f64::consts::SQRT_2;
1019        assert!((state[0] - Complex64::new(inv_sqrt2, 0.0)).norm() < 1e-10);
1020        assert!(state[1].norm() < 1e-10);
1021        assert!(state[2].norm() < 1e-10);
1022        assert!((state[3] - Complex64::new(inv_sqrt2, 0.0)).norm() < 1e-10);
1023
1024        // Norm must be 1 (real, normalized state).
1025        let norm_sq: f64 = state.iter().map(scirs2_core::Complex::norm_sqr).sum();
1026        assert!((norm_sq - 1.0).abs() < 1e-10);
1027    }
1028
1029    #[test]
1030    fn test_get_current_state_prefix_after_first_gate() {
1031        // After only H(0), the state is (|00> + |01>)/sqrt2 = (1/sqrt2, 1/sqrt2, 0, 0).
1032        // Qubit 0 is the low bit (little-endian: index 1 == q0=1), so H(0) populates
1033        // indices 0 and 1. This verifies the executed *prefix* is reconstructed, not
1034        // the full circuit.
1035        let mut debugger = bell_debugger();
1036        debugger.step().expect("step 1 failed"); // apply H only
1037
1038        let state = debugger
1039            .get_current_state()
1040            .expect("Failed to get current state");
1041
1042        let inv_sqrt2 = 1.0 / std::f64::consts::SQRT_2;
1043        assert!((state[0] - Complex64::new(inv_sqrt2, 0.0)).norm() < 1e-10);
1044        assert!((state[1] - Complex64::new(inv_sqrt2, 0.0)).norm() < 1e-10);
1045        assert!(state[2].norm() < 1e-10);
1046        assert!(state[3].norm() < 1e-10);
1047    }
1048
1049    #[test]
1050    fn test_entanglement_entropy_bell_is_ln2() {
1051        // A Bell state is maximally entangled across the 1|1 cut: S = ln(2) nats.
1052        let mut debugger = bell_debugger();
1053        debugger.run().expect("run failed");
1054
1055        let entropy = debugger
1056            .get_entanglement_entropy(1)
1057            .expect("entropy failed");
1058        assert!(
1059            (entropy - std::f64::consts::LN_2).abs() < 1e-10,
1060            "Bell entropy {entropy} should equal ln(2)"
1061        );
1062    }
1063
1064    #[test]
1065    fn test_entanglement_entropy_product_state_is_zero() {
1066        // |+0> = H(0) only is a product state across the 1|1 cut: S = 0.
1067        let mut debugger = bell_debugger();
1068        debugger.step().expect("step failed"); // H(0) only
1069
1070        let entropy = debugger
1071            .get_entanglement_entropy(1)
1072            .expect("entropy failed");
1073        assert!(
1074            entropy.abs() < 1e-10,
1075            "product-state entropy {entropy} should be 0"
1076        );
1077    }
1078
1079    #[test]
1080    fn test_compute_entanglement_entropy_known_schmidt() {
1081        // Construct a 2-qubit state with known Schmidt coefficients
1082        // |psi> = sqrt(0.8)|00> + sqrt(0.2)|11>.
1083        // Reduced density eigenvalues are {0.8, 0.2}; entropy is the binary
1084        // entropy in nats: -(0.8 ln 0.8 + 0.2 ln 0.2).
1085        let p0 = 0.8_f64;
1086        let p1 = 0.2_f64;
1087        let state = Array1::from(vec![
1088            Complex64::new(p0.sqrt(), 0.0),
1089            Complex64::new(0.0, 0.0),
1090            Complex64::new(0.0, 0.0),
1091            Complex64::new(p1.sqrt(), 0.0),
1092        ]);
1093
1094        let expected = -(p0 * p0.ln() + p1 * p1.ln());
1095        let entropy = compute_entanglement_entropy(&state, 1, 2).expect("entropy failed");
1096        assert!(
1097            (entropy - expected).abs() < 1e-10,
1098            "entropy {entropy} should equal {expected}"
1099        );
1100    }
1101
1102    #[test]
1103    fn test_compute_entanglement_entropy_rejects_bad_cut() {
1104        let state: Array1<Complex64> =
1105            Array1::from(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
1106        // num_qubits = 1: no valid bipartition exists.
1107        assert!(compute_entanglement_entropy(&state, 0, 1).is_err());
1108    }
1109
1110    #[test]
1111    fn test_pauli_expectation_z_on_computational_basis() {
1112        // <Z> on |0> = +1, <Z> on |1> = -1.
1113        let zero: Array1<Complex64> =
1114            Array1::from(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
1115        let one: Array1<Complex64> =
1116            Array1::from(vec![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)]);
1117
1118        let ez0 = compute_pauli_expectation(&zero, "Z").expect("pauli failed");
1119        let ez1 = compute_pauli_expectation(&one, "Z").expect("pauli failed");
1120        assert!((ez0 - Complex64::new(1.0, 0.0)).norm() < 1e-12);
1121        assert!((ez1 - Complex64::new(-1.0, 0.0)).norm() < 1e-12);
1122    }
1123
1124    #[test]
1125    fn test_pauli_expectation_x_on_plus_state() {
1126        // |+> = (|0> + |1>)/sqrt2; <X> = +1, <Z> = 0.
1127        let inv_sqrt2 = 1.0 / std::f64::consts::SQRT_2;
1128        let plus: Array1<Complex64> = Array1::from(vec![
1129            Complex64::new(inv_sqrt2, 0.0),
1130            Complex64::new(inv_sqrt2, 0.0),
1131        ]);
1132
1133        let ex = compute_pauli_expectation(&plus, "X").expect("pauli failed");
1134        let ez = compute_pauli_expectation(&plus, "Z").expect("pauli failed");
1135        assert!((ex - Complex64::new(1.0, 0.0)).norm() < 1e-12);
1136        assert!(ez.norm() < 1e-12);
1137    }
1138
1139    #[test]
1140    fn test_pauli_expectation_zz_on_bell() {
1141        // Bell state (|00>+|11>)/sqrt2: <ZZ> = +1, <XX> = +1, <ZI> = 0.
1142        let inv_sqrt2 = 1.0 / std::f64::consts::SQRT_2;
1143        let bell: Array1<Complex64> = Array1::from(vec![
1144            Complex64::new(inv_sqrt2, 0.0),
1145            Complex64::new(0.0, 0.0),
1146            Complex64::new(0.0, 0.0),
1147            Complex64::new(inv_sqrt2, 0.0),
1148        ]);
1149
1150        let ezz = compute_pauli_expectation(&bell, "ZZ").expect("pauli failed");
1151        let exx = compute_pauli_expectation(&bell, "XX").expect("pauli failed");
1152        let ezi = compute_pauli_expectation(&bell, "ZI").expect("pauli failed");
1153        assert!((ezz - Complex64::new(1.0, 0.0)).norm() < 1e-12);
1154        assert!((exx - Complex64::new(1.0, 0.0)).norm() < 1e-12);
1155        assert!(ezi.norm() < 1e-12);
1156    }
1157
1158    #[test]
1159    fn test_pauli_expectation_length_mismatch_errors() {
1160        let zero: Array1<Complex64> =
1161            Array1::from(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
1162        // One qubit but a two-character Pauli string.
1163        assert!(compute_pauli_expectation(&zero, "ZZ").is_err());
1164    }
1165
1166    #[test]
1167    fn test_pauli_via_debugger_zz_on_bell() {
1168        // End-to-end through the debugger API on a real executed circuit.
1169        let mut debugger = bell_debugger();
1170        debugger.run().expect("run failed");
1171
1172        let ezz = debugger.get_pauli_expectation("ZZ").expect("pauli failed");
1173        assert!((ezz - Complex64::new(1.0, 0.0)).norm() < 1e-10);
1174    }
1175}