1use crate::backend::{analyze_circuit, BackendType};
18use crate::circuit::QuantumCircuit;
19use crate::gate::Gate;
20use crate::simulator::Simulator;
21use crate::stabilizer::StabilizerState;
22
23use std::collections::HashMap;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum VerificationLevel {
32 Exact,
34 Statistical,
36 Trend,
38 Skipped,
40}
41
42#[derive(Debug, Clone)]
44pub struct VerificationResult {
45 pub level: VerificationLevel,
47 pub passed: bool,
49 pub primary_backend: BackendType,
51 pub reference_backend: Option<BackendType>,
53 pub total_variation_distance: Option<f64>,
55 pub chi_squared_p_value: Option<f64>,
57 pub correlation: Option<f64>,
59 pub explanation: String,
61 pub discrepancies: Vec<Discrepancy>,
63}
64
65#[derive(Debug, Clone)]
67pub struct Discrepancy {
68 pub bitstring: Vec<bool>,
70 pub primary_probability: f64,
72 pub reference_probability: f64,
74 pub absolute_difference: f64,
76}
77
78pub fn verify_circuit(
101 circuit: &QuantumCircuit,
102 shots: u32,
103 seed: u64,
104) -> VerificationResult {
105 let analysis = analyze_circuit(circuit);
106 let num_qubits = circuit.num_qubits();
107 let is_clifford = is_clifford_circuit(circuit);
108
109 if is_clifford && num_qubits <= 25 {
111 let sv_result = Simulator::run_shots(circuit, shots, Some(seed));
113 let sv_counts = match sv_result {
114 Ok(r) => r.counts,
115 Err(e) => {
116 return VerificationResult {
117 level: VerificationLevel::Skipped,
118 passed: false,
119 primary_backend: BackendType::StateVector,
120 reference_backend: None,
121 total_variation_distance: None,
122 chi_squared_p_value: None,
123 correlation: None,
124 explanation: format!(
125 "State-vector simulation failed: {}",
126 e
127 ),
128 discrepancies: vec![],
129 };
130 }
131 };
132
133 let stab_counts = run_stabilizer_shots(circuit, shots, seed);
135
136 let mut result = verify_against_reference(
138 &sv_counts,
139 &stab_counts,
140 0.0, );
142
143 result.primary_backend = BackendType::StateVector;
144 result.reference_backend = Some(BackendType::Stabilizer);
145
146 if result.passed
148 && result
149 .total_variation_distance
150 .map_or(false, |d| d == 0.0)
151 {
152 result.level = VerificationLevel::Exact;
153 result.explanation = format!(
154 "Exact match: {}-qubit Clifford circuit verified across \
155 state-vector and stabilizer backends ({} shots, \
156 clifford_fraction={:.2})",
157 num_qubits, shots, analysis.clifford_fraction
158 );
159 } else {
160 let tight_tolerance = 0.05;
163 let mut stat_result = verify_against_reference(
164 &sv_counts,
165 &stab_counts,
166 tight_tolerance,
167 );
168 stat_result.primary_backend = BackendType::StateVector;
169 stat_result.reference_backend = Some(BackendType::Stabilizer);
170 stat_result.explanation = format!(
171 "Statistical comparison of {}-qubit Clifford circuit across \
172 state-vector and stabilizer backends ({} shots, TVD={:.6})",
173 num_qubits,
174 shots,
175 stat_result
176 .total_variation_distance
177 .unwrap_or(0.0)
178 );
179 return stat_result;
180 }
181
182 return result;
183 }
184
185 if !is_clifford && num_qubits <= 25 {
187 return VerificationResult {
188 level: VerificationLevel::Skipped,
189 passed: true,
190 primary_backend: BackendType::StateVector,
191 reference_backend: None,
192 total_variation_distance: None,
193 chi_squared_p_value: None,
194 correlation: None,
195 explanation: format!(
196 "Verification skipped: {}-qubit circuit contains non-Clifford \
197 gates (clifford_fraction={:.2}, {} non-Clifford gates). \
198 No reference backend available for cross-validation.",
199 num_qubits,
200 analysis.clifford_fraction,
201 analysis.non_clifford_gates
202 ),
203 discrepancies: vec![],
204 };
205 }
206
207 VerificationResult {
209 level: VerificationLevel::Skipped,
210 passed: true,
211 primary_backend: analysis.recommended_backend,
212 reference_backend: None,
213 total_variation_distance: None,
214 chi_squared_p_value: None,
215 correlation: None,
216 explanation: format!(
217 "Verification skipped: {}-qubit circuit exceeds state-vector \
218 capacity for cross-backend comparison.",
219 num_qubits
220 ),
221 discrepancies: vec![],
222 }
223}
224
225pub fn verify_against_reference(
242 primary: &HashMap<Vec<bool>, usize>,
243 reference: &HashMap<Vec<bool>, usize>,
244 tolerance: f64,
245) -> VerificationResult {
246 let p_norm = normalize_counts(primary);
247 let q_norm = normalize_counts(reference);
248
249 let distance = tvd(&p_norm, &q_norm);
250
251 let total_ref: usize = reference.values().sum();
252 let (chi2_stat, dof) =
253 chi_squared_statistic(primary, &q_norm, total_ref);
254 let p_value = if dof > 0 {
255 chi_squared_p_value(chi2_stat, dof)
256 } else {
257 1.0
258 };
259
260 let corr = pearson_correlation(&p_norm, &q_norm);
261
262 let mut all_keys: Vec<&Vec<bool>> =
264 p_norm.keys().chain(q_norm.keys()).collect();
265 all_keys.sort();
266 all_keys.dedup();
267
268 let mut discrepancies: Vec<Discrepancy> = all_keys
269 .iter()
270 .map(|key| {
271 let pp = p_norm.get(*key).copied().unwrap_or(0.0);
272 let rp = q_norm.get(*key).copied().unwrap_or(0.0);
273 Discrepancy {
274 bitstring: (*key).clone(),
275 primary_probability: pp,
276 reference_probability: rp,
277 absolute_difference: (pp - rp).abs(),
278 }
279 })
280 .filter(|d| d.absolute_difference > 1e-15)
281 .collect();
282
283 discrepancies
285 .sort_by(|a, b| b.absolute_difference.partial_cmp(&a.absolute_difference).unwrap());
286
287 let passed = distance <= tolerance;
288
289 let level = if tolerance == 0.0 && passed {
290 VerificationLevel::Exact
291 } else {
292 VerificationLevel::Statistical
293 };
294
295 let explanation = if passed {
296 format!(
297 "Verification passed: TVD={:.6}, chi2 p-value={:.4}, \
298 correlation={:.4}, tolerance={:.6}",
299 distance, p_value, corr, tolerance
300 )
301 } else {
302 format!(
303 "Verification FAILED: TVD={:.6} exceeds tolerance={:.6}, \
304 chi2 p-value={:.4}, correlation={:.4}, \
305 {} discrepancies found",
306 distance,
307 tolerance,
308 p_value,
309 corr,
310 discrepancies.len()
311 )
312 };
313
314 VerificationResult {
315 level,
316 passed,
317 primary_backend: BackendType::Auto,
318 reference_backend: None,
319 total_variation_distance: Some(distance),
320 chi_squared_p_value: Some(p_value),
321 correlation: Some(corr),
322 explanation,
323 discrepancies,
324 }
325}
326
327pub fn is_clifford_circuit(circuit: &QuantumCircuit) -> bool {
337 circuit.gates().iter().all(|gate| is_clifford_gate(gate))
338}
339
340fn is_clifford_gate(gate: &Gate) -> bool {
342 matches!(
343 gate,
344 Gate::H(_)
345 | Gate::X(_)
346 | Gate::Y(_)
347 | Gate::Z(_)
348 | Gate::S(_)
349 | Gate::Sdg(_)
350 | Gate::CNOT(_, _)
351 | Gate::CZ(_, _)
352 | Gate::SWAP(_, _)
353 | Gate::Measure(_)
354 | Gate::Reset(_)
355 | Gate::Barrier
356 )
357}
358
359pub fn run_stabilizer_shots(
377 circuit: &QuantumCircuit,
378 shots: u32,
379 seed: u64,
380) -> HashMap<Vec<bool>, usize> {
381 let n = circuit.num_qubits() as usize;
382 let mut counts: HashMap<Vec<bool>, usize> = HashMap::new();
383
384 let has_measurements = circuit
385 .gates()
386 .iter()
387 .any(|g| matches!(g, Gate::Measure(_)));
388
389 for shot in 0..shots {
390 let shot_seed = seed.wrapping_add(shot as u64);
391 let mut state = StabilizerState::new_with_seed(n, shot_seed)
392 .expect("failed to create stabilizer state");
393
394 let mut measured_bits: Vec<Option<bool>> = vec![None; n];
395
396 for gate in circuit.gates() {
397 match gate {
398 Gate::Reset(q) => {
399 let qubit = *q as usize;
401 let outcome = state
402 .measure(qubit)
403 .expect("stabilizer measurement failed");
404 if outcome.result {
405 state.x_gate(qubit);
406 }
407 measured_bits[qubit] = None;
409 }
410 Gate::Measure(q) => {
411 let outcomes = state
412 .apply_gate(gate)
413 .expect("stabilizer gate application failed");
414 if let Some(outcome) = outcomes.first() {
415 measured_bits[*q as usize] = Some(outcome.result);
416 }
417 }
418 _ => {
419 state
420 .apply_gate(gate)
421 .expect("stabilizer gate application failed");
422 }
423 }
424 }
425
426 if !has_measurements {
428 for q in 0..n {
429 let outcome = state
430 .measure(q)
431 .expect("stabilizer measurement failed");
432 measured_bits[q] = Some(outcome.result);
433 }
434 }
435
436 let bits: Vec<bool> = measured_bits
438 .iter()
439 .map(|mb| mb.unwrap_or(false))
440 .collect();
441
442 *counts.entry(bits).or_insert(0) += 1;
443 }
444
445 counts
446}
447
448pub fn normalize_counts(
457 counts: &HashMap<Vec<bool>, usize>,
458) -> HashMap<Vec<bool>, f64> {
459 let total: usize = counts.values().sum();
460 if total == 0 {
461 return HashMap::new();
462 }
463 let total_f = total as f64;
464 counts
465 .iter()
466 .map(|(k, &v)| (k.clone(), v as f64 / total_f))
467 .collect()
468}
469
470pub fn tvd(
477 p: &HashMap<Vec<bool>, f64>,
478 q: &HashMap<Vec<bool>, f64>,
479) -> f64 {
480 let mut all_keys: Vec<&Vec<bool>> =
481 p.keys().chain(q.keys()).collect();
482 all_keys.sort();
483 all_keys.dedup();
484
485 let sum: f64 = all_keys
486 .iter()
487 .map(|key| {
488 let pv = p.get(*key).copied().unwrap_or(0.0);
489 let qv = q.get(*key).copied().unwrap_or(0.0);
490 (pv - qv).abs()
491 })
492 .sum();
493
494 0.5 * sum
495}
496
497pub fn chi_squared_statistic(
513 observed: &HashMap<Vec<bool>, usize>,
514 expected_probs: &HashMap<Vec<bool>, f64>,
515 _total: usize,
516) -> (f64, usize) {
517 let obs_total: usize = observed.values().sum();
518 if obs_total == 0 {
519 return (0.0, 0);
520 }
521 let obs_total_f = obs_total as f64;
522
523 let mut all_keys: Vec<&Vec<bool>> = observed
524 .keys()
525 .chain(expected_probs.keys())
526 .collect();
527 all_keys.sort();
528 all_keys.dedup();
529
530 let mut chi2 = 0.0;
531 let mut bins_used = 0usize;
532 let mut other_observed = 0.0;
533 let mut other_expected = 0.0;
534
535 for key in &all_keys {
536 let obs = observed.get(*key).copied().unwrap_or(0) as f64;
537 let exp_prob = expected_probs.get(*key).copied().unwrap_or(0.0);
538 let exp = exp_prob * obs_total_f;
539
540 if exp < 5.0 {
541 other_observed += obs;
543 other_expected += exp;
544 } else {
545 let diff = obs - exp;
546 chi2 += (diff * diff) / exp;
547 bins_used += 1;
548 }
549 }
550
551 if other_expected >= 5.0 {
553 let diff = other_observed - other_expected;
554 chi2 += (diff * diff) / other_expected;
555 bins_used += 1;
556 } else if other_expected > 0.0 && other_observed > 0.0 {
557 let diff = other_observed - other_expected;
559 chi2 += (diff * diff) / other_expected.max(1.0);
560 bins_used += 1;
561 }
562
563 let dof = if bins_used > 1 { bins_used - 1 } else { 0 };
565
566 (chi2, dof)
567}
568
569pub fn chi_squared_p_value(statistic: f64, dof: usize) -> f64 {
582 if dof == 0 {
583 return 1.0;
584 }
585 if statistic <= 0.0 {
586 return 1.0;
587 }
588
589 let k = dof as f64;
590
591 let term = 2.0 / (9.0 * k);
593 let cube_root = (statistic / k).powf(1.0 / 3.0);
594 let z = (cube_root - (1.0 - term)) / term.sqrt();
595
596 1.0 - standard_normal_cdf(z)
599}
600
601fn pearson_correlation(
610 p: &HashMap<Vec<bool>, f64>,
611 q: &HashMap<Vec<bool>, f64>,
612) -> f64 {
613 let mut all_keys: Vec<&Vec<bool>> =
614 p.keys().chain(q.keys()).collect();
615 all_keys.sort();
616 all_keys.dedup();
617
618 if all_keys.is_empty() {
619 return 0.0;
620 }
621
622 let n = all_keys.len() as f64;
623
624 let p_vals: Vec<f64> = all_keys
625 .iter()
626 .map(|k| p.get(*k).copied().unwrap_or(0.0))
627 .collect();
628 let q_vals: Vec<f64> = all_keys
629 .iter()
630 .map(|k| q.get(*k).copied().unwrap_or(0.0))
631 .collect();
632
633 let p_mean: f64 = p_vals.iter().sum::<f64>() / n;
634 let q_mean: f64 = q_vals.iter().sum::<f64>() / n;
635
636 let mut cov = 0.0;
637 let mut var_p = 0.0;
638 let mut var_q = 0.0;
639
640 for i in 0..all_keys.len() {
641 let dp = p_vals[i] - p_mean;
642 let dq = q_vals[i] - q_mean;
643 cov += dp * dq;
644 var_p += dp * dp;
645 var_q += dq * dq;
646 }
647
648 if var_p < 1e-30 || var_q < 1e-30 {
649 return 0.0;
650 }
651
652 cov / (var_p.sqrt() * var_q.sqrt())
653}
654
655fn standard_normal_cdf(x: f64) -> f64 {
664 if x < -8.0 {
665 return 0.0;
666 }
667 if x > 8.0 {
668 return 1.0;
669 }
670
671 let a1 = 0.254829592;
673 let a2 = -0.284496736;
674 let a3 = 1.421413741;
675 let a4 = -1.453152027;
676 let a5 = 1.061405429;
677 let p = 0.3275911;
678
679 let sign = if x < 0.0 { -1.0 } else { 1.0 };
680 let abs_x = x.abs() / std::f64::consts::SQRT_2;
681
682 let t = 1.0 / (1.0 + p * abs_x);
683 let t2 = t * t;
684 let t3 = t2 * t;
685 let t4 = t3 * t;
686 let t5 = t4 * t;
687
688 let erf_approx =
689 1.0 - (a1 * t + a2 * t2 + a3 * t3 + a4 * t4 + a5 * t5)
690 * (-abs_x * abs_x).exp();
691
692 0.5 * (1.0 + sign * erf_approx)
693}
694
695#[cfg(test)]
700mod tests {
701 use super::*;
702 use crate::circuit::QuantumCircuit;
703
704 fn make_counts(
707 entries: &[(&[bool], usize)],
708 ) -> HashMap<Vec<bool>, usize> {
709 entries
710 .iter()
711 .map(|(bits, count)| (bits.to_vec(), *count))
712 .collect()
713 }
714
715 #[test]
720 fn clifford_circuit_returns_true_for_clifford_only() {
721 let mut circ = QuantumCircuit::new(3);
722 circ.h(0).cnot(0, 1).s(2).x(0).y(1).z(2);
723 circ.cz(0, 2).swap(1, 2);
724 circ.measure(0).measure(1).measure(2);
725 assert!(is_clifford_circuit(&circ));
726 }
727
728 #[test]
729 fn clifford_circuit_returns_false_with_t_gate() {
730 let mut circ = QuantumCircuit::new(2);
731 circ.h(0).t(0).cnot(0, 1);
732 assert!(!is_clifford_circuit(&circ));
733 }
734
735 #[test]
736 fn clifford_circuit_returns_true_for_sdg_gate() {
737 let mut circ = QuantumCircuit::new(1);
738 circ.h(0);
739 circ.add_gate(Gate::Sdg(0));
740 assert!(is_clifford_circuit(&circ));
741 }
742
743 #[test]
744 fn clifford_circuit_returns_false_for_rx_gate() {
745 let mut circ = QuantumCircuit::new(1);
746 circ.rx(0, 0.5);
747 assert!(!is_clifford_circuit(&circ));
748 }
749
750 #[test]
751 fn clifford_circuit_returns_true_with_reset_and_barrier() {
752 let mut circ = QuantumCircuit::new(2);
753 circ.h(0).cnot(0, 1).barrier();
754 circ.reset(0).measure(1);
755 assert!(is_clifford_circuit(&circ));
756 }
757
758 #[test]
763 fn normalize_counts_produces_probabilities() {
764 let counts = make_counts(&[
765 (&[false, false], 50),
766 (&[true, true], 50),
767 ]);
768 let probs = normalize_counts(&counts);
769 assert!((probs[&vec![false, false]] - 0.5).abs() < 1e-10);
770 assert!((probs[&vec![true, true]] - 0.5).abs() < 1e-10);
771 }
772
773 #[test]
774 fn normalize_counts_empty_returns_empty() {
775 let counts: HashMap<Vec<bool>, usize> = HashMap::new();
776 let probs = normalize_counts(&counts);
777 assert!(probs.is_empty());
778 }
779
780 #[test]
785 fn identical_distributions_have_zero_tvd() {
786 let p: HashMap<Vec<bool>, f64> = [
787 (vec![false, false], 0.5),
788 (vec![true, true], 0.5),
789 ]
790 .into_iter()
791 .collect();
792
793 let distance = tvd(&p, &p);
794 assert!(
795 distance.abs() < 1e-15,
796 "TVD of identical distributions should be 0, got {}",
797 distance
798 );
799 }
800
801 #[test]
802 fn completely_different_distributions_have_tvd_near_one() {
803 let p: HashMap<Vec<bool>, f64> =
804 [(vec![false], 1.0)].into_iter().collect();
805 let q: HashMap<Vec<bool>, f64> =
806 [(vec![true], 1.0)].into_iter().collect();
807
808 let distance = tvd(&p, &q);
809 assert!(
810 (distance - 1.0).abs() < 1e-15,
811 "TVD of disjoint distributions should be 1, got {}",
812 distance
813 );
814 }
815
816 #[test]
817 fn tvd_partial_overlap() {
818 let p: HashMap<Vec<bool>, f64> = [
819 (vec![false], 0.7),
820 (vec![true], 0.3),
821 ]
822 .into_iter()
823 .collect();
824
825 let q: HashMap<Vec<bool>, f64> = [
826 (vec![false], 0.3),
827 (vec![true], 0.7),
828 ]
829 .into_iter()
830 .collect();
831
832 let distance = tvd(&p, &q);
833 assert!(
835 (distance - 0.4).abs() < 1e-15,
836 "Expected TVD=0.4, got {}",
837 distance
838 );
839 }
840
841 #[test]
846 fn chi_squared_perfect_fit_has_low_statistic() {
847 let observed = make_counts(&[
848 (&[false], 500),
849 (&[true], 500),
850 ]);
851 let expected: HashMap<Vec<bool>, f64> = [
852 (vec![false], 0.5),
853 (vec![true], 0.5),
854 ]
855 .into_iter()
856 .collect();
857
858 let (stat, dof) =
859 chi_squared_statistic(&observed, &expected, 1000);
860 assert!(
861 stat < 1.0,
862 "Perfect fit should have near-zero chi2, got {}",
863 stat
864 );
865 assert_eq!(dof, 1);
866
867 let pval = chi_squared_p_value(stat, dof);
868 assert!(
869 pval > 0.05,
870 "Perfect fit p-value should be large, got {}",
871 pval
872 );
873 }
874
875 #[test]
876 fn chi_squared_bad_fit_has_high_statistic() {
877 let observed = make_counts(&[
879 (&[false], 900),
880 (&[true], 100),
881 ]);
882 let expected: HashMap<Vec<bool>, f64> = [
883 (vec![false], 0.5),
884 (vec![true], 0.5),
885 ]
886 .into_iter()
887 .collect();
888
889 let (stat, dof) =
890 chi_squared_statistic(&observed, &expected, 1000);
891 assert!(
892 stat > 10.0,
893 "Bad fit should have large chi2, got {}",
894 stat
895 );
896 assert_eq!(dof, 1);
897
898 let pval = chi_squared_p_value(stat, dof);
899 assert!(
900 pval < 0.01,
901 "Bad fit p-value should be very small, got {}",
902 pval
903 );
904 }
905
906 #[test]
907 fn chi_squared_p_value_zero_dof() {
908 let pval = chi_squared_p_value(5.0, 0);
909 assert!((pval - 1.0).abs() < 1e-10);
910 }
911
912 #[test]
913 fn chi_squared_p_value_zero_statistic() {
914 let pval = chi_squared_p_value(0.0, 5);
915 assert!((pval - 1.0).abs() < 1e-10);
916 }
917
918 #[test]
923 fn identical_distributions_pass_verification() {
924 let counts = make_counts(&[
925 (&[false, false], 500),
926 (&[true, true], 500),
927 ]);
928 let result = verify_against_reference(&counts, &counts, 0.01);
929 assert!(result.passed);
930 assert!(
931 result.total_variation_distance.unwrap() < 1e-10,
932 "TVD should be 0 for identical counts"
933 );
934 }
935
936 #[test]
937 fn very_different_distributions_fail_verification() {
938 let primary = make_counts(&[(&[false], 1000)]);
939 let reference = make_counts(&[(&[true], 1000)]);
940
941 let result =
942 verify_against_reference(&primary, &reference, 0.1);
943 assert!(!result.passed);
944 assert!(
945 (result.total_variation_distance.unwrap() - 1.0).abs()
946 < 1e-10,
947 "TVD should be 1 for disjoint distributions"
948 );
949 }
950
951 #[test]
952 fn discrepancies_sorted_by_absolute_difference() {
953 let primary = make_counts(&[
954 (&[false, false], 400),
955 (&[false, true], 300),
956 (&[true, false], 200),
957 (&[true, true], 100),
958 ]);
959 let reference = make_counts(&[
960 (&[false, false], 250),
961 (&[false, true], 250),
962 (&[true, false], 250),
963 (&[true, true], 250),
964 ]);
965
966 let result =
967 verify_against_reference(&primary, &reference, 0.5);
968
969 for i in 1..result.discrepancies.len() {
971 assert!(
972 result.discrepancies[i - 1].absolute_difference
973 >= result.discrepancies[i].absolute_difference,
974 "Discrepancies should be sorted descending by \
975 absolute_difference: {} < {}",
976 result.discrepancies[i - 1].absolute_difference,
977 result.discrepancies[i].absolute_difference
978 );
979 }
980
981 assert!(
987 result.discrepancies[0].absolute_difference >= 0.14,
988 "Top discrepancy should have absolute_difference >= 0.14, got {}",
989 result.discrepancies[0].absolute_difference
990 );
991 }
992
993 #[test]
998 fn stabilizer_shots_zero_state_gives_all_zeros() {
999 let mut circ = QuantumCircuit::new(3);
1001 circ.measure(0).measure(1).measure(2);
1002
1003 let counts = run_stabilizer_shots(&circ, 100, 42);
1004
1005 assert_eq!(counts.len(), 1, "Should have exactly one outcome");
1007 assert_eq!(
1008 counts[&vec![false, false, false]],
1009 100,
1010 "All 100 shots should give |000>"
1011 );
1012 }
1013
1014 #[test]
1015 fn stabilizer_shots_bell_state_gives_correlated_results() {
1016 let mut circ = QuantumCircuit::new(2);
1017 circ.h(0).cnot(0, 1).measure(0).measure(1);
1018
1019 let counts = run_stabilizer_shots(&circ, 1000, 42);
1020
1021 for (bits, _count) in &counts {
1023 assert_eq!(
1024 bits[0], bits[1],
1025 "Bell state qubits must be correlated, got {:?}",
1026 bits
1027 );
1028 }
1029
1030 assert!(
1032 counts.contains_key(&vec![false, false]),
1033 "Should see |00> outcome"
1034 );
1035 assert!(
1036 counts.contains_key(&vec![true, true]),
1037 "Should see |11> outcome"
1038 );
1039
1040 let count_00 =
1042 counts.get(&vec![false, false]).copied().unwrap_or(0);
1043 let count_11 =
1044 counts.get(&vec![true, true]).copied().unwrap_or(0);
1045 assert_eq!(count_00 + count_11, 1000);
1046 assert!(
1047 count_00 > 350 && count_00 < 650,
1048 "Expected roughly 50/50, got {}/{}",
1049 count_00,
1050 count_11
1051 );
1052 }
1053
1054 #[test]
1055 fn stabilizer_shots_implicit_measurement() {
1056 let mut circ = QuantumCircuit::new(2);
1058 circ.h(0).cnot(0, 1);
1059
1060 let counts = run_stabilizer_shots(&circ, 500, 99);
1061
1062 for (bits, _count) in &counts {
1064 assert_eq!(bits[0], bits[1], "Bell state must be correlated");
1065 }
1066 }
1067
1068 #[test]
1073 fn bell_state_passes_exact_verification() {
1074 let mut circ = QuantumCircuit::new(2);
1075 circ.h(0).cnot(0, 1).measure(0).measure(1);
1076
1077 let result = verify_circuit(&circ, 2000, 42);
1078
1079 assert_eq!(result.primary_backend, BackendType::StateVector);
1080 assert_eq!(
1081 result.reference_backend,
1082 Some(BackendType::Stabilizer)
1083 );
1084 assert!(
1085 result.passed,
1086 "Bell state should pass verification: {}",
1087 result.explanation
1088 );
1089 assert!(
1091 result.level == VerificationLevel::Exact
1092 || result.level == VerificationLevel::Statistical,
1093 "Expected Exact or Statistical, got {:?}",
1094 result.level
1095 );
1096 }
1097
1098 #[test]
1099 fn non_clifford_circuit_is_skipped() {
1100 let mut circ = QuantumCircuit::new(2);
1101 circ.h(0).t(0).cnot(0, 1).measure(0).measure(1);
1102
1103 let result = verify_circuit(&circ, 1000, 42);
1104
1105 assert_eq!(result.level, VerificationLevel::Skipped);
1106 assert!(result.reference_backend.is_none());
1107 assert!(
1108 result.explanation.contains("non-Clifford"),
1109 "Explanation should mention non-Clifford gates: {}",
1110 result.explanation
1111 );
1112 }
1113
1114 #[test]
1115 fn ghz_state_passes_verification() {
1116 let mut circ = QuantumCircuit::new(4);
1117 circ.h(0);
1118 circ.cnot(0, 1).cnot(1, 2).cnot(2, 3);
1119 circ.measure(0).measure(1).measure(2).measure(3);
1120
1121 let result = verify_circuit(&circ, 2000, 123);
1122
1123 assert!(
1124 result.passed,
1125 "GHZ state should pass verification: {}",
1126 result.explanation
1127 );
1128 }
1129
1130 #[test]
1135 fn empty_circuit_passes_verification() {
1136 let mut circ = QuantumCircuit::new(2);
1137 circ.measure(0).measure(1);
1138
1139 let result = verify_circuit(&circ, 100, 0);
1140
1141 assert!(result.passed);
1142 assert_eq!(
1144 result.reference_backend,
1145 Some(BackendType::Stabilizer)
1146 );
1147 }
1148
1149 #[test]
1150 fn pearson_correlation_identical_distributions() {
1151 let p: HashMap<Vec<bool>, f64> = [
1152 (vec![false], 0.3),
1153 (vec![true], 0.7),
1154 ]
1155 .into_iter()
1156 .collect();
1157
1158 let corr = pearson_correlation(&p, &p);
1159 assert!(
1160 (corr - 1.0).abs() < 1e-10,
1161 "Identical distributions should have correlation 1.0, got {}",
1162 corr
1163 );
1164 }
1165
1166 #[test]
1167 fn standard_normal_cdf_known_values() {
1168 assert!(
1170 (standard_normal_cdf(0.0) - 0.5).abs() < 1e-6,
1171 "CDF(0) should be 0.5"
1172 );
1173 assert!(
1175 standard_normal_cdf(-10.0) < 1e-10,
1176 "CDF(-10) should be near 0"
1177 );
1178 assert!(
1180 (standard_normal_cdf(10.0) - 1.0).abs() < 1e-10,
1181 "CDF(10) should be near 1"
1182 );
1183 assert!(
1185 (standard_normal_cdf(1.96) - 0.975).abs() < 0.01,
1186 "CDF(1.96) should be near 0.975, got {}",
1187 standard_normal_cdf(1.96)
1188 );
1189 }
1190}