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    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!("\n=== {size_name} Circuit Tests (up to {max_qubits} qubits) ===");
270
271    // Only proceed if the template parameter is big enough
272    if N < max_qubits {
273        println!("Cannot benchmark this size - const generic N is too small");
274        return;
275    }
276
277    // Benchmark increasing qubit counts
278    for qubits in [
279        max_qubits / 4,
280        max_qubits / 2,
281        (3 * max_qubits) / 4,
282        max_qubits,
283    ] {
284        let num_gates = qubits * gates_per_qubit;
285        println!("\nCircuit with {qubits} qubits and {num_gates} gates:");
286
287        // Generate the circuit
288        let circuit = generate_benchmark_circuit::<N>(num_gates, two_qubit_ratio);
289
290        // Run all benchmarks
291        let results = compare_simulators(&circuit);
292
293        // Print results
294        for result in &results {
295            println!("{}\n", result.format());
296        }
297    }
298}
299
300// Special benchmark for very large circuits
301fn benchmark_large_circuit<const N: usize>(
302    size_name: &str,
303    max_qubits: usize,
304    gates_per_qubit: usize,
305    two_qubit_ratio: f64,
306) {
307    println!("\n=== {size_name} Circuit Tests ({max_qubits} qubits) ===");
308
309    // Only proceed if the template parameter is big enough
310    if N < max_qubits {
311        println!("Cannot benchmark this size - const generic N is too small");
312        return;
313    }
314
315    let num_gates = max_qubits * gates_per_qubit;
316    println!("\nCircuit with {max_qubits} qubits and {num_gates} gates:");
317
318    // Generate the circuit
319    let circuit = generate_benchmark_circuit::<N>(num_gates, two_qubit_ratio);
320
321    // Only run with chunked and full optimized simulators
322    let mut results = Vec::new();
323
324    // Run chunked optimized simulator
325    let chunked_sim = OptimizedSimulatorChunked::new();
326    if let Ok(result) = run_benchmark("Chunked Optimized", &chunked_sim, &circuit, None) {
327        results.push(result);
328    }
329
330    // Run full optimized simulator
331    let optimized_sim = OptimizedSimulator::new();
332    if let Ok(result) = run_benchmark(
333        "Full Optimized",
334        &optimized_sim,
335        &circuit,
336        Some("Auto-selection".to_string()),
337    ) {
338        results.push(result);
339    }
340
341    // Print results
342    for result in &results {
343        println!("{}\n", result.format());
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350
351    #[test]
352    #[ignore] // Run only when explicitly requested, not during regular testing
353    fn benchmark_small_circuit() {
354        const QUBITS: usize = 16;
355        let circuit = generate_benchmark_circuit::<QUBITS>(100, 0.3);
356
357        let results = compare_simulators(&circuit);
358
359        for result in &results {
360            println!("{}\n", result.format());
361        }
362    }
363
364    #[test]
365    #[ignore] // Run only when explicitly requested, not during regular testing
366    fn benchmark_medium_circuit() {
367        const QUBITS: usize = 20;
368        let circuit = generate_benchmark_circuit::<QUBITS>(50, 0.3);
369
370        let results = compare_simulators(&circuit);
371
372        for result in &results {
373            println!("{}\n", result.format());
374        }
375    }
376
377    #[test]
378    #[ignore] // Run only when explicitly requested, not during regular testing
379    fn benchmark_large_circuit() {
380        const QUBITS: usize = 25;
381        let circuit = generate_benchmark_circuit::<QUBITS>(20, 0.2);
382
383        // For large circuits, only compare the optimized implementations
384        let mut results = Vec::new();
385
386        // Run optimized simulators
387        let chunked_sim = OptimizedSimulatorChunked::new();
388        if let Ok(result) = run_benchmark("Chunked Optimized", &chunked_sim, &circuit, None) {
389            results.push(result);
390        }
391
392        let optimized_sim = OptimizedSimulator::new();
393        if let Ok(result) = run_benchmark(
394            "Full Optimized",
395            &optimized_sim,
396            &circuit,
397            Some("Auto-selection".to_string()),
398        ) {
399            results.push(result);
400        }
401
402        for result in &results {
403            println!("{}\n", result.format());
404        }
405    }
406
407    #[test]
408    #[ignore] // Run only when explicitly requested, not during regular testing
409    fn run_full_benchmark_suite() {
410        run_benchmark_suite();
411    }
412}