use crate::backend::{analyze_circuit, BackendType, CircuitAnalysis};
use crate::circuit::QuantumCircuit;
#[derive(Debug, Clone)]
pub struct ExecutionPlan {
pub backend: BackendType,
pub predicted_memory_bytes: u64,
pub predicted_runtime_ms: f64,
pub confidence: f64,
pub verification_policy: VerificationPolicy,
pub mitigation_strategy: MitigationStrategy,
pub entanglement_budget: Option<EntanglementBudget>,
pub explanation: String,
pub cost_breakdown: CostBreakdown,
}
#[derive(Debug, Clone, PartialEq)]
pub enum VerificationPolicy {
ExactCliffordCheck,
DownscaledStateVector(u32),
StatisticalSampling(u32),
None,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MitigationStrategy {
None,
MeasurementCorrectionOnly,
ZneWithScales(Vec<f64>),
ZnePlusMeasurementCorrection(Vec<f64>),
Full {
zne_scales: Vec<f64>,
cdr_circuits: usize,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct EntanglementBudget {
pub max_bond_dimension: u32,
pub predicted_peak_bond: u32,
pub truncation_needed: bool,
}
#[derive(Debug, Clone)]
pub struct CostBreakdown {
pub simulation_cost: f64,
pub mitigation_overhead: f64,
pub verification_overhead: f64,
pub total_shots_needed: u32,
}
#[derive(Debug, Clone)]
pub struct PlannerConfig {
pub available_memory_bytes: u64,
pub noise_level: Option<f64>,
pub shot_budget: u32,
pub target_precision: f64,
}
impl Default for PlannerConfig {
fn default() -> Self {
Self {
available_memory_bytes: 8 * 1024 * 1024 * 1024, noise_level: Option::None,
shot_budget: 10_000,
target_precision: 0.01,
}
}
}
const SV_NS_PER_GATE: f64 = 4.0;
const STAB_NS_PER_GATE: f64 = 0.1;
const TN_NS_PER_GATE: f64 = 2.0;
const SV_COMFORT_QUBITS: u32 = 25;
const DEFAULT_MAX_BOND_DIM: u32 = 256;
const ABSOLUTE_MAX_BOND_DIM: u32 = 4096;
const CT_NS_PER_GATE: f64 = 0.15;
const CT_MAX_T_COUNT: usize = 40;
pub fn plan_execution(circuit: &QuantumCircuit, config: &PlannerConfig) -> ExecutionPlan {
let analysis = analyze_circuit(circuit);
let entanglement = estimate_entanglement(circuit);
let num_qubits = analysis.num_qubits;
let total_gates = analysis.total_gates;
let stab_viable = analysis.clifford_fraction >= 1.0;
let stab_memory = predict_memory_stabilizer(num_qubits);
let stab_runtime = predict_runtime_stabilizer(num_qubits, total_gates);
let sv_memory = predict_memory_statevector(num_qubits);
let sv_viable = sv_memory <= config.available_memory_bytes;
let sv_runtime = predict_runtime_statevector(num_qubits, total_gates);
let chi = entanglement.predicted_peak_bond.min(ABSOLUTE_MAX_BOND_DIM);
let tn_memory = predict_memory_tensor_network(num_qubits, chi);
let tn_viable = tn_memory <= config.available_memory_bytes;
let tn_runtime = predict_runtime_tensor_network(num_qubits, total_gates, chi);
let t_count = analysis.non_clifford_gates;
let ct_viable = t_count > 0 && t_count <= CT_MAX_T_COUNT && num_qubits > 32;
let ct_terms = if ct_viable { 1u64.checked_shl(t_count as u32).unwrap_or(u64::MAX) } else { u64::MAX };
let ct_memory = predict_memory_clifford_t(num_qubits, ct_terms);
let ct_runtime = predict_runtime_clifford_t(num_qubits, total_gates, ct_terms);
let (backend, predicted_memory, predicted_runtime, confidence, explanation) =
select_optimal_backend(
&analysis,
&entanglement,
config,
stab_viable,
stab_memory,
stab_runtime,
sv_viable,
sv_memory,
sv_runtime,
tn_viable,
tn_memory,
tn_runtime,
chi,
ct_viable,
ct_memory,
ct_runtime,
ct_terms,
);
let verification_policy = select_verification_policy(&analysis, backend, num_qubits);
let mitigation_strategy =
select_mitigation_strategy(config.noise_level, config.shot_budget, &analysis);
let entanglement_budget = if backend == BackendType::TensorNetwork {
Some(entanglement)
} else {
Option::None
};
let cost_breakdown = compute_cost_breakdown(
backend,
predicted_runtime,
&mitigation_strategy,
&verification_policy,
config.shot_budget,
config.target_precision,
);
ExecutionPlan {
backend,
predicted_memory_bytes: predicted_memory,
predicted_runtime_ms: predicted_runtime,
confidence,
verification_policy,
mitigation_strategy,
entanglement_budget,
explanation,
cost_breakdown,
}
}
pub fn estimate_entanglement(circuit: &QuantumCircuit) -> EntanglementBudget {
let n = circuit.num_qubits();
if n <= 1 {
return EntanglementBudget {
max_bond_dimension: 1,
predicted_peak_bond: 1,
truncation_needed: false,
};
}
let num_cuts = (n - 1) as usize;
let mut cut_counts = vec![0u32; num_cuts];
for gate in circuit.gates() {
let qubits = gate.qubits();
if qubits.len() == 2 {
let (lo, hi) = if qubits[0] < qubits[1] {
(qubits[0], qubits[1])
} else {
(qubits[1], qubits[0])
};
for cut_idx in (lo as usize)..(hi as usize) {
if cut_idx < num_cuts {
cut_counts[cut_idx] += 1;
}
}
}
}
let max_gates_across_cut = cut_counts.iter().copied().max().unwrap_or(0);
let half_n = n / 2;
let effective_exponent = max_gates_across_cut.min(half_n).min(30);
let predicted_peak_bond = 1u32.checked_shl(effective_exponent).unwrap_or(u32::MAX);
let max_bond_dimension = predicted_peak_bond
.saturating_mul(2)
.min(ABSOLUTE_MAX_BOND_DIM);
let truncation_needed = predicted_peak_bond > DEFAULT_MAX_BOND_DIM;
EntanglementBudget {
max_bond_dimension,
predicted_peak_bond,
truncation_needed,
}
}
fn predict_memory_statevector(num_qubits: u32) -> u64 {
if num_qubits >= 64 {
return u64::MAX;
}
(1u64 << num_qubits).saturating_mul(16)
}
fn predict_memory_stabilizer(num_qubits: u32) -> u64 {
let n = num_qubits as u64;
n.saturating_mul(n) / 4
}
fn predict_memory_tensor_network(num_qubits: u32, chi: u32) -> u64 {
let n = num_qubits as u64;
let c = chi as u64;
n.saturating_mul(c)
.saturating_mul(c)
.saturating_mul(16)
}
fn predict_runtime_statevector(num_qubits: u32, total_gates: usize) -> f64 {
if num_qubits >= 64 {
return f64::INFINITY;
}
let base_ops = (1u64 << num_qubits) as f64 * total_gates as f64;
let ns = base_ops * SV_NS_PER_GATE;
let scaling = if num_qubits > SV_COMFORT_QUBITS {
2.0_f64.powi((num_qubits - SV_COMFORT_QUBITS) as i32)
} else {
1.0
};
ns * scaling / 1_000_000.0 }
fn predict_runtime_stabilizer(num_qubits: u32, total_gates: usize) -> f64 {
let n = num_qubits as f64;
let ns = n * n * total_gates as f64 * STAB_NS_PER_GATE;
ns / 1_000_000.0
}
fn predict_runtime_tensor_network(num_qubits: u32, total_gates: usize, chi: u32) -> f64 {
let n = num_qubits as f64;
let c = chi as f64;
let ns = n * c * c * c * total_gates as f64 * TN_NS_PER_GATE;
ns / 1_000_000.0
}
fn predict_memory_clifford_t(num_qubits: u32, terms: u64) -> u64 {
let n = num_qubits as u64;
let per_term = n.saturating_mul(n) / 4 + 16;
terms.saturating_mul(per_term)
}
fn predict_runtime_clifford_t(num_qubits: u32, total_gates: usize, terms: u64) -> f64 {
let n = num_qubits as f64;
let ns = terms as f64 * n * n * total_gates as f64 * CT_NS_PER_GATE;
ns / 1_000_000.0
}
#[allow(clippy::too_many_arguments)]
fn select_optimal_backend(
analysis: &CircuitAnalysis,
entanglement: &EntanglementBudget,
config: &PlannerConfig,
stab_viable: bool,
stab_memory: u64,
stab_runtime: f64,
sv_viable: bool,
sv_memory: u64,
sv_runtime: f64,
_tn_viable: bool,
tn_memory: u64,
tn_runtime: f64,
chi: u32,
ct_viable: bool,
ct_memory: u64,
ct_runtime: f64,
ct_terms: u64,
) -> (BackendType, u64, f64, f64, String) {
let n = analysis.num_qubits;
if stab_viable {
return (
BackendType::Stabilizer,
stab_memory,
stab_runtime,
0.99,
format!(
"Pure Clifford circuit ({} qubits, {} gates): stabilizer simulation in \
O(n^2) per gate. Predicted {:.1} ms, {} bytes memory.",
n, analysis.total_gates, stab_runtime, stab_memory
),
);
}
if analysis.clifford_fraction >= 0.95
&& n > 32
&& analysis.non_clifford_gates <= 10
{
return (
BackendType::Stabilizer,
stab_memory,
stab_runtime,
0.85,
format!(
"{:.0}% Clifford with only {} non-Clifford gates on {} qubits: \
stabilizer backend with approximate decomposition.",
analysis.clifford_fraction * 100.0,
analysis.non_clifford_gates,
n
),
);
}
if ct_viable && ct_memory <= config.available_memory_bytes {
return (
BackendType::CliffordT,
ct_memory,
ct_runtime,
0.90,
format!(
"{} qubits with {} T-gates: Clifford+T decomposition with {} stabilizer terms. \
Predicted {:.2} ms, {} bytes.",
n, analysis.non_clifford_gates, ct_terms, ct_runtime, ct_memory
),
);
}
if sv_viable && n <= 32 {
let conf = if n <= SV_COMFORT_QUBITS { 0.95 } else { 0.80 };
return (
BackendType::StateVector,
sv_memory,
sv_runtime,
conf,
format!(
"{} qubits fits in state vector ({} bytes). Predicted {:.2} ms runtime.",
n, sv_memory, sv_runtime
),
);
}
if !sv_viable || n > 32 {
let conf = if analysis.is_nearest_neighbor && analysis.depth < n * 2 {
0.85
} else if analysis.is_nearest_neighbor {
0.75
} else {
0.55
};
let used_memory = tn_memory;
let used_runtime = tn_runtime;
let truncation_note = if entanglement.truncation_needed {
" Results will be approximate due to bond dimension truncation."
} else {
""
};
return (
BackendType::TensorNetwork,
used_memory,
used_runtime,
conf,
format!(
"{} qubits exceeds state vector capacity ({} bytes > {} bytes available). \
Using tensor network with chi={}.{} Predicted {:.2} ms.",
n,
predict_memory_statevector(n),
config.available_memory_bytes,
chi,
truncation_note,
used_runtime
),
);
}
(
BackendType::StateVector,
sv_memory,
sv_runtime,
0.70,
"Default to exact state vector simulation.".into(),
)
}
fn select_verification_policy(
analysis: &CircuitAnalysis,
backend: BackendType,
num_qubits: u32,
) -> VerificationPolicy {
if analysis.clifford_fraction >= 1.0 {
return VerificationPolicy::ExactCliffordCheck;
}
if analysis.clifford_fraction >= 0.9 && num_qubits > 20 {
let downscale_qubits = num_qubits.min(16);
return VerificationPolicy::DownscaledStateVector(downscale_qubits);
}
if backend == BackendType::StateVector && num_qubits <= SV_COMFORT_QUBITS {
return VerificationPolicy::None;
}
if backend == BackendType::StateVector && num_qubits <= 32 {
return VerificationPolicy::StatisticalSampling(10);
}
if backend == BackendType::TensorNetwork {
if num_qubits <= 20 {
return VerificationPolicy::DownscaledStateVector(num_qubits);
}
return VerificationPolicy::StatisticalSampling(
(num_qubits / 2).max(5).min(50),
);
}
VerificationPolicy::None
}
fn select_mitigation_strategy(
noise_level: Option<f64>,
shot_budget: u32,
analysis: &CircuitAnalysis,
) -> MitigationStrategy {
let noise = match noise_level {
Some(n) if n > 0.0 => n,
_ => return MitigationStrategy::None,
};
if noise < 0.01 {
return MitigationStrategy::MeasurementCorrectionOnly;
}
let zne_scales_3 = vec![1.0, 1.5, 2.0];
let zne_scales_5 = vec![1.0, 1.25, 1.5, 1.75, 2.0];
if noise < 0.1 {
let scales = if shot_budget >= 50_000 {
zne_scales_5
} else {
zne_scales_3.clone()
};
return MitigationStrategy::ZneWithScales(scales);
}
if noise < 0.5 {
let scales = if shot_budget >= 50_000 {
zne_scales_5
} else {
zne_scales_3
};
return MitigationStrategy::ZnePlusMeasurementCorrection(scales);
}
let cdr_circuits = (analysis.non_clifford_gates * 2).max(10).min(100);
MitigationStrategy::Full {
zne_scales: vec![1.0, 1.5, 2.0, 2.5, 3.0],
cdr_circuits,
}
}
fn compute_cost_breakdown(
_backend: BackendType,
predicted_runtime_ms: f64,
mitigation: &MitigationStrategy,
verification: &VerificationPolicy,
shot_budget: u32,
target_precision: f64,
) -> CostBreakdown {
let simulation_cost = predicted_runtime_ms.max(0.001);
let mitigation_overhead = match mitigation {
MitigationStrategy::None => 1.0,
MitigationStrategy::MeasurementCorrectionOnly => 1.1, MitigationStrategy::ZneWithScales(scales) => scales.len() as f64,
MitigationStrategy::ZnePlusMeasurementCorrection(scales) => {
scales.len() as f64 * 1.1
}
MitigationStrategy::Full { zne_scales, cdr_circuits } => {
zne_scales.len() as f64 + *cdr_circuits as f64 * 0.5
}
};
let verification_overhead = match verification {
VerificationPolicy::None => 1.0,
VerificationPolicy::ExactCliffordCheck => 1.05, VerificationPolicy::DownscaledStateVector(_) => 1.1,
VerificationPolicy::StatisticalSampling(n) => {
1.0 + (*n as f64) * 0.01
}
};
let base_shots = (1.0 / (target_precision * target_precision)).ceil() as u32;
let mitigated_shots =
(base_shots as f64 * mitigation_overhead).ceil() as u32;
let total_shots_needed = mitigated_shots.min(shot_budget);
CostBreakdown {
simulation_cost,
mitigation_overhead,
verification_overhead,
total_shots_needed,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::circuit::QuantumCircuit;
fn default_config() -> PlannerConfig {
PlannerConfig::default()
}
#[test]
fn test_pure_clifford_plan() {
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 config = default_config();
let plan = plan_execution(&circ, &config);
assert_eq!(
plan.backend,
BackendType::Stabilizer,
"Pure Clifford circuit should use Stabilizer backend"
);
assert_eq!(
plan.verification_policy,
VerificationPolicy::ExactCliffordCheck,
"Pure Clifford should use ExactCliffordCheck verification"
);
assert_eq!(
plan.mitigation_strategy,
MitigationStrategy::None,
"Noiseless config should have no mitigation"
);
assert!(
plan.confidence > 0.9,
"Confidence should be high for pure Clifford"
);
assert!(
plan.entanglement_budget.is_none(),
"Stabilizer backend should not have entanglement budget"
);
}
#[test]
fn test_small_circuit_plan() {
let mut circ = QuantumCircuit::new(5);
circ.h(0).t(1).cnot(0, 1).rx(2, 0.5);
let config = default_config();
let plan = plan_execution(&circ, &config);
assert_eq!(
plan.backend,
BackendType::StateVector,
"Small non-Clifford circuit should use StateVector"
);
assert_eq!(
plan.mitigation_strategy,
MitigationStrategy::None,
"Noiseless config should have no mitigation"
);
assert_eq!(
plan.verification_policy,
VerificationPolicy::None,
"Small SV circuit should not need verification"
);
assert!(plan.entanglement_budget.is_none());
assert_eq!(plan.predicted_memory_bytes, 512);
assert!(plan.predicted_runtime_ms > 0.0);
assert!(plan.confidence >= 0.9);
}
#[test]
fn test_large_mps_plan() {
let mut circ = QuantumCircuit::new(64);
for q in 0..63 {
circ.cnot(q, q + 1);
}
for q in 0..50 {
circ.t(q % 64);
}
let config = PlannerConfig {
available_memory_bytes: 8 * 1024 * 1024 * 1024,
noise_level: Option::None,
shot_budget: 10_000,
target_precision: 0.01,
};
let plan = plan_execution(&circ, &config);
assert_eq!(
plan.backend,
BackendType::TensorNetwork,
"Large non-Clifford circuit should use TensorNetwork"
);
assert!(
plan.entanglement_budget.is_some(),
"TensorNetwork backend should have entanglement budget"
);
let budget = plan.entanglement_budget.as_ref().unwrap();
assert!(
budget.predicted_peak_bond >= 2,
"Entangling gates should produce bond dimension >= 2"
);
assert!(
budget.max_bond_dimension >= budget.predicted_peak_bond,
"Max bond dimension should be >= predicted peak"
);
}
#[test]
fn test_memory_overflow_fallback() {
let mut circ = QuantumCircuit::new(30);
circ.h(0).t(1).cnot(0, 1);
let config = PlannerConfig {
available_memory_bytes: 1024 * 1024, noise_level: Option::None,
shot_budget: 10_000,
target_precision: 0.01,
};
let plan = plan_execution(&circ, &config);
assert_eq!(
plan.backend,
BackendType::TensorNetwork,
"When SV exceeds memory, should fall back to TensorNetwork"
);
assert!(
plan.predicted_memory_bytes <= config.available_memory_bytes,
"TensorNetwork memory ({}) should fit within budget ({})",
plan.predicted_memory_bytes,
config.available_memory_bytes
);
}
#[test]
fn test_noisy_circuit_plan() {
let mut circ = QuantumCircuit::new(5);
circ.h(0).cnot(0, 1).t(2);
let config = PlannerConfig {
available_memory_bytes: 8 * 1024 * 1024 * 1024,
noise_level: Some(0.05), shot_budget: 10_000,
target_precision: 0.01,
};
let plan = plan_execution(&circ, &config);
match &plan.mitigation_strategy {
MitigationStrategy::ZneWithScales(scales) => {
assert!(
scales.len() >= 3,
"ZNE should have at least 3 scale factors"
);
assert!(
scales.contains(&1.0),
"ZNE scales must include the baseline 1.0"
);
}
other => panic!(
"Expected ZneWithScales for noise=0.05, got {:?}",
other
),
}
assert!(
plan.cost_breakdown.mitigation_overhead > 1.0,
"Mitigation should add overhead"
);
}
#[test]
fn test_entanglement_estimate() {
let mut circ = QuantumCircuit::new(2);
circ.h(0).cnot(0, 1);
let budget = estimate_entanglement(&circ);
assert_eq!(
budget.predicted_peak_bond, 2,
"Bell state should have bond dimension 2"
);
assert!(
!budget.truncation_needed,
"Bell state bond dimension 2 should not need truncation"
);
}
#[test]
fn test_entanglement_estimate_single_qubit() {
let mut circ = QuantumCircuit::new(1);
circ.h(0);
let budget = estimate_entanglement(&circ);
assert_eq!(budget.predicted_peak_bond, 1);
assert_eq!(budget.max_bond_dimension, 1);
assert!(!budget.truncation_needed);
}
#[test]
fn test_entanglement_estimate_no_two_qubit_gates() {
let mut circ = QuantumCircuit::new(10);
for q in 0..10 {
circ.h(q);
}
let budget = estimate_entanglement(&circ);
assert_eq!(budget.predicted_peak_bond, 1);
}
#[test]
fn test_entanglement_estimate_ghz_chain() {
let mut circ = QuantumCircuit::new(4);
circ.h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3);
let budget = estimate_entanglement(&circ);
assert_eq!(
budget.predicted_peak_bond, 2,
"GHZ chain should have peak bond dim 2 (nearest-neighbor only)"
);
}
#[test]
fn test_workload_routing_accuracy() {
let config = default_config();
let circ_empty = QuantumCircuit::new(10);
let plan = plan_execution(&circ_empty, &config);
assert_eq!(plan.backend, BackendType::Stabilizer);
let mut circ_h = QuantumCircuit::new(3);
circ_h.h(0);
let plan = plan_execution(&circ_h, &config);
assert_eq!(plan.backend, BackendType::Stabilizer);
let mut circ_bell = QuantumCircuit::new(2);
circ_bell.h(0).cnot(0, 1);
let plan = plan_execution(&circ_bell, &config);
assert_eq!(plan.backend, BackendType::Stabilizer);
let mut circ_small_t = QuantumCircuit::new(5);
circ_small_t.h(0).t(1).cnot(0, 1);
let plan = plan_execution(&circ_small_t, &config);
assert_eq!(plan.backend, BackendType::StateVector);
let mut circ_vqe = QuantumCircuit::new(20);
for q in 0..20 {
circ_vqe.ry(q, 0.5);
}
for q in 0..19 {
circ_vqe.cnot(q, q + 1);
}
let plan = plan_execution(&circ_vqe, &config);
assert_eq!(plan.backend, BackendType::StateVector);
let mut circ_40_cliff = QuantumCircuit::new(40);
for q in 0..40 {
circ_40_cliff.h(q);
}
for q in 0..39 {
circ_40_cliff.cnot(q, q + 1);
}
let plan = plan_execution(&circ_40_cliff, &config);
assert_eq!(plan.backend, BackendType::Stabilizer);
let mut circ_100 = QuantumCircuit::new(100);
for q in 0..99 {
circ_100.cnot(q, q + 1);
}
for q in 0..50 {
circ_100.rx(q, 1.0);
}
let plan = plan_execution(&circ_100, &config);
assert_eq!(plan.backend, BackendType::TensorNetwork);
let mut circ_mostly_cliff = QuantumCircuit::new(50);
for q in 0..50 {
circ_mostly_cliff.h(q);
}
for q in 0..49 {
circ_mostly_cliff.cnot(q, q + 1);
}
for q in 0..5 {
circ_mostly_cliff.t(q);
}
let plan = plan_execution(&circ_mostly_cliff, &config);
assert_eq!(
plan.backend,
BackendType::Stabilizer,
"Mostly-Clifford 50-qubit circuit should use Stabilizer"
);
let mut circ_25 = QuantumCircuit::new(25);
for q in 0..25 {
circ_25.h(q);
}
for q in 0..24 {
circ_25.cnot(q, q + 1);
}
circ_25.t(0).t(1).rx(2, 0.5);
let plan = plan_execution(&circ_25, &config);
assert_eq!(plan.backend, BackendType::StateVector);
let mut circ_28 = QuantumCircuit::new(28);
circ_28.h(0).t(1).cnot(0, 1);
let tight_config = PlannerConfig {
available_memory_bytes: 1024, noise_level: Option::None,
shot_budget: 1000,
target_precision: 0.01,
};
let plan = plan_execution(&circ_28, &tight_config);
assert_eq!(
plan.backend,
BackendType::TensorNetwork,
"Should fall back to TN when memory is too tight for SV"
);
let mut circ_noisy = QuantumCircuit::new(5);
circ_noisy.h(0).t(0).cnot(0, 1);
let noisy_config = PlannerConfig {
available_memory_bytes: 8 * 1024 * 1024 * 1024,
noise_level: Some(0.7),
shot_budget: 100_000,
target_precision: 0.01,
};
let plan = plan_execution(&circ_noisy, &noisy_config);
match &plan.mitigation_strategy {
MitigationStrategy::Full {
zne_scales,
cdr_circuits,
} => {
assert!(zne_scales.len() >= 3);
assert!(*cdr_circuits >= 2);
}
other => panic!(
"Expected Full mitigation for noise=0.7, got {:?}",
other
),
}
}
#[test]
fn test_memory_prediction_statevector() {
assert_eq!(predict_memory_statevector(1), 32); assert_eq!(predict_memory_statevector(10), 1024 * 16); assert_eq!(predict_memory_statevector(20), 1048576 * 16); }
#[test]
fn test_memory_prediction_stabilizer() {
assert_eq!(predict_memory_stabilizer(100), 2500);
assert_eq!(predict_memory_stabilizer(1000), 250_000);
}
#[test]
fn test_memory_prediction_tensor_network() {
assert_eq!(predict_memory_tensor_network(10, 4), 10 * 16 * 16);
assert_eq!(predict_memory_tensor_network(100, 32), 100 * 1024 * 16);
}
#[test]
fn test_runtime_prediction_statevector() {
let rt = predict_runtime_statevector(10, 100);
let expected = (1024.0 * 100.0 * 4.0) / 1_000_000.0;
assert!(
(rt - expected).abs() < 1e-6,
"SV runtime for 10 qubits: expected {expected}, got {rt}"
);
}
#[test]
fn test_runtime_prediction_stabilizer() {
let rt = predict_runtime_stabilizer(100, 200);
let expected = (10000.0 * 200.0 * 0.1) / 1_000_000.0;
assert!(
(rt - expected).abs() < 1e-6,
"Stabilizer runtime: expected {expected}, got {rt}"
);
}
#[test]
fn test_runtime_scales_above_25_qubits() {
let rt_25 = predict_runtime_statevector(25, 100);
let rt_26 = predict_runtime_statevector(26, 100);
let ratio = rt_26 / rt_25;
assert!(
(ratio - 4.0).abs() < 0.1,
"Going from 25 to 26 qubits should ~4x the runtime, got {ratio}x"
);
}
#[test]
fn test_cost_breakdown_no_mitigation() {
let breakdown = compute_cost_breakdown(
BackendType::StateVector,
1.0,
&MitigationStrategy::None,
&VerificationPolicy::None,
10_000,
0.01,
);
assert_eq!(breakdown.mitigation_overhead, 1.0);
assert_eq!(breakdown.verification_overhead, 1.0);
assert!(breakdown.total_shots_needed <= 10_000);
}
#[test]
fn test_cost_breakdown_with_zne() {
let scales = vec![1.0, 1.5, 2.0];
let breakdown = compute_cost_breakdown(
BackendType::StateVector,
1.0,
&MitigationStrategy::ZneWithScales(scales),
&VerificationPolicy::None,
100_000,
0.01,
);
assert_eq!(
breakdown.mitigation_overhead, 3.0,
"3 ZNE scales -> 3x overhead"
);
assert!(breakdown.total_shots_needed > 10_000);
}
#[test]
fn test_mitigation_none_for_noiseless() {
let analysis = make_analysis(5, 10, 1.0);
let strat = select_mitigation_strategy(Option::None, 10_000, &analysis);
assert_eq!(strat, MitigationStrategy::None);
}
#[test]
fn test_mitigation_measurement_correction_low_noise() {
let analysis = make_analysis(5, 10, 0.5);
let strat = select_mitigation_strategy(Some(0.005), 10_000, &analysis);
assert_eq!(strat, MitigationStrategy::MeasurementCorrectionOnly);
}
#[test]
fn test_mitigation_zne_medium_noise() {
let analysis = make_analysis(5, 10, 0.5);
let strat = select_mitigation_strategy(Some(0.05), 10_000, &analysis);
match strat {
MitigationStrategy::ZneWithScales(scales) => {
assert!(scales.contains(&1.0));
assert!(scales.len() >= 3);
}
other => panic!("Expected ZneWithScales, got {:?}", other),
}
}
#[test]
fn test_mitigation_full_for_high_noise() {
let analysis = make_analysis(5, 10, 0.5);
let strat = select_mitigation_strategy(Some(0.7), 100_000, &analysis);
match strat {
MitigationStrategy::Full { zne_scales, cdr_circuits } => {
assert!(zne_scales.len() >= 3);
assert!(cdr_circuits >= 2);
}
other => panic!("Expected Full mitigation, got {:?}", other),
}
}
#[test]
fn test_verification_clifford_check() {
let analysis = make_analysis(10, 50, 1.0);
let policy = select_verification_policy(
&analysis,
BackendType::Stabilizer,
10,
);
assert_eq!(policy, VerificationPolicy::ExactCliffordCheck);
}
#[test]
fn test_verification_none_for_small_sv() {
let analysis = make_analysis(5, 10, 0.5);
let policy = select_verification_policy(
&analysis,
BackendType::StateVector,
5,
);
assert_eq!(policy, VerificationPolicy::None);
}
#[test]
fn test_verification_statistical_for_tn() {
let analysis = make_analysis(50, 100, 0.5);
let policy = select_verification_policy(
&analysis,
BackendType::TensorNetwork,
50,
);
match policy {
VerificationPolicy::StatisticalSampling(n) => {
assert!(n >= 5, "Should sample at least 5 observables");
}
other => panic!(
"Expected StatisticalSampling for TN, got {:?}",
other
),
}
}
#[test]
fn test_planner_config_default() {
let config = PlannerConfig::default();
assert_eq!(config.available_memory_bytes, 8 * 1024 * 1024 * 1024);
assert!(config.noise_level.is_none());
assert_eq!(config.shot_budget, 10_000);
assert!((config.target_precision - 0.01).abs() < 1e-12);
}
#[test]
fn test_plan_has_nonempty_explanation() {
let mut circ = QuantumCircuit::new(3);
circ.h(0).cnot(0, 1);
let plan = plan_execution(&circ, &default_config());
assert!(
!plan.explanation.is_empty(),
"Plan explanation should not be empty"
);
}
#[test]
fn test_zero_qubit_circuit() {
let circ = QuantumCircuit::new(0);
let plan = plan_execution(&circ, &default_config());
assert_eq!(plan.backend, BackendType::Stabilizer);
}
fn make_analysis(
num_qubits: u32,
total_gates: usize,
clifford_fraction: f64,
) -> CircuitAnalysis {
let clifford_gates =
(total_gates as f64 * clifford_fraction).round() as usize;
let non_clifford_gates = total_gates - clifford_gates;
CircuitAnalysis {
num_qubits,
total_gates,
clifford_gates,
non_clifford_gates,
clifford_fraction,
measurement_gates: 0,
depth: total_gates as u32,
max_connectivity: 1,
is_nearest_neighbor: true,
recommended_backend: BackendType::Auto,
confidence: 0.5,
explanation: String::new(),
}
}
#[test]
fn test_clifford_t_routing() {
let mut circ = QuantumCircuit::new(50);
for q in 0..50 {
circ.h(q);
}
for q in 0..49 {
circ.cnot(q, q + 1);
}
for q in 0..15 {
circ.t(q);
}
let config = default_config();
let plan = plan_execution(&circ, &config);
assert_eq!(
plan.backend,
BackendType::CliffordT,
"50 qubits with 15 T-gates should use CliffordT backend"
);
assert!(plan.confidence >= 0.85);
}
}