1use 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
24pub 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
42pub struct RoutingBenchmark {
44 pub num_circuits: usize,
45 pub results: Vec<RoutingResult>,
46}
47
48impl RoutingBenchmark {
49 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 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
74fn 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 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
117fn naive_select(_circuit: &QuantumCircuit) -> BackendType {
119 BackendType::StateVector
120}
121
122fn 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
132pub 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
195fn 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
284pub 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
297pub 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 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 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 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 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 if rng.gen_bool(0.3) && half > 0 && half < n {
368 circ.cnot(half - 1, half);
369 }
370 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
378pub 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
392pub 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 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
480pub 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
494pub 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 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 let stab_counts = run_stabilizer_shots(&circuit, shots, shot_seed);
526
527 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 for q in 0..n {
578 circ.measure(q);
579 }
580 circ
581}
582
583pub 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
596pub 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
621pub 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 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 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 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 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 out.push_str(&format!(
713 "Total benchmark time: {} ms\n",
714 report.total_time_ms
715 ));
716
717 out
718}
719
720#[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 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}