Files
wifi-densepose/crates/ruqu-core/src/backend.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

473 lines
16 KiB
Rust

//! 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");
}
}