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