1use 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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
30pub enum BreakCondition {
31 GateIndex(usize),
33 QubitState { qubit: usize, state: bool },
35 EntanglementThreshold { cut: usize, threshold: f64 },
37 FidelityThreshold {
39 target_state: Vec<Complex64>,
40 threshold: f64,
41 },
42 ObservableThreshold {
44 observable: String,
45 threshold: f64,
46 direction: ThresholdDirection,
47 },
48 CircuitDepth(usize),
50 ExecutionTime(Duration),
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub enum ThresholdDirection {
57 Above,
58 Below,
59 Either,
60}
61
62#[derive(Debug, Clone)]
64pub struct ExecutionSnapshot {
65 pub gate_index: usize,
67 pub state: Array1<Complex64>,
69 pub timestamp: Instant,
71 pub last_gate: Option<Arc<dyn GateOp + Send + Sync>>,
73 pub gate_counts: HashMap<String, usize>,
75 pub entanglement_entropies: Vec<f64>,
77 pub circuit_depth: usize,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct PerformanceMetrics {
84 pub total_time: Duration,
86 pub gate_times: HashMap<String, Duration>,
88 pub memory_usage: MemoryUsage,
90 pub gate_counts: HashMap<String, usize>,
92 pub avg_entanglement: f64,
94 pub max_entanglement: f64,
96 pub snapshot_count: usize,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct MemoryUsage {
103 pub peak_statevector_memory: usize,
105 pub mps_bond_dims: Vec<usize>,
107 pub peak_mps_memory: usize,
109 pub debugger_overhead: usize,
111}
112
113#[derive(Debug, Clone)]
115pub struct Watchpoint {
116 pub id: String,
118 pub description: String,
120 pub property: WatchProperty,
122 pub frequency: WatchFrequency,
124 pub history: VecDeque<(usize, f64)>, }
127
128#[derive(Debug, Clone)]
130pub enum WatchProperty {
131 Normalization,
133 EntanglementEntropy(usize),
135 PauliExpectation(String),
137 Fidelity(Array1<Complex64>),
139 GateFidelity,
141 CircuitDepth,
143 MPSBondDimension,
145}
146
147#[derive(Debug, Clone)]
149pub enum WatchFrequency {
150 EveryGate,
152 EveryNGates(usize),
154 AtGates(HashSet<usize>),
156}
157
158#[derive(Debug, Clone)]
160pub struct DebugConfig {
161 pub store_snapshots: bool,
163 pub max_snapshots: usize,
165 pub track_performance: bool,
167 pub validate_state: bool,
169 pub entropy_cuts: Vec<usize>,
171 pub use_mps: bool,
173 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
191pub struct QuantumDebugger<const N: usize> {
193 config: DebugConfig,
195 circuit: Option<Circuit<N>>,
197 breakpoints: Vec<BreakCondition>,
199 watchpoints: HashMap<String, Watchpoint>,
201 snapshots: VecDeque<ExecutionSnapshot>,
203 metrics: PerformanceMetrics,
205 execution_state: ExecutionState,
207 simulator: StateVectorSimulator,
209 #[cfg(feature = "mps")]
211 mps_simulator: Option<EnhancedMPS>,
212 current_gate: usize,
214 start_time: Option<Instant>,
216}
217
218#[derive(Debug, Clone)]
220enum ExecutionState {
221 Idle,
223 Running,
225 Paused { reason: String },
227 Finished,
229 Error { message: String },
231}
232
233impl<const N: usize> QuantumDebugger<N> {
234 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 pub fn load_circuit(&mut self, circuit: Circuit<N>) -> Result<()> {
279 self.circuit = Some(circuit);
280 self.reset();
281 Ok(())
282 }
283
284 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 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 for watchpoint in self.watchpoints.values_mut() {
314 watchpoint.history.clear();
315 }
316 }
317
318 pub fn add_breakpoint(&mut self, condition: BreakCondition) {
320 self.breakpoints.push(condition);
321 }
322
323 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 pub fn add_watchpoint(&mut self, watchpoint: Watchpoint) {
334 self.watchpoints.insert(watchpoint.id.clone(), watchpoint);
335 }
336
337 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 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 if let ExecutionState::Paused { .. } = self.execution_state {
361 self.execution_state = ExecutionState::Running;
363 }
364
365 if self.start_time.is_none() {
367 self.start_time = Some(Instant::now());
368 self.execution_state = ExecutionState::Running;
369 }
370
371 let gate_name = circuit.gates()[self.current_gate].name().to_string();
373 let total_gates = circuit.gates().len();
374
375 let gate_start = Instant::now();
377
378 #[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 *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 self.update_watchpoints()?;
403
404 if self.config.store_snapshots {
406 self.take_snapshot()?;
407 }
408
409 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 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 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 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 let mut amplitudes = Array1::zeros(dim);
474 amplitudes[0] = Complex64::new(1.0, 0.0);
475 return Ok(amplitudes);
476 };
477
478 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 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 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 pub const fn get_metrics(&self) -> &PerformanceMetrics {
524 &self.metrics
525 }
526
527 pub const fn get_snapshots(&self) -> &VecDeque<ExecutionSnapshot> {
529 &self.snapshots
530 }
531
532 pub fn get_watchpoint(&self, id: &str) -> Option<&Watchpoint> {
534 self.watchpoints.get(id)
535 }
536
537 pub const fn get_watchpoints(&self) -> &HashMap<String, Watchpoint> {
539 &self.watchpoints
540 }
541
542 pub const fn is_finished(&self) -> bool {
544 matches!(self.execution_state, ExecutionState::Finished)
545 }
546
547 pub const fn is_paused(&self) -> bool {
549 matches!(self.execution_state, ExecutionState::Paused { .. })
550 }
551
552 pub const fn get_execution_state(&self) -> &ExecutionState {
554 &self.execution_state
555 }
556
557 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 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, };
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 }
639 }
640 }
641 Ok(None)
642 }
643
644 fn update_watchpoints(&mut self) -> Result<()> {
645 let current_gate = self.current_gate;
646
647 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(¤t_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, };
674
675 updates.push((id.clone(), current_gate, value));
676 }
677 }
678
679 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 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 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 EntanglementAnalysis {
709 max_entropy: self.metrics.max_entanglement,
710 avg_entropy: self.metrics.avg_entanglement,
711 entropy_evolution: Vec::new(), }
713 }
714
715 const fn analyze_state(&self) -> StateAnalysis {
716 StateAnalysis {
718 is_separable: false, schmidt_rank: 1, participation_ratio: 1.0, }
722 }
723
724 fn generate_recommendations(&self) -> Vec<String> {
725 let mut recommendations = Vec::new();
726
727 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#[derive(Debug, Clone)]
746pub enum StepResult {
747 Continue,
749 BreakpointHit { reason: String },
751 Finished,
753}
754
755#[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#[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#[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#[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
789fn compute_entanglement_entropy(
802 state: &Array1<Complex64>,
803 cut: usize,
804 num_qubits: usize,
805) -> Result<f64> {
806 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 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 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 if p > 1e-12 {
832 entropy -= p * p.ln();
833 }
834 }
835
836 Ok(entropy.max(0.0))
838}
839
840fn 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 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
908fn estimate_circuit_depth<const N: usize>(circuit: &Circuit<N>) -> usize {
910 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 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 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 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 let mut debugger = bell_debugger();
1011 debugger.step().expect("step 1 failed"); debugger.step().expect("step 2 failed"); 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 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 let mut debugger = bell_debugger();
1036 debugger.step().expect("step 1 failed"); 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 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 let mut debugger = bell_debugger();
1068 debugger.step().expect("step failed"); 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 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 assert!(compute_entanglement_entropy(&state, 0, 1).is_err());
1108 }
1109
1110 #[test]
1111 fn test_pauli_expectation_z_on_computational_basis() {
1112 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 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 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 assert!(compute_pauli_expectation(&zero, "ZZ").is_err());
1164 }
1165
1166 #[test]
1167 fn test_pauli_via_debugger_zz_on_bell() {
1168 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}