ruqu-core 2.0.5

High-performance quantum circuit simulator in pure Rust - state-vector simulation with SIMD acceleration, noise models, and multi-threading
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
//! Cross-backend automatic verification for quantum circuit simulation.
//!
//! This module provides tools to verify simulation results by running circuits
//! on multiple backends and comparing their output distributions. For pure
//! Clifford circuits, the stabilizer backend serves as an efficient reference
//! implementation that can be compared bitwise against the state-vector backend.
//!
//! # Verification levels
//!
//! | Level | Method | When used |
//! |-------|--------|-----------|
//! | Exact | Bitwise match of distributions | Clifford circuits, <= 25 qubits |
//! | Statistical | Chi-squared + TVD | General comparison of two distributions |
//! | Trend | Correlation of energy landscape | Future: Hamiltonian-level comparison |
//! | Skipped | N/A | Non-Clifford or no reference available |

use crate::backend::{analyze_circuit, BackendType};
use crate::circuit::QuantumCircuit;
use crate::gate::Gate;
use crate::simulator::Simulator;
use crate::stabilizer::StabilizerState;

use std::collections::HashMap;

// ---------------------------------------------------------------------------
// Public types
// ---------------------------------------------------------------------------

/// How rigorously the verification was performed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VerificationLevel {
    /// Bitwise match (Clifford circuits: stabilizer vs state vector).
    Exact,
    /// Chi-squared test within tolerance.
    Statistical,
    /// Correlation of energy landscape.
    Trend,
    /// Verification not applicable.
    Skipped,
}

/// Outcome of a cross-backend verification run.
#[derive(Debug, Clone)]
pub struct VerificationResult {
    /// The level of verification that was performed.
    pub level: VerificationLevel,
    /// Whether the verification passed.
    pub passed: bool,
    /// The backend used for the primary simulation.
    pub primary_backend: BackendType,
    /// The backend used for the reference simulation, if any.
    pub reference_backend: Option<BackendType>,
    /// Total variation distance between the two distributions.
    pub total_variation_distance: Option<f64>,
    /// P-value from the chi-squared goodness-of-fit test.
    pub chi_squared_p_value: Option<f64>,
    /// Pearson correlation coefficient between distributions.
    pub correlation: Option<f64>,
    /// Human-readable explanation of the verification outcome.
    pub explanation: String,
    /// Individual bitstring discrepancies, sorted by absolute difference.
    pub discrepancies: Vec<Discrepancy>,
}

/// A single bitstring where the primary and reference distributions disagree.
#[derive(Debug, Clone)]
pub struct Discrepancy {
    /// The bitstring (one bool per qubit, qubit 0 first).
    pub bitstring: Vec<bool>,
    /// Probability of this bitstring in the primary distribution.
    pub primary_probability: f64,
    /// Probability of this bitstring in the reference distribution.
    pub reference_probability: f64,
    /// Absolute difference between the two probabilities.
    pub absolute_difference: f64,
}

// ---------------------------------------------------------------------------
// Main verification entry point
// ---------------------------------------------------------------------------

/// Verify a quantum circuit by running it on multiple backends and comparing
/// the resulting measurement distributions.
///
/// # Algorithm
///
/// 1. Analyze the circuit to determine its Clifford fraction.
/// 2. If the circuit is pure Clifford AND has <= 25 qubits, run on both the
///    state-vector and stabilizer backends, then compare the distributions at
///    the Exact level.
/// 3. If the circuit is NOT pure Clifford AND has <= 25 qubits, run on the
///    state-vector backend only and report verification as Skipped.
/// 4. For circuits exceeding 25 qubits, report as Skipped.
///
/// # Arguments
///
/// * `circuit` - The quantum circuit to verify.
/// * `shots` - Number of measurement shots per backend.
/// * `seed` - Deterministic seed for reproducibility.
pub fn verify_circuit(
    circuit: &QuantumCircuit,
    shots: u32,
    seed: u64,
) -> VerificationResult {
    let analysis = analyze_circuit(circuit);
    let num_qubits = circuit.num_qubits();
    let is_clifford = is_clifford_circuit(circuit);

    // Case 1: Pure Clifford AND small enough for state vector comparison.
    if is_clifford && num_qubits <= 25 {
        // Run on state-vector backend.
        let sv_result = Simulator::run_shots(circuit, shots, Some(seed));
        let sv_counts = match sv_result {
            Ok(r) => r.counts,
            Err(e) => {
                return VerificationResult {
                    level: VerificationLevel::Skipped,
                    passed: false,
                    primary_backend: BackendType::StateVector,
                    reference_backend: None,
                    total_variation_distance: None,
                    chi_squared_p_value: None,
                    correlation: None,
                    explanation: format!(
                        "State-vector simulation failed: {}",
                        e
                    ),
                    discrepancies: vec![],
                };
            }
        };

        // Run on stabilizer backend.
        let stab_counts = run_stabilizer_shots(circuit, shots, seed);

        // Compare the two distributions.
        let mut result = verify_against_reference(
            &sv_counts,
            &stab_counts,
            0.0, // Exact match: zero tolerance for Clifford circuits
        );

        result.primary_backend = BackendType::StateVector;
        result.reference_backend = Some(BackendType::Stabilizer);

        // Upgrade to Exact level if the distributions match perfectly.
        if result.passed
            && result
                .total_variation_distance
                .map_or(false, |d| d == 0.0)
        {
            result.level = VerificationLevel::Exact;
            result.explanation = format!(
                "Exact match: {}-qubit Clifford circuit verified across \
                 state-vector and stabilizer backends ({} shots, \
                 clifford_fraction={:.2})",
                num_qubits, shots, analysis.clifford_fraction
            );
        } else {
            // Even for Clifford circuits, sampling noise may cause small
            // differences. Use statistical comparison with a tight tolerance.
            let tight_tolerance = 0.05;
            let mut stat_result = verify_against_reference(
                &sv_counts,
                &stab_counts,
                tight_tolerance,
            );
            stat_result.primary_backend = BackendType::StateVector;
            stat_result.reference_backend = Some(BackendType::Stabilizer);
            stat_result.explanation = format!(
                "Statistical comparison of {}-qubit Clifford circuit across \
                 state-vector and stabilizer backends ({} shots, TVD={:.6})",
                num_qubits,
                shots,
                stat_result
                    .total_variation_distance
                    .unwrap_or(0.0)
            );
            return stat_result;
        }

        return result;
    }

    // Case 2: Not Clifford AND small enough for state vector.
    if !is_clifford && num_qubits <= 25 {
        return VerificationResult {
            level: VerificationLevel::Skipped,
            passed: true,
            primary_backend: BackendType::StateVector,
            reference_backend: None,
            total_variation_distance: None,
            chi_squared_p_value: None,
            correlation: None,
            explanation: format!(
                "Verification skipped: {}-qubit circuit contains non-Clifford \
                 gates (clifford_fraction={:.2}, {} non-Clifford gates). \
                 No reference backend available for cross-validation.",
                num_qubits,
                analysis.clifford_fraction,
                analysis.non_clifford_gates
            ),
            discrepancies: vec![],
        };
    }

    // Case 3: Too many qubits for state-vector comparison.
    VerificationResult {
        level: VerificationLevel::Skipped,
        passed: true,
        primary_backend: analysis.recommended_backend,
        reference_backend: None,
        total_variation_distance: None,
        chi_squared_p_value: None,
        correlation: None,
        explanation: format!(
            "Verification skipped: {}-qubit circuit exceeds state-vector \
             capacity for cross-backend comparison.",
            num_qubits
        ),
        discrepancies: vec![],
    }
}

// ---------------------------------------------------------------------------
// Distribution comparison
// ---------------------------------------------------------------------------

/// Compare two measurement distributions and produce a verification result.
///
/// # Arguments
///
/// * `primary` - Counts from the primary backend.
/// * `reference` - Counts from the reference backend.
/// * `tolerance` - Maximum allowed total variation distance for a pass.
///
/// # Returns
///
/// A `VerificationResult` at the `Statistical` level (or `Exact` if TVD is
/// exactly zero and tolerance is zero).
pub fn verify_against_reference(
    primary: &HashMap<Vec<bool>, usize>,
    reference: &HashMap<Vec<bool>, usize>,
    tolerance: f64,
) -> VerificationResult {
    let p_norm = normalize_counts(primary);
    let q_norm = normalize_counts(reference);

    let distance = tvd(&p_norm, &q_norm);

    let total_ref: usize = reference.values().sum();
    let (chi2_stat, dof) =
        chi_squared_statistic(primary, &q_norm, total_ref);
    let p_value = if dof > 0 {
        chi_squared_p_value(chi2_stat, dof)
    } else {
        1.0
    };

    let corr = pearson_correlation(&p_norm, &q_norm);

    // Build sorted discrepancy list.
    let mut all_keys: Vec<&Vec<bool>> =
        p_norm.keys().chain(q_norm.keys()).collect();
    all_keys.sort();
    all_keys.dedup();

    let mut discrepancies: Vec<Discrepancy> = all_keys
        .iter()
        .map(|key| {
            let pp = p_norm.get(*key).copied().unwrap_or(0.0);
            let rp = q_norm.get(*key).copied().unwrap_or(0.0);
            Discrepancy {
                bitstring: (*key).clone(),
                primary_probability: pp,
                reference_probability: rp,
                absolute_difference: (pp - rp).abs(),
            }
        })
        .filter(|d| d.absolute_difference > 1e-15)
        .collect();

    // Sort by absolute difference, descending.
    discrepancies
        .sort_by(|a, b| b.absolute_difference.partial_cmp(&a.absolute_difference).unwrap());

    let passed = distance <= tolerance;

    let level = if tolerance == 0.0 && passed {
        VerificationLevel::Exact
    } else {
        VerificationLevel::Statistical
    };

    let explanation = if passed {
        format!(
            "Verification passed: TVD={:.6}, chi2 p-value={:.4}, \
             correlation={:.4}, tolerance={:.6}",
            distance, p_value, corr, tolerance
        )
    } else {
        format!(
            "Verification FAILED: TVD={:.6} exceeds tolerance={:.6}, \
             chi2 p-value={:.4}, correlation={:.4}, \
             {} discrepancies found",
            distance,
            tolerance,
            p_value,
            corr,
            discrepancies.len()
        )
    };

    VerificationResult {
        level,
        passed,
        primary_backend: BackendType::Auto,
        reference_backend: None,
        total_variation_distance: Some(distance),
        chi_squared_p_value: Some(p_value),
        correlation: Some(corr),
        explanation,
        discrepancies,
    }
}

// ---------------------------------------------------------------------------
// Clifford circuit detection
// ---------------------------------------------------------------------------

/// Check if ALL gates in a circuit are Clifford-compatible.
///
/// Clifford-compatible gates are: H, X, Y, Z, S, Sdg, CNOT, CZ, SWAP,
/// Measure, Reset, and Barrier. Any other gate (T, Tdg, rotations, custom
/// unitaries) makes the circuit non-Clifford.
pub fn is_clifford_circuit(circuit: &QuantumCircuit) -> bool {
    circuit.gates().iter().all(|gate| is_clifford_gate(gate))
}

/// Check if a single gate is Clifford-compatible.
fn is_clifford_gate(gate: &Gate) -> bool {
    matches!(
        gate,
        Gate::H(_)
            | Gate::X(_)
            | Gate::Y(_)
            | Gate::Z(_)
            | Gate::S(_)
            | Gate::Sdg(_)
            | Gate::CNOT(_, _)
            | Gate::CZ(_, _)
            | Gate::SWAP(_, _)
            | Gate::Measure(_)
            | Gate::Reset(_)
            | Gate::Barrier
    )
}

// ---------------------------------------------------------------------------
// Stabilizer shot execution
// ---------------------------------------------------------------------------

/// Execute a Clifford circuit on the stabilizer backend for multiple shots.
///
/// For each shot, creates a fresh `StabilizerState`, applies all gates in
/// order, and collects measurement outcomes into a histogram. If the circuit
/// contains no explicit `Measure` gates, all qubits are measured at the end.
///
/// `Reset` gates are handled by measuring the qubit and conditionally
/// applying an X gate to force it back to |0>.
///
/// # Panics
///
/// Panics if a non-Clifford gate is encountered (the caller must ensure the
/// circuit is Clifford-only via `is_clifford_circuit`).
pub fn run_stabilizer_shots(
    circuit: &QuantumCircuit,
    shots: u32,
    seed: u64,
) -> HashMap<Vec<bool>, usize> {
    let n = circuit.num_qubits() as usize;
    let mut counts: HashMap<Vec<bool>, usize> = HashMap::new();

    let has_measurements = circuit
        .gates()
        .iter()
        .any(|g| matches!(g, Gate::Measure(_)));

    for shot in 0..shots {
        let shot_seed = seed.wrapping_add(shot as u64);
        let mut state = StabilizerState::new_with_seed(n, shot_seed)
            .expect("failed to create stabilizer state");

        let mut measured_bits: Vec<Option<bool>> = vec![None; n];

        for gate in circuit.gates() {
            match gate {
                Gate::Reset(q) => {
                    // Implement reset: measure, then conditionally flip.
                    let qubit = *q as usize;
                    let outcome = state
                        .measure(qubit)
                        .expect("stabilizer measurement failed");
                    if outcome.result {
                        state.x_gate(qubit);
                    }
                    // Clear the measured bit since reset puts qubit back to |0>.
                    measured_bits[qubit] = None;
                }
                Gate::Measure(q) => {
                    let outcomes = state
                        .apply_gate(gate)
                        .expect("stabilizer gate application failed");
                    if let Some(outcome) = outcomes.first() {
                        measured_bits[*q as usize] = Some(outcome.result);
                    }
                }
                _ => {
                    state
                        .apply_gate(gate)
                        .expect("stabilizer gate application failed");
                }
            }
        }

        // If no explicit measurements, measure all qubits.
        if !has_measurements {
            for q in 0..n {
                let outcome = state
                    .measure(q)
                    .expect("stabilizer measurement failed");
                measured_bits[q] = Some(outcome.result);
            }
        }

        // Build the bit-vector for this shot.
        let bits: Vec<bool> = measured_bits
            .iter()
            .map(|mb| mb.unwrap_or(false))
            .collect();

        *counts.entry(bits).or_insert(0) += 1;
    }

    counts
}

// ---------------------------------------------------------------------------
// Helper functions: distribution normalization and metrics
// ---------------------------------------------------------------------------

/// Convert raw counts to a probability distribution.
///
/// Each count is divided by the total number of shots to produce a
/// probability in [0, 1].
pub fn normalize_counts(
    counts: &HashMap<Vec<bool>, usize>,
) -> HashMap<Vec<bool>, f64> {
    let total: usize = counts.values().sum();
    if total == 0 {
        return HashMap::new();
    }
    let total_f = total as f64;
    counts
        .iter()
        .map(|(k, &v)| (k.clone(), v as f64 / total_f))
        .collect()
}

/// Compute the total variation distance between two probability distributions.
///
/// TVD = 0.5 * sum_x |p(x) - q(x)|
///
/// Returns a value in [0, 1] where 0 means identical distributions and 1
/// means completely disjoint support.
pub fn tvd(
    p: &HashMap<Vec<bool>, f64>,
    q: &HashMap<Vec<bool>, f64>,
) -> f64 {
    let mut all_keys: Vec<&Vec<bool>> =
        p.keys().chain(q.keys()).collect();
    all_keys.sort();
    all_keys.dedup();

    let sum: f64 = all_keys
        .iter()
        .map(|key| {
            let pv = p.get(*key).copied().unwrap_or(0.0);
            let qv = q.get(*key).copied().unwrap_or(0.0);
            (pv - qv).abs()
        })
        .sum();

    0.5 * sum
}

/// Compute the chi-squared statistic for a goodness-of-fit test.
///
/// Tests whether the observed counts (from the primary distribution) are
/// consistent with the expected probabilities (from the reference
/// distribution).
///
/// Returns `(statistic, degrees_of_freedom)`. Bins with an expected count
/// below 5 are merged into an "other" bin to maintain test validity.
///
/// # Arguments
///
/// * `observed` - Raw counts from the primary distribution.
/// * `expected_probs` - Probability distribution from the reference.
/// * `total` - Total number of reference shots (used to scale expected probs
///   to expected counts).
pub fn chi_squared_statistic(
    observed: &HashMap<Vec<bool>, usize>,
    expected_probs: &HashMap<Vec<bool>, f64>,
    _total: usize,
) -> (f64, usize) {
    let obs_total: usize = observed.values().sum();
    if obs_total == 0 {
        return (0.0, 0);
    }
    let obs_total_f = obs_total as f64;

    let mut all_keys: Vec<&Vec<bool>> = observed
        .keys()
        .chain(expected_probs.keys())
        .collect();
    all_keys.sort();
    all_keys.dedup();

    let mut chi2 = 0.0;
    let mut bins_used = 0usize;
    let mut other_observed = 0.0;
    let mut other_expected = 0.0;

    for key in &all_keys {
        let obs = observed.get(*key).copied().unwrap_or(0) as f64;
        let exp_prob = expected_probs.get(*key).copied().unwrap_or(0.0);
        let exp = exp_prob * obs_total_f;

        if exp < 5.0 {
            // Merge into the "other" bin.
            other_observed += obs;
            other_expected += exp;
        } else {
            let diff = obs - exp;
            chi2 += (diff * diff) / exp;
            bins_used += 1;
        }
    }

    // Process the merged "other" bin.
    if other_expected >= 5.0 {
        let diff = other_observed - other_expected;
        chi2 += (diff * diff) / other_expected;
        bins_used += 1;
    } else if other_expected > 0.0 && other_observed > 0.0 {
        // Small expected count; include but note reduced reliability.
        let diff = other_observed - other_expected;
        chi2 += (diff * diff) / other_expected.max(1.0);
        bins_used += 1;
    }

    // Degrees of freedom = number of bins - 1 (constraint: totals match).
    let dof = if bins_used > 1 { bins_used - 1 } else { 0 };

    (chi2, dof)
}

/// Approximate the chi-squared p-value using the Wilson-Hilferty
/// normal approximation.
///
/// For a chi-squared random variable X with k degrees of freedom:
///
/// ```text
/// z = ((X/k)^(1/3) - (1 - 2/(9k))) / sqrt(2/(9k))
/// ```
///
/// The p-value is then `1 - Phi(z)` where `Phi` is the standard normal CDF.
///
/// This approximation is accurate for k >= 3 and reasonable for k >= 1.
pub fn chi_squared_p_value(statistic: f64, dof: usize) -> f64 {
    if dof == 0 {
        return 1.0;
    }
    if statistic <= 0.0 {
        return 1.0;
    }

    let k = dof as f64;

    // Wilson-Hilferty approximation.
    let term = 2.0 / (9.0 * k);
    let cube_root = (statistic / k).powf(1.0 / 3.0);
    let z = (cube_root - (1.0 - term)) / term.sqrt();

    // Standard normal survival function: 1 - Phi(z).
    // Use the complementary error function approximation.
    1.0 - standard_normal_cdf(z)
}

// ---------------------------------------------------------------------------
// Pearson correlation
// ---------------------------------------------------------------------------

/// Compute the Pearson correlation coefficient between two distributions.
///
/// Returns a value in [-1, 1]. Returns 0.0 if either distribution has zero
/// variance (constant).
fn pearson_correlation(
    p: &HashMap<Vec<bool>, f64>,
    q: &HashMap<Vec<bool>, f64>,
) -> f64 {
    let mut all_keys: Vec<&Vec<bool>> =
        p.keys().chain(q.keys()).collect();
    all_keys.sort();
    all_keys.dedup();

    if all_keys.is_empty() {
        return 0.0;
    }

    let n = all_keys.len() as f64;

    let p_vals: Vec<f64> = all_keys
        .iter()
        .map(|k| p.get(*k).copied().unwrap_or(0.0))
        .collect();
    let q_vals: Vec<f64> = all_keys
        .iter()
        .map(|k| q.get(*k).copied().unwrap_or(0.0))
        .collect();

    let p_mean: f64 = p_vals.iter().sum::<f64>() / n;
    let q_mean: f64 = q_vals.iter().sum::<f64>() / n;

    let mut cov = 0.0;
    let mut var_p = 0.0;
    let mut var_q = 0.0;

    for i in 0..all_keys.len() {
        let dp = p_vals[i] - p_mean;
        let dq = q_vals[i] - q_mean;
        cov += dp * dq;
        var_p += dp * dp;
        var_q += dq * dq;
    }

    if var_p < 1e-30 || var_q < 1e-30 {
        return 0.0;
    }

    cov / (var_p.sqrt() * var_q.sqrt())
}

// ---------------------------------------------------------------------------
// Standard normal CDF approximation
// ---------------------------------------------------------------------------

/// Approximate the standard normal CDF using the Abramowitz and Stegun
/// rational approximation (formula 26.2.17).
///
/// Accurate to approximately 7.5 decimal digits.
fn standard_normal_cdf(x: f64) -> f64 {
    if x < -8.0 {
        return 0.0;
    }
    if x > 8.0 {
        return 1.0;
    }

    // Constants for the approximation.
    let a1 = 0.254829592;
    let a2 = -0.284496736;
    let a3 = 1.421413741;
    let a4 = -1.453152027;
    let a5 = 1.061405429;
    let p = 0.3275911;

    let sign = if x < 0.0 { -1.0 } else { 1.0 };
    let abs_x = x.abs() / std::f64::consts::SQRT_2;

    let t = 1.0 / (1.0 + p * abs_x);
    let t2 = t * t;
    let t3 = t2 * t;
    let t4 = t3 * t;
    let t5 = t4 * t;

    let erf_approx =
        1.0 - (a1 * t + a2 * t2 + a3 * t3 + a4 * t4 + a5 * t5)
            * (-abs_x * abs_x).exp();

    0.5 * (1.0 + sign * erf_approx)
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use crate::circuit::QuantumCircuit;

    // -- Helper to build a count map from a list of (bitstring, count) pairs --

    fn make_counts(
        entries: &[(&[bool], usize)],
    ) -> HashMap<Vec<bool>, usize> {
        entries
            .iter()
            .map(|(bits, count)| (bits.to_vec(), *count))
            .collect()
    }

    // -----------------------------------------------------------------------
    // is_clifford_circuit
    // -----------------------------------------------------------------------

    #[test]
    fn clifford_circuit_returns_true_for_clifford_only() {
        let mut circ = QuantumCircuit::new(3);
        circ.h(0).cnot(0, 1).s(2).x(0).y(1).z(2);
        circ.cz(0, 2).swap(1, 2);
        circ.measure(0).measure(1).measure(2);
        assert!(is_clifford_circuit(&circ));
    }

    #[test]
    fn clifford_circuit_returns_false_with_t_gate() {
        let mut circ = QuantumCircuit::new(2);
        circ.h(0).t(0).cnot(0, 1);
        assert!(!is_clifford_circuit(&circ));
    }

    #[test]
    fn clifford_circuit_returns_true_for_sdg_gate() {
        let mut circ = QuantumCircuit::new(1);
        circ.h(0);
        circ.add_gate(Gate::Sdg(0));
        assert!(is_clifford_circuit(&circ));
    }

    #[test]
    fn clifford_circuit_returns_false_for_rx_gate() {
        let mut circ = QuantumCircuit::new(1);
        circ.rx(0, 0.5);
        assert!(!is_clifford_circuit(&circ));
    }

    #[test]
    fn clifford_circuit_returns_true_with_reset_and_barrier() {
        let mut circ = QuantumCircuit::new(2);
        circ.h(0).cnot(0, 1).barrier();
        circ.reset(0).measure(1);
        assert!(is_clifford_circuit(&circ));
    }

    // -----------------------------------------------------------------------
    // normalize_counts
    // -----------------------------------------------------------------------

    #[test]
    fn normalize_counts_produces_probabilities() {
        let counts = make_counts(&[
            (&[false, false], 50),
            (&[true, true], 50),
        ]);
        let probs = normalize_counts(&counts);
        assert!((probs[&vec![false, false]] - 0.5).abs() < 1e-10);
        assert!((probs[&vec![true, true]] - 0.5).abs() < 1e-10);
    }

    #[test]
    fn normalize_counts_empty_returns_empty() {
        let counts: HashMap<Vec<bool>, usize> = HashMap::new();
        let probs = normalize_counts(&counts);
        assert!(probs.is_empty());
    }

    // -----------------------------------------------------------------------
    // tvd
    // -----------------------------------------------------------------------

    #[test]
    fn identical_distributions_have_zero_tvd() {
        let p: HashMap<Vec<bool>, f64> = [
            (vec![false, false], 0.5),
            (vec![true, true], 0.5),
        ]
        .into_iter()
        .collect();

        let distance = tvd(&p, &p);
        assert!(
            distance.abs() < 1e-15,
            "TVD of identical distributions should be 0, got {}",
            distance
        );
    }

    #[test]
    fn completely_different_distributions_have_tvd_near_one() {
        let p: HashMap<Vec<bool>, f64> =
            [(vec![false], 1.0)].into_iter().collect();
        let q: HashMap<Vec<bool>, f64> =
            [(vec![true], 1.0)].into_iter().collect();

        let distance = tvd(&p, &q);
        assert!(
            (distance - 1.0).abs() < 1e-15,
            "TVD of disjoint distributions should be 1, got {}",
            distance
        );
    }

    #[test]
    fn tvd_partial_overlap() {
        let p: HashMap<Vec<bool>, f64> = [
            (vec![false], 0.7),
            (vec![true], 0.3),
        ]
        .into_iter()
        .collect();

        let q: HashMap<Vec<bool>, f64> = [
            (vec![false], 0.3),
            (vec![true], 0.7),
        ]
        .into_iter()
        .collect();

        let distance = tvd(&p, &q);
        // TVD = 0.5 * (|0.7-0.3| + |0.3-0.7|) = 0.5 * (0.4 + 0.4) = 0.4
        assert!(
            (distance - 0.4).abs() < 1e-15,
            "Expected TVD=0.4, got {}",
            distance
        );
    }

    // -----------------------------------------------------------------------
    // chi_squared_statistic and chi_squared_p_value
    // -----------------------------------------------------------------------

    #[test]
    fn chi_squared_perfect_fit_has_low_statistic() {
        let observed = make_counts(&[
            (&[false], 500),
            (&[true], 500),
        ]);
        let expected: HashMap<Vec<bool>, f64> = [
            (vec![false], 0.5),
            (vec![true], 0.5),
        ]
        .into_iter()
        .collect();

        let (stat, dof) =
            chi_squared_statistic(&observed, &expected, 1000);
        assert!(
            stat < 1.0,
            "Perfect fit should have near-zero chi2, got {}",
            stat
        );
        assert_eq!(dof, 1);

        let pval = chi_squared_p_value(stat, dof);
        assert!(
            pval > 0.05,
            "Perfect fit p-value should be large, got {}",
            pval
        );
    }

    #[test]
    fn chi_squared_bad_fit_has_high_statistic() {
        // Observed is heavily biased; expected is uniform.
        let observed = make_counts(&[
            (&[false], 900),
            (&[true], 100),
        ]);
        let expected: HashMap<Vec<bool>, f64> = [
            (vec![false], 0.5),
            (vec![true], 0.5),
        ]
        .into_iter()
        .collect();

        let (stat, dof) =
            chi_squared_statistic(&observed, &expected, 1000);
        assert!(
            stat > 10.0,
            "Bad fit should have large chi2, got {}",
            stat
        );
        assert_eq!(dof, 1);

        let pval = chi_squared_p_value(stat, dof);
        assert!(
            pval < 0.01,
            "Bad fit p-value should be very small, got {}",
            pval
        );
    }

    #[test]
    fn chi_squared_p_value_zero_dof() {
        let pval = chi_squared_p_value(5.0, 0);
        assert!((pval - 1.0).abs() < 1e-10);
    }

    #[test]
    fn chi_squared_p_value_zero_statistic() {
        let pval = chi_squared_p_value(0.0, 5);
        assert!((pval - 1.0).abs() < 1e-10);
    }

    // -----------------------------------------------------------------------
    // verify_against_reference
    // -----------------------------------------------------------------------

    #[test]
    fn identical_distributions_pass_verification() {
        let counts = make_counts(&[
            (&[false, false], 500),
            (&[true, true], 500),
        ]);
        let result = verify_against_reference(&counts, &counts, 0.01);
        assert!(result.passed);
        assert!(
            result.total_variation_distance.unwrap() < 1e-10,
            "TVD should be 0 for identical counts"
        );
    }

    #[test]
    fn very_different_distributions_fail_verification() {
        let primary = make_counts(&[(&[false], 1000)]);
        let reference = make_counts(&[(&[true], 1000)]);

        let result =
            verify_against_reference(&primary, &reference, 0.1);
        assert!(!result.passed);
        assert!(
            (result.total_variation_distance.unwrap() - 1.0).abs()
                < 1e-10,
            "TVD should be 1 for disjoint distributions"
        );
    }

    #[test]
    fn discrepancies_sorted_by_absolute_difference() {
        let primary = make_counts(&[
            (&[false, false], 400),
            (&[false, true], 300),
            (&[true, false], 200),
            (&[true, true], 100),
        ]);
        let reference = make_counts(&[
            (&[false, false], 250),
            (&[false, true], 250),
            (&[true, false], 250),
            (&[true, true], 250),
        ]);

        let result =
            verify_against_reference(&primary, &reference, 0.5);

        // Verify discrepancies are sorted descending by absolute_difference.
        for i in 1..result.discrepancies.len() {
            assert!(
                result.discrepancies[i - 1].absolute_difference
                    >= result.discrepancies[i].absolute_difference,
                "Discrepancies should be sorted descending by \
                 absolute_difference: {} < {}",
                result.discrepancies[i - 1].absolute_difference,
                result.discrepancies[i].absolute_difference
            );
        }

        // The largest discrepancy should be for [false, false] or [true, true].
        // primary [false,false] = 0.4, reference = 0.25, diff = 0.15
        // primary [true,true] = 0.1, reference = 0.25, diff = 0.15
        // primary [false,true] = 0.3, reference = 0.25, diff = 0.05
        // primary [true,false] = 0.2, reference = 0.25, diff = 0.05
        assert!(
            result.discrepancies[0].absolute_difference >= 0.14,
            "Top discrepancy should have absolute_difference >= 0.14, got {}",
            result.discrepancies[0].absolute_difference
        );
    }

    // -----------------------------------------------------------------------
    // run_stabilizer_shots
    // -----------------------------------------------------------------------

    #[test]
    fn stabilizer_shots_zero_state_gives_all_zeros() {
        // Circuit with no gates, just measure all qubits.
        let mut circ = QuantumCircuit::new(3);
        circ.measure(0).measure(1).measure(2);

        let counts = run_stabilizer_shots(&circ, 100, 42);

        // All outcomes should be [false, false, false].
        assert_eq!(counts.len(), 1, "Should have exactly one outcome");
        assert_eq!(
            counts[&vec![false, false, false]],
            100,
            "All 100 shots should give |000>"
        );
    }

    #[test]
    fn stabilizer_shots_bell_state_gives_correlated_results() {
        let mut circ = QuantumCircuit::new(2);
        circ.h(0).cnot(0, 1).measure(0).measure(1);

        let counts = run_stabilizer_shots(&circ, 1000, 42);

        // A Bell state should only produce |00> and |11>.
        for (bits, _count) in &counts {
            assert_eq!(
                bits[0], bits[1],
                "Bell state qubits must be correlated, got {:?}",
                bits
            );
        }

        // Both outcomes should appear (with high probability at 1000 shots).
        assert!(
            counts.contains_key(&vec![false, false]),
            "Should see |00> outcome"
        );
        assert!(
            counts.contains_key(&vec![true, true]),
            "Should see |11> outcome"
        );

        // Check roughly 50/50 split (within a generous margin).
        let count_00 =
            counts.get(&vec![false, false]).copied().unwrap_or(0);
        let count_11 =
            counts.get(&vec![true, true]).copied().unwrap_or(0);
        assert_eq!(count_00 + count_11, 1000);
        assert!(
            count_00 > 350 && count_00 < 650,
            "Expected roughly 50/50, got {}/{}",
            count_00,
            count_11
        );
    }

    #[test]
    fn stabilizer_shots_implicit_measurement() {
        // No explicit measure gates: all qubits measured at the end.
        let mut circ = QuantumCircuit::new(2);
        circ.h(0).cnot(0, 1);

        let counts = run_stabilizer_shots(&circ, 500, 99);

        // Bell state: only |00> and |11> should appear.
        for (bits, _count) in &counts {
            assert_eq!(bits[0], bits[1], "Bell state must be correlated");
        }
    }

    // -----------------------------------------------------------------------
    // verify_circuit (integration tests)
    // -----------------------------------------------------------------------

    #[test]
    fn bell_state_passes_exact_verification() {
        let mut circ = QuantumCircuit::new(2);
        circ.h(0).cnot(0, 1).measure(0).measure(1);

        let result = verify_circuit(&circ, 2000, 42);

        assert_eq!(result.primary_backend, BackendType::StateVector);
        assert_eq!(
            result.reference_backend,
            Some(BackendType::Stabilizer)
        );
        assert!(
            result.passed,
            "Bell state should pass verification: {}",
            result.explanation
        );
        // Should be Exact or Statistical (both acceptable for Clifford).
        assert!(
            result.level == VerificationLevel::Exact
                || result.level == VerificationLevel::Statistical,
            "Expected Exact or Statistical, got {:?}",
            result.level
        );
    }

    #[test]
    fn non_clifford_circuit_is_skipped() {
        let mut circ = QuantumCircuit::new(2);
        circ.h(0).t(0).cnot(0, 1).measure(0).measure(1);

        let result = verify_circuit(&circ, 1000, 42);

        assert_eq!(result.level, VerificationLevel::Skipped);
        assert!(result.reference_backend.is_none());
        assert!(
            result.explanation.contains("non-Clifford"),
            "Explanation should mention non-Clifford gates: {}",
            result.explanation
        );
    }

    #[test]
    fn ghz_state_passes_verification() {
        let mut circ = QuantumCircuit::new(4);
        circ.h(0);
        circ.cnot(0, 1).cnot(1, 2).cnot(2, 3);
        circ.measure(0).measure(1).measure(2).measure(3);

        let result = verify_circuit(&circ, 2000, 123);

        assert!(
            result.passed,
            "GHZ state should pass verification: {}",
            result.explanation
        );
    }

    // -----------------------------------------------------------------------
    // Edge cases
    // -----------------------------------------------------------------------

    #[test]
    fn empty_circuit_passes_verification() {
        let mut circ = QuantumCircuit::new(2);
        circ.measure(0).measure(1);

        let result = verify_circuit(&circ, 100, 0);

        assert!(result.passed);
        // Pure Clifford (only measurements), should do cross-backend check.
        assert_eq!(
            result.reference_backend,
            Some(BackendType::Stabilizer)
        );
    }

    #[test]
    fn pearson_correlation_identical_distributions() {
        let p: HashMap<Vec<bool>, f64> = [
            (vec![false], 0.3),
            (vec![true], 0.7),
        ]
        .into_iter()
        .collect();

        let corr = pearson_correlation(&p, &p);
        assert!(
            (corr - 1.0).abs() < 1e-10,
            "Identical distributions should have correlation 1.0, got {}",
            corr
        );
    }

    #[test]
    fn standard_normal_cdf_known_values() {
        // Phi(0) = 0.5
        assert!(
            (standard_normal_cdf(0.0) - 0.5).abs() < 1e-6,
            "CDF(0) should be 0.5"
        );
        // Phi(-inf) -> 0
        assert!(
            standard_normal_cdf(-10.0) < 1e-10,
            "CDF(-10) should be near 0"
        );
        // Phi(+inf) -> 1
        assert!(
            (standard_normal_cdf(10.0) - 1.0).abs() < 1e-10,
            "CDF(10) should be near 1"
        );
        // Phi(1.96) ~ 0.975
        assert!(
            (standard_normal_cdf(1.96) - 0.975).abs() < 0.01,
            "CDF(1.96) should be near 0.975, got {}",
            standard_normal_cdf(1.96)
        );
    }
}