//! Scaling benchmarks for ruQu Coherence Gate. //! //! Measures how performance scales with: //! - Code distance (5, 9, 13, 17, 21) //! - Qubit count (50, 100, 500, 1000) //! - Tile count (10, 50, 100, 255) //! - Graph density //! //! Run with: `cargo bench -p ruqu --bench scaling_bench` use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use std::hint::black_box as hint_black_box; use ruqu::filters::{FilterConfig, FilterPipeline, SystemState}; use ruqu::syndrome::{DetectorBitmap, SyndromeBuffer, SyndromeRound}; use ruqu::tile::{GateThresholds, PatchGraph, SyndromeDelta, TileReport, TileZero, WorkerTile}; // ============================================================================ // HELPER FUNCTIONS // ============================================================================ /// Calculate approximate detector count for a surface code distance fn detectors_for_distance(distance: usize) -> usize { // For surface code, detector count is roughly d^2 distance * distance } /// Calculate approximate qubit count for a surface code distance fn qubits_for_distance(distance: usize) -> usize { // For surface code, data qubits = 2*d^2 - 2*d + 1 2 * distance * distance - 2 * distance + 1 } /// Create a worker tile sized for a given qubit count fn create_scaled_worker_tile(tile_id: u8, qubit_count: usize) -> WorkerTile { let mut tile = WorkerTile::new(tile_id); let vertices = (qubit_count / 4).min(255) as u16; // Tile handles a fraction of qubits let edges_per_vertex = 4; // Surface code connectivity for i in 0..vertices { tile.patch_graph.ensure_vertex(i); } let mut edges_added = 0u16; let max_edges = (vertices as usize * edges_per_vertex / 2).min(1000) as u16; 'outer: for i in 0..vertices.saturating_sub(1) { // Lattice-like connectivity let neighbors = [i + 1, i.wrapping_add(vertices / 10)]; for &neighbor in &neighbors { if neighbor < vertices && neighbor != i && edges_added < max_edges { if tile.patch_graph.add_edge(i, neighbor, 1000).is_some() { edges_added += 1; } } if edges_added >= max_edges { break 'outer; } } } tile.patch_graph.recompute_components(); tile } /// Create a filter pipeline sized for a given qubit count fn create_scaled_filter_pipeline(qubit_count: usize) -> FilterPipeline { let config = FilterConfig::default(); let mut pipeline = FilterPipeline::new(config); let vertices = qubit_count.min(500) as u64; // Add graph structure proportional to qubit count for i in 0..vertices.saturating_sub(1) { let _ = pipeline.structural_mut().insert_edge(i, i + 1, 1.0); if i % 10 == 0 && i + 10 < vertices { let _ = pipeline.structural_mut().insert_edge(i, i + 10, 0.5); } } pipeline.structural_mut().build(); // Warm up shift filter let num_regions = (qubit_count / 16).min(64); for region in 0..num_regions { for _ in 0..50 { pipeline.shift_mut().update(region, 0.5); } } // Warm up evidence filter for _ in 0..20 { pipeline.evidence_mut().update(1.5); } pipeline } // ============================================================================ // LATENCY VS CODE DISTANCE // ============================================================================ /// Benchmark latency scaling with code distance fn bench_latency_vs_distance(c: &mut Criterion) { let mut group = c.benchmark_group("latency_vs_distance"); group.sample_size(100); let distances = [5, 9, 13, 17, 21]; for distance in distances.iter() { let qubit_count = qubits_for_distance(*distance); let detector_count = detectors_for_distance(*distance); // Worker tile tick latency group.bench_with_input( BenchmarkId::new("worker_tick", format!("d{}", distance)), &qubit_count, |b, &qubits| { let mut tile = create_scaled_worker_tile(1, qubits); let delta = SyndromeDelta::new(0, 1, 100); b.iter(|| { let report = tile.tick(black_box(&delta)); black_box(report) }); }, ); // Filter pipeline evaluation latency group.bench_with_input( BenchmarkId::new("filter_pipeline", format!("d{}", distance)), &qubit_count, |b, &qubits| { let pipeline = create_scaled_filter_pipeline(qubits); let state = SystemState::new(qubits); b.iter(|| { let result = pipeline.evaluate(black_box(&state)); black_box(result) }); }, ); // Full decision cycle latency group.bench_with_input( BenchmarkId::new("full_decision", format!("d{}", distance)), &qubit_count, |b, &qubits| { let mut tile = create_scaled_worker_tile(1, qubits); let thresholds = GateThresholds::default(); let mut tilezero = TileZero::new(thresholds); b.iter(|| { let delta = SyndromeDelta::new(0, 1, 100); let report = tile.tick(&delta); let reports = vec![report; 10]; let decision = tilezero.merge_reports(reports); black_box(decision) }); }, ); // Syndrome buffer push latency group.bench_with_input( BenchmarkId::new("syndrome_push", format!("d{}", distance)), &detector_count, |b, &detectors| { let mut buffer = SyndromeBuffer::new(1024); let mut round_id = 0u64; b.iter(|| { let mut bitmap = DetectorBitmap::new(detectors.min(1024)); for i in 0..detectors.min(1024) / 10 { bitmap.set(i * 10, true); } let round = SyndromeRound::new(round_id, round_id, round_id * 1000, bitmap, 0); buffer.push(round); round_id += 1; black_box(buffer.len()) }); }, ); } group.finish(); } // ============================================================================ // LATENCY VS QUBIT COUNT // ============================================================================ /// Benchmark latency scaling with qubit count fn bench_latency_vs_qubit_count(c: &mut Criterion) { let mut group = c.benchmark_group("latency_vs_qubits"); group.sample_size(100); let qubit_counts = [50, 100, 500, 1000]; for qubit_count in qubit_counts.iter() { // Worker tile tick latency group.bench_with_input( BenchmarkId::new("worker_tick", format!("q{}", qubit_count)), qubit_count, |b, &qubits| { let mut tile = create_scaled_worker_tile(1, qubits); let delta = SyndromeDelta::new(0, 1, 100); b.iter(|| { let report = tile.tick(black_box(&delta)); black_box(report) }); }, ); // Filter pipeline evaluation group.bench_with_input( BenchmarkId::new("filter_pipeline", format!("q{}", qubit_count)), qubit_count, |b, &qubits| { let pipeline = create_scaled_filter_pipeline(qubits); let state = SystemState::new(qubits); b.iter(|| { let result = pipeline.evaluate(black_box(&state)); black_box(result) }); }, ); // Patch graph operations group.bench_with_input( BenchmarkId::new("patch_graph_estimate_cut", format!("q{}", qubit_count)), qubit_count, |b, &qubits| { let tile = create_scaled_worker_tile(1, qubits); b.iter(|| { let cut = tile.patch_graph.estimate_local_cut(); black_box(cut) }); }, ); // Component recomputation group.bench_with_input( BenchmarkId::new("recompute_components", format!("q{}", qubit_count)), qubit_count, |b, &qubits| { let mut tile = create_scaled_worker_tile(1, qubits); b.iter(|| { tile.patch_graph.status |= PatchGraph::STATUS_DIRTY; let count = tile.patch_graph.recompute_components(); black_box(count) }); }, ); } group.finish(); } // ============================================================================ // LATENCY VS TILE COUNT // ============================================================================ /// Benchmark latency scaling with tile count (TileZero merge) fn bench_latency_vs_tile_count(c: &mut Criterion) { let mut group = c.benchmark_group("latency_vs_tiles"); group.sample_size(100); let tile_counts = [10, 50, 100, 150, 200, 255]; for tile_count in tile_counts.iter() { // TileZero merge latency group.bench_with_input( BenchmarkId::new("tilezero_merge", format!("t{}", tile_count)), tile_count, |b, &count| { let thresholds = GateThresholds::default(); let mut tilezero = TileZero::new(thresholds); let reports: Vec = (1..=count) .map(|i| { let mut report = TileReport::new(i as u8); report.local_cut = 10.0 + (i as f64 * 0.1); report.shift_score = 0.1; report.e_value = 200.0; report.num_vertices = 100; report.num_edges = 200; report }) .collect(); b.iter(|| { let decision = tilezero.merge_reports(black_box(reports.clone())); black_box(decision) }); }, ); // Full decision cycle with scaled tiles group.bench_with_input( BenchmarkId::new("full_decision", format!("t{}", tile_count)), tile_count, |b, &count| { let mut tile = create_scaled_worker_tile(1, 100); let thresholds = GateThresholds::default(); let mut tilezero = TileZero::new(thresholds); b.iter(|| { let delta = SyndromeDelta::new(0, 1, 100); let report = tile.tick(&delta); let reports = vec![report; count]; let decision = tilezero.merge_reports(reports); black_box(decision) }); }, ); } group.finish(); } // ============================================================================ // THROUGHPUT VS SYSTEM SIZE // ============================================================================ /// Benchmark throughput scaling with system size fn bench_throughput_vs_size(c: &mut Criterion) { let mut group = c.benchmark_group("throughput_vs_size"); let qubit_counts = [50, 100, 500, 1000]; for qubit_count in qubit_counts.iter() { // Syndrome ingestion throughput group.throughput(Throughput::Elements(1000)); group.bench_with_input( BenchmarkId::new("syndrome_ingestion", format!("q{}", qubit_count)), qubit_count, |b, &qubits| { let mut buffer = SyndromeBuffer::new(4096); let detector_count = (qubits / 2).min(1024); let mut round_id = 0u64; b.iter(|| { for _ in 0..1000 { let mut bitmap = DetectorBitmap::new(detector_count); for i in 0..detector_count / 10 { bitmap.set(i * 10, true); } let round = SyndromeRound::new(round_id, round_id, round_id * 1000, bitmap, 0); buffer.push(round); round_id += 1; } black_box(buffer.len()) }); }, ); // Decision throughput group.throughput(Throughput::Elements(100)); group.bench_with_input( BenchmarkId::new("decision_throughput", format!("q{}", qubit_count)), qubit_count, |b, &qubits| { let mut tile = create_scaled_worker_tile(1, qubits); let thresholds = GateThresholds::default(); let mut tilezero = TileZero::new(thresholds); b.iter(|| { for i in 0..100 { let delta = SyndromeDelta::new(0, 1, (i % 256) as u16); let report = tile.tick(&delta); let reports = vec![report; 10]; let decision = tilezero.merge_reports(reports); hint_black_box(decision); } }); }, ); } group.finish(); } // ============================================================================ // GRAPH DENSITY SCALING // ============================================================================ /// Benchmark latency scaling with graph density fn bench_latency_vs_density(c: &mut Criterion) { let mut group = c.benchmark_group("latency_vs_density"); group.sample_size(100); let base_vertices = 100u16; let densities = [ ("sparse", base_vertices / 2), // 0.5 edges per vertex ("linear", base_vertices), // 1 edge per vertex ("lattice", base_vertices * 2), // 2 edges per vertex ("dense", base_vertices * 4), // 4 edges per vertex ("very_dense", base_vertices * 8), // 8 edges per vertex ]; for (name, edge_count) in densities.iter() { // Worker tile tick group.bench_with_input( BenchmarkId::new("worker_tick", *name), edge_count, |b, &edges| { let mut tile = WorkerTile::new(1); for i in 0..base_vertices { tile.patch_graph.ensure_vertex(i); } let mut added = 0u16; 'outer: for i in 0..base_vertices { for j in (i + 1)..base_vertices.min(i + 10) { if added >= edges { break 'outer; } if tile.patch_graph.add_edge(i, j, 1000).is_some() { added += 1; } } } tile.patch_graph.recompute_components(); let delta = SyndromeDelta::new(0, 1, 100); b.iter(|| { let report = tile.tick(black_box(&delta)); black_box(report) }); }, ); // Local cut estimation group.bench_with_input( BenchmarkId::new("estimate_local_cut", *name), edge_count, |b, &edges| { let mut graph = PatchGraph::new(); for i in 0..base_vertices { graph.ensure_vertex(i); } let mut added = 0u16; 'outer: for i in 0..base_vertices { for j in (i + 1)..base_vertices.min(i + 10) { if added >= edges { break 'outer; } if graph.add_edge(i, j, 1000).is_some() { added += 1; } } } graph.recompute_components(); b.iter(|| { let cut = graph.estimate_local_cut(); black_box(cut) }); }, ); // Component recomputation group.bench_with_input( BenchmarkId::new("recompute_components", *name), edge_count, |b, &edges| { let mut graph = PatchGraph::new(); for i in 0..base_vertices { graph.ensure_vertex(i); } let mut added = 0u16; 'outer: for i in 0..base_vertices { for j in (i + 1)..base_vertices.min(i + 10) { if added >= edges { break 'outer; } if graph.add_edge(i, j, 1000).is_some() { added += 1; } } } b.iter(|| { graph.status |= PatchGraph::STATUS_DIRTY; let count = graph.recompute_components(); black_box(count) }); }, ); } group.finish(); } // ============================================================================ // MEMORY PRESSURE SCALING // ============================================================================ /// Benchmark under memory pressure (large buffers) fn bench_memory_pressure(c: &mut Criterion) { let mut group = c.benchmark_group("memory_pressure"); group.sample_size(50); let buffer_sizes = [1024, 4096, 16384, 65536]; for buffer_size in buffer_sizes.iter() { // Syndrome buffer under pressure group.throughput(Throughput::Elements(1000)); group.bench_with_input( BenchmarkId::new("syndrome_buffer", format!("cap{}", buffer_size)), buffer_size, |b, &size| { let mut buffer = SyndromeBuffer::new(size); // Pre-fill to capacity for i in 0..(size as u64) { let round = SyndromeRound::new(i, i, i * 1000, DetectorBitmap::new(64), 0); buffer.push(round); } let mut round_id = size as u64; b.iter(|| { for _ in 0..1000 { let round = SyndromeRound::new( round_id, round_id, round_id * 1000, DetectorBitmap::new(64), 0, ); buffer.push(round); round_id += 1; } black_box(buffer.len()) }); }, ); // Window extraction under pressure group.bench_with_input( BenchmarkId::new("window_extraction", format!("cap{}", buffer_size)), buffer_size, |b, &size| { let mut buffer = SyndromeBuffer::new(size); for i in 0..(size as u64) { let round = SyndromeRound::new(i, i, i * 1000, DetectorBitmap::new(64), 0); buffer.push(round); } let window_size = (size / 10).max(10); b.iter(|| { let window = buffer.window(window_size); black_box(window) }); }, ); } group.finish(); } // ============================================================================ // CRITERION GROUPS // ============================================================================ criterion_group!( scaling_benches, bench_latency_vs_distance, bench_latency_vs_qubit_count, bench_latency_vs_tile_count, bench_throughput_vs_size, bench_latency_vs_density, bench_memory_pressure, ); criterion_main!(scaling_benches);