ruqu-core 2.0.5

High-performance quantum circuit simulator in pure Rust - state-vector simulation with SIMD acceleration, noise models, and multi-threading
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
//! Unified simulation backend trait and automatic backend selection.
//!
//! ruqu-core supports multiple simulation backends, each optimal for
//! different circuit structures:
//!
//! | Backend | Qubits | Best for |
//! |---------|--------|----------|
//! | StateVector | up to ~32 | General circuits, exact simulation |
//! | Stabilizer | millions | Clifford circuits + measurement |
//! | TensorNetwork | hundreds-thousands | Low-depth, local connectivity |

use crate::circuit::QuantumCircuit;
use crate::gate::Gate;

// ---------------------------------------------------------------------------
// Backend type enum
// ---------------------------------------------------------------------------

/// Which backend to use for simulation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackendType {
    /// Dense state-vector (exact, up to ~32 qubits).
    StateVector,
    /// Aaronson-Gottesman stabilizer tableau (Clifford-only, millions of qubits).
    Stabilizer,
    /// Matrix Product State tensor network (bounded entanglement, hundreds+).
    TensorNetwork,
    /// Clifford+T stabilizer rank decomposition (moderate T-count, many qubits).
    CliffordT,
    /// Automatically select the best backend based on circuit analysis.
    Auto,
}

// ---------------------------------------------------------------------------
// Circuit analysis result
// ---------------------------------------------------------------------------

/// Result of circuit analysis, used for backend selection.
///
/// Produced by [`analyze_circuit`] and contains both raw statistics about the
/// circuit (gate counts, depth, connectivity) and a recommended backend with
/// a confidence score and human-readable explanation.
#[derive(Debug, Clone)]
pub struct CircuitAnalysis {
    /// Number of qubits in the circuit.
    pub num_qubits: u32,
    /// Total number of gates.
    pub total_gates: usize,
    /// Number of Clifford gates (H, S, CNOT, CZ, SWAP, X, Y, Z, Sdg).
    pub clifford_gates: usize,
    /// Number of non-Clifford gates (T, Tdg, Rx, Ry, Rz, Phase, Rzz, Unitary1Q).
    pub non_clifford_gates: usize,
    /// Fraction of unitary gates that are Clifford (0.0 to 1.0).
    pub clifford_fraction: f64,
    /// Number of measurement gates.
    pub measurement_gates: usize,
    /// Circuit depth (longest qubit timeline).
    pub depth: u32,
    /// Maximum qubit distance in any two-qubit gate.
    pub max_connectivity: u32,
    /// Whether all two-qubit gates are between adjacent qubits.
    pub is_nearest_neighbor: bool,
    /// Recommended backend based on the analysis heuristics.
    pub recommended_backend: BackendType,
    /// Confidence in the recommendation (0.0 to 1.0).
    pub confidence: f64,
    /// Human-readable explanation of the recommendation.
    pub explanation: String,
}

// ---------------------------------------------------------------------------
// Public analysis entry point
// ---------------------------------------------------------------------------

/// Analyze a quantum circuit to determine the optimal simulation backend.
///
/// Walks the gate list once to collect statistics, then applies a series of
/// heuristic rules to recommend a [`BackendType`]. The returned
/// [`CircuitAnalysis`] contains both the raw numbers and the recommendation.
///
/// # Example
///
/// ```
/// use ruqu_core::circuit::QuantumCircuit;
/// use ruqu_core::backend::{analyze_circuit, BackendType};
///
/// // A small circuit with a non-Clifford gate routes to StateVector.
/// let mut circ = QuantumCircuit::new(3);
/// circ.h(0).t(1).cnot(0, 1);
/// let analysis = analyze_circuit(&circ);
/// assert_eq!(analysis.recommended_backend, BackendType::StateVector);
/// ```
pub fn analyze_circuit(circuit: &QuantumCircuit) -> CircuitAnalysis {
    let num_qubits = circuit.num_qubits();
    let gates = circuit.gates();
    let total_gates = gates.len();

    let mut clifford_gates = 0usize;
    let mut non_clifford_gates = 0usize;
    let mut measurement_gates = 0usize;
    let mut max_connectivity: u32 = 0;
    let mut is_nearest_neighbor = true;

    for gate in gates {
        match gate {
            // Clifford gates
            Gate::H(_)
            | Gate::X(_)
            | Gate::Y(_)
            | Gate::Z(_)
            | Gate::S(_)
            | Gate::Sdg(_)
            | Gate::CNOT(_, _)
            | Gate::CZ(_, _)
            | Gate::SWAP(_, _) => {
                clifford_gates += 1;
            }
            // Non-Clifford gates
            Gate::T(_)
            | Gate::Tdg(_)
            | Gate::Rx(_, _)
            | Gate::Ry(_, _)
            | Gate::Rz(_, _)
            | Gate::Phase(_, _)
            | Gate::Rzz(_, _, _)
            | Gate::Unitary1Q(_, _) => {
                non_clifford_gates += 1;
            }
            Gate::Measure(_) => {
                measurement_gates += 1;
            }
            Gate::Reset(_) | Gate::Barrier => {}
        }

        // Check connectivity for two-qubit gates.
        let qubits = gate.qubits();
        if qubits.len() == 2 {
            let dist = if qubits[0] > qubits[1] {
                qubits[0] - qubits[1]
            } else {
                qubits[1] - qubits[0]
            };
            if dist > max_connectivity {
                max_connectivity = dist;
            }
            if dist > 1 {
                is_nearest_neighbor = false;
            }
        }
    }

    let unitary_gates = clifford_gates + non_clifford_gates;
    let clifford_fraction = if unitary_gates > 0 {
        clifford_gates as f64 / unitary_gates as f64
    } else {
        1.0
    };

    let depth = circuit.depth();

    // Decide which backend fits best.
    let (recommended_backend, confidence, explanation) = select_backend(
        num_qubits,
        clifford_fraction,
        non_clifford_gates,
        depth,
        is_nearest_neighbor,
        max_connectivity,
    );

    CircuitAnalysis {
        num_qubits,
        total_gates,
        clifford_gates,
        non_clifford_gates,
        clifford_fraction,
        measurement_gates,
        depth,
        max_connectivity,
        is_nearest_neighbor,
        recommended_backend,
        confidence,
        explanation,
    }
}

// ---------------------------------------------------------------------------
// Internal selection heuristics
// ---------------------------------------------------------------------------

/// Internal backend selection logic.
///
/// Returns `(backend, confidence, explanation)` based on a priority-ordered
/// set of heuristic rules.
fn select_backend(
    num_qubits: u32,
    clifford_fraction: f64,
    non_clifford_gates: usize,
    depth: u32,
    is_nearest_neighbor: bool,
    max_connectivity: u32,
) -> (BackendType, f64, String) {
    // Rule 1: Pure Clifford circuits -> Stabilizer (any size).
    if clifford_fraction >= 1.0 {
        return (
            BackendType::Stabilizer,
            0.99,
            format!(
                "Pure Clifford circuit: stabilizer backend handles {} qubits in O(n^2) per gate",
                num_qubits
            ),
        );
    }

    // Rule 2: Mostly Clifford with very few non-Clifford gates and too many
    // qubits for state vector -> Stabilizer with approximate decomposition.
    if clifford_fraction >= 0.95 && num_qubits > 32 && non_clifford_gates <= 10 {
        return (
            BackendType::Stabilizer,
            0.85,
            format!(
                "{}% Clifford with only {} non-Clifford gates: \
                 stabilizer backend recommended for {} qubits",
                (clifford_fraction * 100.0) as u32,
                non_clifford_gates,
                num_qubits
            ),
        );
    }

    // Rule 3: Small enough for state vector -> use it (exact, comfortable).
    if num_qubits <= 25 {
        return (
            BackendType::StateVector,
            0.95,
            format!(
                "{} qubits fits comfortably in state vector ({})",
                num_qubits,
                format_memory(num_qubits)
            ),
        );
    }

    // Rule 4: State vector possible but tight on memory.
    if num_qubits <= 32 {
        return (
            BackendType::StateVector,
            0.80,
            format!(
                "{} qubits requires {} for state vector - verify available memory",
                num_qubits,
                format_memory(num_qubits)
            ),
        );
    }

    // Rule 5: Low depth, local connectivity -> tensor network.
    if is_nearest_neighbor && depth < num_qubits * 2 {
        return (
            BackendType::TensorNetwork,
            0.85,
            format!(
                "Nearest-neighbor connectivity with depth {} on {} qubits: \
                 tensor network efficient",
                depth, num_qubits
            ),
        );
    }

    // Rule 6: General large circuit -> tensor network as best approximation.
    if num_qubits > 32 {
        let conf = if is_nearest_neighbor { 0.75 } else { 0.55 };
        return (
            BackendType::TensorNetwork,
            conf,
            format!(
                "{} qubits exceeds state vector capacity. \
                 Tensor network with connectivity {} - results are approximate",
                num_qubits, max_connectivity
            ),
        );
    }

    // Fallback: exact state vector simulation.
    (
        BackendType::StateVector,
        0.70,
        "Default to exact state vector simulation".into(),
    )
}

// ---------------------------------------------------------------------------
// Memory formatting helper
// ---------------------------------------------------------------------------

/// Format the state-vector memory requirement for a given qubit count.
///
/// Each amplitude is a `Complex` (16 bytes), and there are `2^n` of them.
fn format_memory(num_qubits: u32) -> String {
    // Use u128 to avoid overflow for up to 127 qubits.
    let bytes = (1u128 << num_qubits) * 16;
    if bytes >= 1 << 40 {
        format!("{:.1} TiB", bytes as f64 / (1u128 << 40) as f64)
    } else if bytes >= 1 << 30 {
        format!("{:.1} GiB", bytes as f64 / (1u128 << 30) as f64)
    } else if bytes >= 1 << 20 {
        format!("{:.1} MiB", bytes as f64 / (1u128 << 20) as f64)
    } else {
        format!("{} bytes", bytes)
    }
}

// ---------------------------------------------------------------------------
// Scaling information
// ---------------------------------------------------------------------------

/// Scaling characteristics for a single simulation backend.
#[derive(Debug, Clone)]
pub struct ScalingInfo {
    /// The backend this info describes.
    pub backend: BackendType,
    /// Maximum qubits for exact (zero-error) simulation.
    pub max_qubits_exact: u32,
    /// Maximum qubits for approximate simulation with truncation.
    pub max_qubits_approximate: u32,
    /// Time complexity in big-O notation.
    pub time_complexity: String,
    /// Space complexity in big-O notation.
    pub space_complexity: String,
}

/// Get scaling information for all supported backends.
///
/// Returns a `Vec` with one [`ScalingInfo`] per backend (StateVector,
/// Stabilizer, TensorNetwork, CliffordT) in that order.
pub fn scaling_report() -> Vec<ScalingInfo> {
    vec![
        ScalingInfo {
            backend: BackendType::StateVector,
            max_qubits_exact: 32,
            max_qubits_approximate: 36,
            time_complexity: "O(2^n * gates)".into(),
            space_complexity: "O(2^n)".into(),
        },
        ScalingInfo {
            backend: BackendType::Stabilizer,
            max_qubits_exact: 10_000_000,
            max_qubits_approximate: 10_000_000,
            time_complexity: "O(n^2 * gates) for Clifford".into(),
            space_complexity: "O(n^2)".into(),
        },
        ScalingInfo {
            backend: BackendType::TensorNetwork,
            max_qubits_exact: 100,
            max_qubits_approximate: 10_000,
            time_complexity: "O(n * chi^3 * gates)".into(),
            space_complexity: "O(n * chi^2)".into(),
        },
        ScalingInfo {
            backend: BackendType::CliffordT,
            max_qubits_exact: 1000,
            max_qubits_approximate: 10_000,
            time_complexity: "O(2^t * n^2 * gates) for t T-gates".into(),
            space_complexity: "O(2^t * n^2)".into(),
        },
    ]
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use crate::circuit::QuantumCircuit;

    #[test]
    fn pure_clifford_selects_stabilizer() {
        let mut circ = QuantumCircuit::new(50);
        for q in 0..50 {
            circ.h(q);
        }
        for q in 0..49 {
            circ.cnot(q, q + 1);
        }
        let analysis = analyze_circuit(&circ);
        assert_eq!(analysis.recommended_backend, BackendType::Stabilizer);
        assert!(analysis.clifford_fraction >= 1.0);
        assert!(analysis.confidence > 0.9);
    }

    #[test]
    fn small_circuit_selects_state_vector() {
        let mut circ = QuantumCircuit::new(5);
        circ.h(0).t(1).cnot(0, 1);
        let analysis = analyze_circuit(&circ);
        assert_eq!(analysis.recommended_backend, BackendType::StateVector);
        assert!(analysis.confidence > 0.9);
    }

    #[test]
    fn medium_circuit_selects_state_vector() {
        let mut circ = QuantumCircuit::new(30);
        circ.h(0).rx(1, 1.0).cnot(0, 1);
        let analysis = analyze_circuit(&circ);
        assert_eq!(analysis.recommended_backend, BackendType::StateVector);
        assert!(analysis.confidence >= 0.80);
    }

    #[test]
    fn large_nearest_neighbor_selects_tensor_network() {
        let mut circ = QuantumCircuit::new(64);
        // Low depth, nearest-neighbor only.
        for q in 0..63 {
            circ.cnot(q, q + 1);
        }
        // Add enough non-Clifford gates to avoid the "mostly Clifford" Rule 2
        // (which requires non_clifford_gates <= 10).
        for q in 0..12 {
            circ.t(q);
        }
        let analysis = analyze_circuit(&circ);
        assert_eq!(analysis.recommended_backend, BackendType::TensorNetwork);
    }

    #[test]
    fn empty_circuit_defaults() {
        let circ = QuantumCircuit::new(10);
        let analysis = analyze_circuit(&circ);
        // Empty circuit is "pure Clifford" (no non-Clifford gates).
        assert_eq!(analysis.total_gates, 0);
        assert!(analysis.clifford_fraction >= 1.0);
    }

    #[test]
    fn measurement_counted() {
        let mut circ = QuantumCircuit::new(3);
        circ.h(0).measure(0).measure(1).measure(2);
        let analysis = analyze_circuit(&circ);
        assert_eq!(analysis.measurement_gates, 3);
    }

    #[test]
    fn connectivity_detected() {
        let mut circ = QuantumCircuit::new(10);
        circ.cnot(0, 5); // distance = 5
        let analysis = analyze_circuit(&circ);
        assert_eq!(analysis.max_connectivity, 5);
        assert!(!analysis.is_nearest_neighbor);
    }

    #[test]
    fn scaling_report_has_four_entries() {
        let report = scaling_report();
        assert_eq!(report.len(), 4);
        assert_eq!(report[0].backend, BackendType::StateVector);
        assert_eq!(report[1].backend, BackendType::Stabilizer);
        assert_eq!(report[2].backend, BackendType::TensorNetwork);
        assert_eq!(report[3].backend, BackendType::CliffordT);
    }

    #[test]
    fn format_memory_values() {
        // 10 qubits => 2^10 * 16 = 16384 bytes
        assert_eq!(format_memory(10), "16384 bytes");
        // 20 qubits => 2^20 * 16 = 16 MiB
        assert_eq!(format_memory(20), "16.0 MiB");
        // 30 qubits => 2^30 * 16 = 16 GiB
        assert_eq!(format_memory(30), "16.0 GiB");
    }
}