quantrs2_sim/
benchmark.rs

1//! Benchmarking utilities for quantum simulators
2//!
3//! This module provides utilities for benchmarking different simulator
4//! implementations and comparing their performance.
5
6use quantrs2_circuit::builder::{Circuit, Simulator};
7use quantrs2_core::{error::QuantRS2Result, qubit::QubitId};
8use scirs2_core::random::{Rng, SeedableRng};
9use scirs2_core::random::ChaCha8Rng;
10use std::time::{Duration, Instant};
11
12use crate::optimized_simulator::OptimizedSimulator;
13use crate::optimized_simulator_chunked::OptimizedSimulatorChunked;
14use crate::optimized_simulator_simple::OptimizedSimulatorSimple;
15use crate::statevector::StateVectorSimulator;
16
17/// Benchmark results for a quantum simulator
18#[derive(Debug, Clone)]
19pub struct BenchmarkResult {
20    /// Name of the simulator
21    pub simulator_name: String,
22    /// Number of qubits
23    pub num_qubits: usize,
24    /// Total number of gates
25    pub num_gates: usize,
26    /// Execution time
27    pub duration: Duration,
28    /// Additional notes or context
29    pub notes: Option<String>,
30    /// Peak memory usage (if available)
31    pub peak_memory: Option<usize>,
32}
33
34impl BenchmarkResult {
35    /// Create a new benchmark result
36    pub fn new(
37        simulator_name: &str,
38        num_qubits: usize,
39        num_gates: usize,
40        duration: Duration,
41        notes: Option<String>,
42        peak_memory: Option<usize>,
43    ) -> Self {
44        Self {
45            simulator_name: simulator_name.to_string(),
46            num_qubits,
47            num_gates,
48            duration,
49            notes,
50            peak_memory,
51        }
52    }
53
54    /// Format the result as a string
55    pub fn format(&self) -> String {
56        let duration_ms = self.duration.as_millis();
57        let rate = if duration_ms > 0 {
58            self.num_gates as f64 / (duration_ms as f64 / 1000.0)
59        } else {
60            f64::INFINITY
61        };
62
63        let notes = if let Some(ref notes) = self.notes {
64            format!(" ({})", notes)
65        } else {
66            String::new()
67        };
68
69        let memory_str = if let Some(mem) = self.peak_memory {
70            format!(", Memory: {:.2} MB", mem as f64 / (1024.0 * 1024.0))
71        } else {
72            String::new()
73        };
74
75        format!(
76            "Simulator: {}{}\n  Qubits: {}\n  Gates: {}\n  Time: {} ms\n  Rate: {:.2} gates/s{}",
77            self.simulator_name,
78            notes,
79            self.num_qubits,
80            self.num_gates,
81            duration_ms,
82            rate,
83            memory_str
84        )
85    }
86}
87
88/// Run a benchmark on a simulator with a given circuit
89pub fn run_benchmark<S, const N: usize>(
90    name: &str,
91    simulator: &S,
92    circuit: &Circuit<N>,
93    notes: Option<String>,
94) -> QuantRS2Result<BenchmarkResult>
95where
96    S: Simulator<N>,
97{
98    let num_qubits = N;
99    let num_gates = circuit.gates().len();
100
101    let start = Instant::now();
102    let _result = simulator.run(circuit)?;
103    let duration = start.elapsed();
104
105    Ok(BenchmarkResult::new(
106        name, num_qubits, num_gates, duration, notes, None,
107    ))
108}
109
110/// Compare different simulator implementations on the same circuit
111pub fn compare_simulators<const N: usize>(circuit: &Circuit<N>) -> Vec<BenchmarkResult> {
112    let mut results = Vec::new();
113
114    // For very large circuits, only test optimized implementations
115    if N <= 24 {
116        // Run standard simulator
117        let standard_sim = StateVectorSimulator::new();
118        if let Ok(result) = run_benchmark("Standard Simulator", &standard_sim, circuit, None) {
119            results.push(result);
120        }
121    }
122
123    // Run simple optimized simulator
124    let simple_opt_sim = OptimizedSimulatorSimple::new();
125    if let Ok(result) = run_benchmark("Simple Optimized", &simple_opt_sim, circuit, None) {
126        results.push(result);
127    }
128
129    // Run chunked optimized simulator
130    let chunked_sim = OptimizedSimulatorChunked::new();
131    if let Ok(result) = run_benchmark("Chunked Optimized", &chunked_sim, circuit, None) {
132        results.push(result);
133    }
134
135    // Run full optimized simulator with automatic selection
136    let optimized_sim = OptimizedSimulator::new();
137    if let Ok(result) = run_benchmark(
138        "Full Optimized",
139        &optimized_sim,
140        circuit,
141        Some("Auto-selection".to_string()),
142    ) {
143        results.push(result);
144    }
145
146    // For smaller circuits, also test memory-efficient configuration
147    if N <= 20 {
148        let memory_efficient_sim = OptimizedSimulator::memory_efficient();
149        if let Ok(result) = run_benchmark(
150            "Memory Efficient",
151            &memory_efficient_sim,
152            circuit,
153            Some("Low memory".to_string()),
154        ) {
155            results.push(result);
156        }
157    }
158
159    results
160}
161
162/// Generate a random quantum circuit for benchmarking
163pub fn generate_benchmark_circuit<const N: usize>(
164    num_gates: usize,
165    two_qubit_ratio: f64,
166) -> Circuit<N> {
167    let mut circuit = Circuit::new();
168    let mut rng = ChaCha8Rng::seed_from_u64(42); // Use fixed seed for reproducibility
169
170    for _ in 0..num_gates {
171        // Decide if this is a single-qubit or two-qubit gate
172        let is_two_qubit = rng.random::<f64>() < two_qubit_ratio;
173
174        if is_two_qubit && N > 1 {
175            // Select two different qubits
176            let qubit1 = QubitId::new(rng.random_range(0..N as u32));
177            let mut qubit2 = QubitId::new(rng.random_range(0..N as u32));
178            while qubit2 == qubit1 {
179                qubit2 = QubitId::new(rng.random_range(0..N as u32));
180            }
181
182            // Choose a two-qubit gate
183            let gate_type = rng.random_range(0..3);
184            match gate_type {
185                0 => {
186                    let _ = circuit.cnot(qubit1, qubit2);
187                }
188                1 => {
189                    let _ = circuit.cz(qubit1, qubit2);
190                }
191                _ => {
192                    let _ = circuit.swap(qubit1, qubit2);
193                }
194            }
195        } else {
196            // Select a qubit
197            let qubit = QubitId::new(rng.random_range(0..N as u32));
198
199            // Choose a single-qubit gate
200            let gate_type = rng.random_range(0..7);
201            match gate_type {
202                0 => {
203                    let _ = circuit.h(qubit);
204                }
205                1 => {
206                    let _ = circuit.x(qubit);
207                }
208                2 => {
209                    let _ = circuit.y(qubit);
210                }
211                3 => {
212                    let _ = circuit.z(qubit);
213                }
214                4 => {
215                    let _ = circuit.s(qubit);
216                }
217                5 => {
218                    let _ = circuit.t(qubit);
219                }
220                _ => {
221                    // Random rotation
222                    let angle = rng.random_range(0.0..std::f64::consts::TAU);
223                    let rotation_type = rng.random_range(0..3);
224                    match rotation_type {
225                        0 => {
226                            let _ = circuit.rx(qubit, angle);
227                        }
228                        1 => {
229                            let _ = circuit.ry(qubit, angle);
230                        }
231                        _ => {
232                            let _ = circuit.rz(qubit, angle);
233                        }
234                    }
235                }
236            }
237        }
238    }
239
240    circuit
241}
242
243/// Run a comprehensive benchmark suite for circuits of different sizes
244pub fn run_benchmark_suite() {
245    println!("=== Quantrs Simulator Benchmark Suite ===");
246    println!("Testing various circuit sizes with different simulator implementations");
247    println!();
248
249    // Test small circuits (up to 16 qubits)
250    benchmark_circuit_size::<16>("Small", 16, 100, 0.3);
251
252    // Test medium circuits (up to 20 qubits)
253    benchmark_circuit_size::<20>("Medium", 20, 50, 0.3);
254
255    // Test large circuits (up to 25 qubits)
256    benchmark_circuit_size::<25>("Large", 25, 20, 0.2);
257
258    // Test very large circuits for memory-efficient implementation
259    benchmark_large_circuit::<28>("Very Large", 28, 10, 0.1);
260}
261
262// Helper function to benchmark a specific circuit size
263fn benchmark_circuit_size<const N: usize>(
264    size_name: &str,
265    max_qubits: usize,
266    gates_per_qubit: usize,
267    two_qubit_ratio: f64,
268) {
269    println!(
270        "\n=== {} Circuit Tests (up to {} qubits) ===",
271        size_name, max_qubits
272    );
273
274    // Only proceed if the template parameter is big enough
275    if N < max_qubits {
276        println!("Cannot benchmark this size - const generic N is too small");
277        return;
278    }
279
280    // Benchmark increasing qubit counts
281    for qubits in [
282        max_qubits / 4,
283        max_qubits / 2,
284        (3 * max_qubits) / 4,
285        max_qubits,
286    ] {
287        let num_gates = qubits * gates_per_qubit;
288        println!("\nCircuit with {} qubits and {} gates:", qubits, num_gates);
289
290        // Generate the circuit
291        let circuit = generate_benchmark_circuit::<N>(num_gates, two_qubit_ratio);
292
293        // Run all benchmarks
294        let results = compare_simulators(&circuit);
295
296        // Print results
297        for result in &results {
298            println!("{}\n", result.format());
299        }
300    }
301}
302
303// Special benchmark for very large circuits
304fn benchmark_large_circuit<const N: usize>(
305    size_name: &str,
306    max_qubits: usize,
307    gates_per_qubit: usize,
308    two_qubit_ratio: f64,
309) {
310    println!(
311        "\n=== {} Circuit Tests ({} qubits) ===",
312        size_name, max_qubits
313    );
314
315    // Only proceed if the template parameter is big enough
316    if N < max_qubits {
317        println!("Cannot benchmark this size - const generic N is too small");
318        return;
319    }
320
321    let num_gates = max_qubits * gates_per_qubit;
322    println!(
323        "\nCircuit with {} qubits and {} gates:",
324        max_qubits, num_gates
325    );
326
327    // Generate the circuit
328    let circuit = generate_benchmark_circuit::<N>(num_gates, two_qubit_ratio);
329
330    // Only run with chunked and full optimized simulators
331    let mut results = Vec::new();
332
333    // Run chunked optimized simulator
334    let chunked_sim = OptimizedSimulatorChunked::new();
335    if let Ok(result) = run_benchmark("Chunked Optimized", &chunked_sim, &circuit, None) {
336        results.push(result);
337    }
338
339    // Run full optimized simulator
340    let optimized_sim = OptimizedSimulator::new();
341    if let Ok(result) = run_benchmark(
342        "Full Optimized",
343        &optimized_sim,
344        &circuit,
345        Some("Auto-selection".to_string()),
346    ) {
347        results.push(result);
348    }
349
350    // Print results
351    for result in &results {
352        println!("{}\n", result.format());
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359
360    #[test]
361    #[ignore] // Run only when explicitly requested, not during regular testing
362    fn benchmark_small_circuit() {
363        const QUBITS: usize = 16;
364        let circuit = generate_benchmark_circuit::<QUBITS>(100, 0.3);
365
366        let results = compare_simulators(&circuit);
367
368        for result in &results {
369            println!("{}\n", result.format());
370        }
371    }
372
373    #[test]
374    #[ignore] // Run only when explicitly requested, not during regular testing
375    fn benchmark_medium_circuit() {
376        const QUBITS: usize = 20;
377        let circuit = generate_benchmark_circuit::<QUBITS>(50, 0.3);
378
379        let results = compare_simulators(&circuit);
380
381        for result in &results {
382            println!("{}\n", result.format());
383        }
384    }
385
386    #[test]
387    #[ignore] // Run only when explicitly requested, not during regular testing
388    fn benchmark_large_circuit() {
389        const QUBITS: usize = 25;
390        let circuit = generate_benchmark_circuit::<QUBITS>(20, 0.2);
391
392        // For large circuits, only compare the optimized implementations
393        let mut results = Vec::new();
394
395        // Run optimized simulators
396        let chunked_sim = OptimizedSimulatorChunked::new();
397        if let Ok(result) = run_benchmark("Chunked Optimized", &chunked_sim, &circuit, None) {
398            results.push(result);
399        }
400
401        let optimized_sim = OptimizedSimulator::new();
402        if let Ok(result) = run_benchmark(
403            "Full Optimized",
404            &optimized_sim,
405            &circuit,
406            Some("Auto-selection".to_string()),
407        ) {
408            results.push(result);
409        }
410
411        for result in &results {
412            println!("{}\n", result.format());
413        }
414    }
415
416    #[test]
417    #[ignore] // Run only when explicitly requested, not during regular testing
418    fn run_full_benchmark_suite() {
419        run_benchmark_suite();
420    }
421}