Skip to main content

ruqu_core/
benchmark.rs

1//! Comprehensive benchmark and proof suite for ruqu-core's four flagship
2//! capabilities: cost-model routing, entanglement budgeting, adaptive
3//! decoding, and cross-backend certification.
4//!
5//! All benchmarks are deterministic (seeded RNG) and self-contained,
6//! using only `rand` and `std` beyond crate-internal imports.
7
8use rand::rngs::StdRng;
9use rand::{Rng, SeedableRng};
10use std::time::Instant;
11
12use crate::backend::{analyze_circuit, BackendType};
13use crate::circuit::QuantumCircuit;
14use crate::confidence::total_variation_distance;
15use crate::decoder::{
16    PartitionedDecoder, StabilizerMeasurement, SurfaceCodeDecoder, SyndromeData,
17    UnionFindDecoder,
18};
19use crate::decomposition::{classify_segment, decompose, estimate_segment_cost};
20use crate::planner::{plan_execution, PlannerConfig};
21use crate::simulator::Simulator;
22use crate::verification::{is_clifford_circuit, run_stabilizer_shots};
23
24// ---------------------------------------------------------------------------
25// Proof 1: Routing benchmark
26// ---------------------------------------------------------------------------
27
28/// Result for a single circuit's routing comparison.
29pub struct RoutingResult {
30    pub circuit_id: usize,
31    pub num_qubits: u32,
32    pub depth: u32,
33    pub t_count: u32,
34    pub naive_time_ns: u64,
35    pub heuristic_time_ns: u64,
36    pub planner_time_ns: u64,
37    pub planner_backend: String,
38    pub speedup_vs_naive: f64,
39    pub speedup_vs_heuristic: f64,
40}
41
42/// Aggregated routing benchmark across many circuits.
43pub struct RoutingBenchmark {
44    pub num_circuits: usize,
45    pub results: Vec<RoutingResult>,
46}
47
48impl RoutingBenchmark {
49    /// Percentage of circuits where the cost-model planner matches or beats
50    /// the naive selector on predicted runtime.
51    pub fn planner_win_rate_vs_naive(&self) -> f64 {
52        if self.results.is_empty() {
53            return 0.0;
54        }
55        let wins = self
56            .results
57            .iter()
58            .filter(|r| r.planner_time_ns <= r.naive_time_ns)
59            .count();
60        wins as f64 / self.results.len() as f64 * 100.0
61    }
62
63    /// Median speedup of planner vs naive.
64    pub fn median_speedup_vs_naive(&self) -> f64 {
65        if self.results.is_empty() {
66            return 1.0;
67        }
68        let mut speedups: Vec<f64> = self.results.iter().map(|r| r.speedup_vs_naive).collect();
69        speedups.sort_by(|a, b| a.partial_cmp(b).unwrap());
70        speedups[speedups.len() / 2]
71    }
72}
73
74/// Simulate the predicted runtime (nanoseconds) for a circuit on a specific
75/// backend, using the planner's cost model.
76fn predicted_runtime_ns(circuit: &QuantumCircuit, backend: BackendType) -> u64 {
77    let analysis = analyze_circuit(circuit);
78    let n = analysis.num_qubits;
79    let gates = analysis.total_gates;
80    match backend {
81        BackendType::Stabilizer => {
82            let ns = (n as f64) * (n as f64) * (gates as f64) * 0.1;
83            ns as u64
84        }
85        BackendType::StateVector => {
86            if n >= 64 {
87                return u64::MAX;
88            }
89            let base = (1u64 << n) as f64 * gates as f64 * 4.0;
90            let scaling = if n > 25 {
91                2.0_f64.powi((n - 25) as i32)
92            } else {
93                1.0
94            };
95            (base * scaling) as u64
96        }
97        BackendType::TensorNetwork => {
98            let chi = 64.0_f64;
99            let ns = (n as f64) * chi * chi * chi * (gates as f64) * 2.0;
100            ns as u64
101        }
102        BackendType::CliffordT => {
103            // 2^t stabiliser terms, each O(n^2) per gate.
104            let t = analysis.non_clifford_gates as u32;
105            let terms = 1u64.checked_shl(t).unwrap_or(u64::MAX);
106            let flops_per_gate = 4 * (n as u64) * (n as u64);
107            let ns = terms as f64 * flops_per_gate as f64 * gates as f64 * 0.1;
108            ns as u64
109        }
110        BackendType::Auto => {
111            let plan = plan_execution(circuit, &PlannerConfig::default());
112            predicted_runtime_ns(circuit, plan.backend)
113        }
114    }
115}
116
117/// Naive selector: always picks StateVector.
118fn naive_select(_circuit: &QuantumCircuit) -> BackendType {
119    BackendType::StateVector
120}
121
122/// Simple heuristic: Clifford fraction > 0.95 => Stabilizer, else StateVector.
123fn heuristic_select(circuit: &QuantumCircuit) -> BackendType {
124    let analysis = analyze_circuit(circuit);
125    if analysis.clifford_fraction > 0.95 {
126        BackendType::Stabilizer
127    } else {
128        BackendType::StateVector
129    }
130}
131
132/// Run the routing benchmark: generate diverse circuits, route through
133/// three selectors, and compare predicted runtimes.
134pub fn run_routing_benchmark(seed: u64, num_circuits: usize) -> RoutingBenchmark {
135    let mut rng = StdRng::seed_from_u64(seed);
136    let config = PlannerConfig::default();
137    let mut results = Vec::with_capacity(num_circuits);
138
139    for id in 0..num_circuits {
140        let kind = id % 5;
141        let circuit = match kind {
142            0 => gen_clifford_circuit(&mut rng),
143            1 => gen_low_t_circuit(&mut rng),
144            2 => gen_high_t_circuit(&mut rng),
145            3 => gen_large_nn_circuit(&mut rng),
146            _ => gen_mixed_circuit(&mut rng),
147        };
148
149        let analysis = analyze_circuit(&circuit);
150        let t_count = analysis.non_clifford_gates as u32;
151        let depth = circuit.depth();
152        let num_qubits = circuit.num_qubits();
153
154        let plan = plan_execution(&circuit, &config);
155        let planner_backend = plan.backend;
156
157        let naive_backend = naive_select(&circuit);
158        let heuristic_backend = heuristic_select(&circuit);
159
160        let planner_time = predicted_runtime_ns(&circuit, planner_backend);
161        let naive_time = predicted_runtime_ns(&circuit, naive_backend);
162        let heuristic_time = predicted_runtime_ns(&circuit, heuristic_backend);
163
164        let speedup_naive = if planner_time > 0 {
165            naive_time as f64 / planner_time as f64
166        } else {
167            1.0
168        };
169        let speedup_heuristic = if planner_time > 0 {
170            heuristic_time as f64 / planner_time as f64
171        } else {
172            1.0
173        };
174
175        results.push(RoutingResult {
176            circuit_id: id,
177            num_qubits,
178            depth,
179            t_count,
180            naive_time_ns: naive_time,
181            heuristic_time_ns: heuristic_time,
182            planner_time_ns: planner_time,
183            planner_backend: format!("{:?}", planner_backend),
184            speedup_vs_naive: speedup_naive,
185            speedup_vs_heuristic: speedup_heuristic,
186        });
187    }
188
189    RoutingBenchmark {
190        num_circuits,
191        results,
192    }
193}
194
195// ---------------------------------------------------------------------------
196// Circuit generators (kept minimal to stay under 500 lines)
197// ---------------------------------------------------------------------------
198
199fn gen_clifford_circuit(rng: &mut StdRng) -> QuantumCircuit {
200    let n = rng.gen_range(2..=60);
201    let mut circ = QuantumCircuit::new(n);
202    for q in 0..n {
203        circ.h(q);
204    }
205    let gates = rng.gen_range(n..n * 3);
206    for _ in 0..gates {
207        let q1 = rng.gen_range(0..n);
208        let q2 = (q1 + 1) % n;
209        circ.cnot(q1, q2);
210    }
211    circ
212}
213
214fn gen_low_t_circuit(rng: &mut StdRng) -> QuantumCircuit {
215    let n = rng.gen_range(4..=20);
216    let mut circ = QuantumCircuit::new(n);
217    for q in 0..n {
218        circ.h(q);
219    }
220    for q in 0..(n - 1) {
221        circ.cnot(q, q + 1);
222    }
223    let t_count = rng.gen_range(1..=3);
224    for _ in 0..t_count {
225        circ.t(rng.gen_range(0..n));
226    }
227    circ
228}
229
230fn gen_high_t_circuit(rng: &mut StdRng) -> QuantumCircuit {
231    let n = rng.gen_range(3..=15);
232    let mut circ = QuantumCircuit::new(n);
233    let depth = rng.gen_range(5..20);
234    for _ in 0..depth {
235        for q in 0..n {
236            if rng.gen_bool(0.5) {
237                circ.t(q);
238            } else {
239                circ.h(q);
240            }
241        }
242        if n > 1 {
243            let q1 = rng.gen_range(0..n - 1);
244            circ.cnot(q1, q1 + 1);
245        }
246    }
247    circ
248}
249
250fn gen_large_nn_circuit(rng: &mut StdRng) -> QuantumCircuit {
251    let n = rng.gen_range(40..=100);
252    let mut circ = QuantumCircuit::new(n);
253    for q in 0..(n - 1) {
254        circ.cnot(q, q + 1);
255    }
256    let t_count = rng.gen_range(15..30);
257    for _ in 0..t_count {
258        circ.t(rng.gen_range(0..n));
259    }
260    circ
261}
262
263fn gen_mixed_circuit(rng: &mut StdRng) -> QuantumCircuit {
264    let n = rng.gen_range(5..=25);
265    let mut circ = QuantumCircuit::new(n);
266    let layers = rng.gen_range(3..10);
267    for _ in 0..layers {
268        for q in 0..n {
269            match rng.gen_range(0..4) {
270                0 => { circ.h(q); }
271                1 => { circ.t(q); }
272                2 => { circ.s(q); }
273                _ => { circ.x(q); }
274            }
275        }
276        if n > 1 {
277            let q1 = rng.gen_range(0..n - 1);
278            circ.cnot(q1, q1 + 1);
279        }
280    }
281    circ
282}
283
284// ---------------------------------------------------------------------------
285// Proof 2: Entanglement budget benchmark
286// ---------------------------------------------------------------------------
287
288/// Results from the entanglement budget verification.
289pub struct EntanglementBudgetBenchmark {
290    pub circuits_tested: usize,
291    pub segments_total: usize,
292    pub segments_within_budget: usize,
293    pub max_violation: f64,
294    pub decomposition_overhead_pct: f64,
295}
296
297/// Run the entanglement budget benchmark: decompose circuits into segments
298/// and verify each segment's estimated entanglement stays within budget.
299pub fn run_entanglement_benchmark(seed: u64, num_circuits: usize) -> EntanglementBudgetBenchmark {
300    let mut rng = StdRng::seed_from_u64(seed);
301    let mut segments_total = 0usize;
302    let mut segments_within = 0usize;
303    let mut max_violation = 0.0_f64;
304    let max_segment_qubits = 25;
305
306    let mut baseline_cost = 0u64;
307    let mut decomposed_cost = 0u64;
308
309    for _ in 0..num_circuits {
310        let circuit = gen_entanglement_circuit(&mut rng);
311
312        // Baseline cost: whole circuit on a single backend.
313        let base_backend = classify_segment(&circuit);
314        let base_seg = estimate_segment_cost(&circuit, base_backend);
315        baseline_cost += base_seg.estimated_flops;
316
317        // Decomposed cost: sum of segment costs.
318        let partition = decompose(&circuit, max_segment_qubits);
319        for seg in &partition.segments {
320            segments_total += 1;
321            decomposed_cost += seg.estimated_cost.estimated_flops;
322
323            // Check entanglement budget: the segment qubit count should
324            // not exceed the max_segment_qubits threshold.
325            let active = seg.circuit.num_qubits();
326            if active <= max_segment_qubits {
327                segments_within += 1;
328            } else {
329                let violation = (active - max_segment_qubits) as f64
330                    / max_segment_qubits as f64;
331                if violation > max_violation {
332                    max_violation = violation;
333                }
334            }
335        }
336    }
337
338    let overhead = if baseline_cost > 0 {
339        ((decomposed_cost as f64 / baseline_cost as f64) - 1.0) * 100.0
340    } else {
341        0.0
342    };
343
344    EntanglementBudgetBenchmark {
345        circuits_tested: num_circuits,
346        segments_total,
347        segments_within_budget: segments_within,
348        max_violation,
349        decomposition_overhead_pct: overhead.max(0.0),
350    }
351}
352
353fn gen_entanglement_circuit(rng: &mut StdRng) -> QuantumCircuit {
354    let n = rng.gen_range(6..=40);
355    let mut circ = QuantumCircuit::new(n);
356    // Create two disconnected blocks with a bridge.
357    let half = n / 2;
358    for q in 0..half.saturating_sub(1) {
359        circ.h(q);
360        circ.cnot(q, q + 1);
361    }
362    for q in half..(n - 1) {
363        circ.h(q);
364        circ.cnot(q, q + 1);
365    }
366    // Occasional bridge gate.
367    if rng.gen_bool(0.3) && half > 0 && half < n {
368        circ.cnot(half - 1, half);
369    }
370    // Sprinkle some T gates.
371    let t_count = rng.gen_range(0..5);
372    for _ in 0..t_count {
373        circ.t(rng.gen_range(0..n));
374    }
375    circ
376}
377
378// ---------------------------------------------------------------------------
379// Proof 3: Decoder benchmark
380// ---------------------------------------------------------------------------
381
382/// Result for a single code distance's decoder comparison.
383pub struct DecoderBenchmarkResult {
384    pub distance: u32,
385    pub union_find_avg_ns: f64,
386    pub partitioned_avg_ns: f64,
387    pub speedup: f64,
388    pub union_find_accuracy: f64,
389    pub partitioned_accuracy: f64,
390}
391
392/// Run the decoder benchmark across multiple code distances.
393pub fn run_decoder_benchmark(
394    seed: u64,
395    distances: &[u32],
396    rounds_per_distance: u32,
397) -> Vec<DecoderBenchmarkResult> {
398    let mut rng = StdRng::seed_from_u64(seed);
399    let error_rate = 0.05;
400    let mut results = Vec::with_capacity(distances.len());
401
402    for &d in distances {
403        let uf_decoder = UnionFindDecoder::new(0);
404        let tile_size = (d / 2).max(2);
405        let part_decoder =
406            PartitionedDecoder::new(tile_size, Box::new(UnionFindDecoder::new(0)));
407
408        let mut uf_total_ns = 0u64;
409        let mut part_total_ns = 0u64;
410        let mut uf_correct = 0u64;
411        let mut part_correct = 0u64;
412
413        for _ in 0..rounds_per_distance {
414            let syndrome = gen_syndrome(&mut rng, d, error_rate);
415
416            let uf_corr = uf_decoder.decode(&syndrome);
417            uf_total_ns += uf_corr.decode_time_ns;
418
419            let part_corr = part_decoder.decode(&syndrome);
420            part_total_ns += part_corr.decode_time_ns;
421
422            // A simple accuracy check: count defects and compare logical
423            // outcome expectation.
424            let defect_count = syndrome
425                .stabilizers
426                .iter()
427                .filter(|s| s.value)
428                .count();
429            let expected_logical = defect_count >= d as usize;
430            if uf_corr.logical_outcome == expected_logical {
431                uf_correct += 1;
432            }
433            if part_corr.logical_outcome == expected_logical {
434                part_correct += 1;
435            }
436        }
437
438        let r = rounds_per_distance as f64;
439        let uf_avg = uf_total_ns as f64 / r;
440        let part_avg = part_total_ns as f64 / r;
441        let speedup = if part_avg > 0.0 {
442            uf_avg / part_avg
443        } else {
444            1.0
445        };
446
447        results.push(DecoderBenchmarkResult {
448            distance: d,
449            union_find_avg_ns: uf_avg,
450            partitioned_avg_ns: part_avg,
451            speedup,
452            union_find_accuracy: uf_correct as f64 / r,
453            partitioned_accuracy: part_correct as f64 / r,
454        });
455    }
456
457    results
458}
459
460fn gen_syndrome(rng: &mut StdRng, distance: u32, error_rate: f64) -> SyndromeData {
461    let grid = if distance > 1 { distance - 1 } else { 1 };
462    let mut stabilizers = Vec::with_capacity((grid * grid) as usize);
463    for y in 0..grid {
464        for x in 0..grid {
465            stabilizers.push(StabilizerMeasurement {
466                x,
467                y,
468                round: 0,
469                value: rng.gen_bool(error_rate),
470            });
471        }
472    }
473    SyndromeData {
474        stabilizers,
475        code_distance: distance,
476        num_rounds: 1,
477    }
478}
479
480// ---------------------------------------------------------------------------
481// Proof 4: Cross-backend certification
482// ---------------------------------------------------------------------------
483
484/// Results from the cross-backend certification benchmark.
485pub struct CertificationBenchmark {
486    pub circuits_tested: usize,
487    pub certified: usize,
488    pub certification_rate: f64,
489    pub max_tvd: f64,
490    pub avg_tvd: f64,
491    pub tvd_bound: f64,
492}
493
494/// Run the certification benchmark: compare Clifford circuits across
495/// state-vector and stabilizer backends, measuring TVD.
496pub fn run_certification_benchmark(
497    seed: u64,
498    num_circuits: usize,
499    shots: u32,
500) -> CertificationBenchmark {
501    let mut rng = StdRng::seed_from_u64(seed);
502    let tvd_bound = 0.15;
503    let mut certified = 0usize;
504    let mut max_tvd = 0.0_f64;
505    let mut tvd_sum = 0.0_f64;
506    let mut tested = 0usize;
507
508    for i in 0..num_circuits {
509        let circuit = gen_certifiable_circuit(&mut rng);
510        if !is_clifford_circuit(&circuit) || circuit.num_qubits() > 20 {
511            continue;
512        }
513
514        tested += 1;
515        let shot_seed = seed.wrapping_add(i as u64 * 9973);
516
517        // Run on state-vector backend.
518        let sv_result = Simulator::run_shots(&circuit, shots, Some(shot_seed));
519        let sv_counts = match sv_result {
520            Ok(r) => r.counts,
521            Err(_) => continue,
522        };
523
524        // Run on stabilizer backend.
525        let stab_counts = run_stabilizer_shots(&circuit, shots, shot_seed);
526
527        // Compute TVD.
528        let tvd = total_variation_distance(&sv_counts, &stab_counts);
529        tvd_sum += tvd;
530        if tvd > max_tvd {
531            max_tvd = tvd;
532        }
533        if tvd <= tvd_bound {
534            certified += 1;
535        }
536    }
537
538    let avg_tvd = if tested > 0 {
539        tvd_sum / tested as f64
540    } else {
541        0.0
542    };
543    let cert_rate = if tested > 0 {
544        certified as f64 / tested as f64
545    } else {
546        0.0
547    };
548
549    CertificationBenchmark {
550        circuits_tested: tested,
551        certified,
552        certification_rate: cert_rate,
553        max_tvd,
554        avg_tvd,
555        tvd_bound,
556    }
557}
558
559fn gen_certifiable_circuit(rng: &mut StdRng) -> QuantumCircuit {
560    let n = rng.gen_range(2..=10);
561    let mut circ = QuantumCircuit::new(n);
562    circ.h(0);
563    for q in 0..(n - 1) {
564        circ.cnot(q, q + 1);
565    }
566    let extras = rng.gen_range(0..n * 2);
567    for _ in 0..extras {
568        let q = rng.gen_range(0..n);
569        match rng.gen_range(0..4) {
570            0 => { circ.h(q); }
571            1 => { circ.s(q); }
572            2 => { circ.x(q); }
573            _ => { circ.z(q); }
574        }
575    }
576    // Add measurements for all qubits.
577    for q in 0..n {
578        circ.measure(q);
579    }
580    circ
581}
582
583// ---------------------------------------------------------------------------
584// Master benchmark runner
585// ---------------------------------------------------------------------------
586
587/// Aggregated report from all four proof-point benchmarks.
588pub struct FullBenchmarkReport {
589    pub routing: RoutingBenchmark,
590    pub entanglement: EntanglementBudgetBenchmark,
591    pub decoder: Vec<DecoderBenchmarkResult>,
592    pub certification: CertificationBenchmark,
593    pub total_time_ms: u64,
594}
595
596/// Run all four benchmarks with a single seed for reproducibility.
597pub fn run_full_benchmark(seed: u64) -> FullBenchmarkReport {
598    let start = Instant::now();
599
600    let routing = run_routing_benchmark(seed, 1000);
601    let entanglement = run_entanglement_benchmark(seed.wrapping_add(1), 200);
602    let decoder = run_decoder_benchmark(
603        seed.wrapping_add(2),
604        &[3, 5, 7, 9, 11, 13, 15, 17, 21, 25],
605        100,
606    );
607    let certification =
608        run_certification_benchmark(seed.wrapping_add(3), 100, 500);
609
610    let total_time_ms = start.elapsed().as_millis() as u64;
611
612    FullBenchmarkReport {
613        routing,
614        entanglement,
615        decoder,
616        certification,
617        total_time_ms,
618    }
619}
620
621/// Format a full benchmark report as a human-readable text summary.
622pub fn format_report(report: &FullBenchmarkReport) -> String {
623    let mut out = String::with_capacity(2048);
624
625    out.push_str("=== ruqu-core Full Benchmark Report ===\n\n");
626
627    // -- Routing --
628    out.push_str("--- Proof 1: Cost-Model Routing ---\n");
629    out.push_str(&format!(
630        "  Circuits tested: {}\n",
631        report.routing.num_circuits
632    ));
633    out.push_str(&format!(
634        "  Planner win rate vs naive: {:.1}%\n",
635        report.routing.planner_win_rate_vs_naive()
636    ));
637    out.push_str(&format!(
638        "  Median speedup vs naive:  {:.2}x\n",
639        report.routing.median_speedup_vs_naive()
640    ));
641    let mut heuristic_speedups: Vec<f64> = report
642        .routing
643        .results
644        .iter()
645        .map(|r| r.speedup_vs_heuristic)
646        .collect();
647    heuristic_speedups.sort_by(|a, b| a.partial_cmp(b).unwrap());
648    let median_h = if heuristic_speedups.is_empty() {
649        1.0
650    } else {
651        heuristic_speedups[heuristic_speedups.len() / 2]
652    };
653    out.push_str(&format!(
654        "  Median speedup vs heuristic: {:.2}x\n\n",
655        median_h
656    ));
657
658    // -- Entanglement --
659    out.push_str("--- Proof 2: Entanglement Budgeting ---\n");
660    let eb = &report.entanglement;
661    out.push_str(&format!("  Circuits tested: {}\n", eb.circuits_tested));
662    out.push_str(&format!("  Total segments:  {}\n", eb.segments_total));
663    out.push_str(&format!(
664        "  Within budget:   {} ({:.1}%)\n",
665        eb.segments_within_budget,
666        if eb.segments_total > 0 {
667            eb.segments_within_budget as f64 / eb.segments_total as f64 * 100.0
668        } else {
669            0.0
670        }
671    ));
672    out.push_str(&format!(
673        "  Max violation:   {:.2}%\n",
674        eb.max_violation * 100.0
675    ));
676    out.push_str(&format!(
677        "  Decomposition overhead: {:.1}%\n\n",
678        eb.decomposition_overhead_pct
679    ));
680
681    // -- Decoder --
682    out.push_str("--- Proof 3: Adaptive Decoder Latency ---\n");
683    out.push_str("  distance | UF avg (ns) | Part avg (ns) | speedup | UF acc  | Part acc\n");
684    out.push_str("  ---------+-------------+---------------+---------+---------+---------\n");
685    for d in &report.decoder {
686        out.push_str(&format!(
687            "  {:>7}  | {:>11.0} | {:>13.0} | {:>6.2}x | {:>6.1}% | {:>6.1}%\n",
688            d.distance,
689            d.union_find_avg_ns,
690            d.partitioned_avg_ns,
691            d.speedup,
692            d.union_find_accuracy * 100.0,
693            d.partitioned_accuracy * 100.0,
694        ));
695    }
696    out.push('\n');
697
698    // -- Certification --
699    out.push_str("--- Proof 4: Cross-Backend Certification ---\n");
700    let c = &report.certification;
701    out.push_str(&format!("  Circuits tested:      {}\n", c.circuits_tested));
702    out.push_str(&format!("  Certified:            {}\n", c.certified));
703    out.push_str(&format!(
704        "  Certification rate:   {:.1}%\n",
705        c.certification_rate * 100.0
706    ));
707    out.push_str(&format!("  Max TVD observed:     {:.6}\n", c.max_tvd));
708    out.push_str(&format!("  Avg TVD:              {:.6}\n", c.avg_tvd));
709    out.push_str(&format!("  TVD bound:            {:.6}\n\n", c.tvd_bound));
710
711    // -- Summary --
712    out.push_str(&format!(
713        "Total benchmark time: {} ms\n",
714        report.total_time_ms
715    ));
716
717    out
718}
719
720// ---------------------------------------------------------------------------
721// Tests
722// ---------------------------------------------------------------------------
723
724#[cfg(test)]
725mod tests {
726    use super::*;
727
728    #[test]
729    fn test_routing_benchmark_runs() {
730        let bench = run_routing_benchmark(42, 50);
731        assert_eq!(bench.num_circuits, 50);
732        assert_eq!(bench.results.len(), 50);
733        assert!(bench.planner_win_rate_vs_naive() > 0.0);
734    }
735
736    #[test]
737    fn test_entanglement_benchmark_runs() {
738        let bench = run_entanglement_benchmark(42, 20);
739        assert_eq!(bench.circuits_tested, 20);
740        assert!(bench.segments_total > 0);
741    }
742
743    #[test]
744    fn test_decoder_benchmark_runs() {
745        let results = run_decoder_benchmark(42, &[3, 5, 7], 10);
746        assert_eq!(results.len(), 3);
747        for r in &results {
748            assert!(r.union_find_avg_ns >= 0.0);
749            assert!(r.partitioned_avg_ns >= 0.0);
750        }
751    }
752
753    #[test]
754    fn test_certification_benchmark_runs() {
755        let bench = run_certification_benchmark(42, 10, 100);
756        assert!(bench.circuits_tested > 0);
757        assert!(bench.certification_rate >= 0.0);
758        assert!(bench.certification_rate <= 1.0);
759    }
760
761    #[test]
762    fn test_format_report_nonempty() {
763        let report = FullBenchmarkReport {
764            routing: run_routing_benchmark(0, 10),
765            entanglement: run_entanglement_benchmark(0, 5),
766            decoder: run_decoder_benchmark(0, &[3, 5], 5),
767            certification: run_certification_benchmark(0, 5, 50),
768            total_time_ms: 42,
769        };
770        let text = format_report(&report);
771        assert!(text.contains("Proof 1"));
772        assert!(text.contains("Proof 2"));
773        assert!(text.contains("Proof 3"));
774        assert!(text.contains("Proof 4"));
775        assert!(text.contains("Total benchmark time"));
776    }
777
778    #[test]
779    fn test_routing_speedup_for_clifford() {
780        // Pure Clifford circuit: planner should choose Stabilizer,
781        // which is faster than naive StateVector.
782        let mut circ = QuantumCircuit::new(50);
783        for q in 0..50 {
784            circ.h(q);
785        }
786        for q in 0..49 {
787            circ.cnot(q, q + 1);
788        }
789        let plan = plan_execution(&circ, &PlannerConfig::default());
790        assert_eq!(plan.backend, BackendType::Stabilizer);
791        let planner_ns = predicted_runtime_ns(&circ, plan.backend);
792        let naive_ns = predicted_runtime_ns(&circ, BackendType::StateVector);
793        assert!(
794            planner_ns < naive_ns,
795            "Stabilizer should be faster than SV for 50-qubit Clifford"
796        );
797    }
798}