use std::collections::HashMap;
use crate::backend::BackendType;
use crate::circuit::QuantumCircuit;
use crate::decomposition::{
decompose, stitch_results, CircuitPartition, DecompositionStrategy,
};
use crate::error::Result;
use crate::planner::{plan_execution, ExecutionPlan, PlannerConfig};
use crate::simulator::Simulator;
use crate::verification::{verify_circuit, VerificationResult};
#[derive(Debug, Clone)]
pub struct PipelineConfig {
pub planner: PlannerConfig,
pub max_segment_qubits: u32,
pub shots: u32,
pub verify: bool,
pub seed: u64,
}
impl Default for PipelineConfig {
fn default() -> Self {
Self {
planner: PlannerConfig::default(),
max_segment_qubits: 25,
shots: 1024,
verify: true,
seed: 42,
}
}
}
#[derive(Debug, Clone)]
pub struct PipelineResult {
pub plan: ExecutionPlan,
pub decomposition: DecompositionSummary,
pub segment_results: Vec<SegmentResult>,
pub distribution: HashMap<Vec<bool>, f64>,
pub total_probability: f64,
pub verification: Option<VerificationResult>,
pub estimated_fidelity: f64,
}
#[derive(Debug, Clone)]
pub struct DecompositionSummary {
pub num_segments: usize,
pub strategy: DecompositionStrategy,
pub backends: Vec<BackendType>,
}
#[derive(Debug, Clone)]
pub struct SegmentResult {
pub index: usize,
pub backend: BackendType,
pub num_qubits: u32,
pub distribution: Vec<(Vec<bool>, f64)>,
}
pub struct Pipeline;
impl Pipeline {
pub fn execute(
circuit: &QuantumCircuit,
config: &PipelineConfig,
) -> Result<PipelineResult> {
let plan = plan_execution(circuit, &config.planner);
let partition = decompose(circuit, config.max_segment_qubits);
let decomposition = DecompositionSummary {
num_segments: partition.segments.len(),
strategy: partition.strategy,
backends: partition
.segments
.iter()
.map(|s| s.backend)
.collect(),
};
let mut segment_results = Vec::new();
let mut all_segment_distributions: Vec<Vec<(Vec<bool>, f64)>> =
Vec::new();
for (idx, segment) in partition.segments.iter().enumerate() {
let shot_seed = config.seed.wrapping_add(idx as u64);
let shot_result = Simulator::run_shots(
&segment.circuit,
config.shots,
Some(shot_seed),
)?;
let dist = counts_to_distribution(&shot_result.counts);
segment_results.push(SegmentResult {
index: idx,
backend: resolve_backend(segment.backend),
num_qubits: segment.circuit.num_qubits(),
distribution: dist.clone(),
});
all_segment_distributions.push(dist);
}
let flat_partitions: Vec<(Vec<bool>, f64)> =
all_segment_distributions
.into_iter()
.flatten()
.collect();
let distribution = stitch_results(&flat_partitions);
let total_probability: f64 = distribution.values().sum();
let estimated_fidelity =
estimate_pipeline_fidelity(&segment_results, &partition);
let verification =
if config.verify && circuit.num_qubits() <= 25 {
Some(verify_circuit(circuit, config.shots, config.seed))
} else {
None
};
Ok(PipelineResult {
plan,
decomposition,
segment_results,
distribution,
total_probability,
verification,
estimated_fidelity,
})
}
}
fn resolve_backend(backend: BackendType) -> BackendType {
match backend {
BackendType::Auto => BackendType::StateVector,
BackendType::CliffordT => BackendType::StateVector,
other => other,
}
}
fn counts_to_distribution(
counts: &HashMap<Vec<bool>, usize>,
) -> Vec<(Vec<bool>, f64)> {
let total: usize = counts.values().sum();
if total == 0 {
return Vec::new();
}
let total_f = total as f64;
let mut dist: Vec<(Vec<bool>, f64)> = counts
.iter()
.map(|(bits, &count)| (bits.clone(), count as f64 / total_f))
.collect();
dist.sort_by(|a, b| {
b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
});
dist
}
fn estimate_pipeline_fidelity(
segments: &[SegmentResult],
partition: &CircuitPartition,
) -> f64 {
if segments.len() <= 1 {
return 1.0;
}
let num_cuts = segments.len().saturating_sub(1);
let per_cut_fidelity: f64 = match partition.strategy {
DecompositionStrategy::Spatial | DecompositionStrategy::Hybrid => 0.95,
DecompositionStrategy::Temporal => 0.99,
DecompositionStrategy::None => 1.0,
};
per_cut_fidelity.powi(num_cuts as i32)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::circuit::QuantumCircuit;
#[test]
fn test_pipeline_bell_state() {
let mut circ = QuantumCircuit::new(2);
circ.h(0).cnot(0, 1);
let config = PipelineConfig {
shots: 1024,
verify: true,
seed: 42,
..PipelineConfig::default()
};
let result = Pipeline::execute(&circ, &config).unwrap();
assert!(
result.total_probability > 0.99,
"total_probability should be ~1.0, got {}",
result.total_probability
);
assert_eq!(result.decomposition.num_segments, 1);
assert_eq!(result.estimated_fidelity, 1.0);
}
#[test]
fn test_pipeline_disjoint_bells() {
let mut circ = QuantumCircuit::new(4);
circ.h(0).cnot(0, 1);
circ.h(2).cnot(2, 3);
let config = PipelineConfig::default();
let result = Pipeline::execute(&circ, &config).unwrap();
assert!(
result.decomposition.num_segments >= 2,
"expected >= 2 segments for disjoint Bell pairs, got {}",
result.decomposition.num_segments
);
assert!(
result.total_probability > 0.95,
"total_probability should be ~1.0, got {}",
result.total_probability
);
assert!(
result.estimated_fidelity > 0.90,
"fidelity should be > 0.90, got {}",
result.estimated_fidelity
);
}
#[test]
fn test_pipeline_single_qubit() {
let mut circ = QuantumCircuit::new(1);
circ.h(0);
let config = PipelineConfig {
verify: false,
..PipelineConfig::default()
};
let result = Pipeline::execute(&circ, &config).unwrap();
assert!(
result.total_probability > 0.99,
"total_probability should be ~1.0, got {}",
result.total_probability
);
assert!(result.verification.is_none());
}
#[test]
fn test_pipeline_ghz_state() {
let mut circ = QuantumCircuit::new(5);
circ.h(0);
for i in 0..4u32 {
circ.cnot(i, i + 1);
}
let config = PipelineConfig {
shots: 2048,
seed: 123,
..PipelineConfig::default()
};
let result = Pipeline::execute(&circ, &config).unwrap();
assert!(
result.total_probability > 0.99,
"total_probability should be ~1.0, got {}",
result.total_probability
);
let all_false = vec![false; 5];
let all_true = vec![true; 5];
let p_all_false = result
.distribution
.get(&all_false)
.copied()
.unwrap_or(0.0);
let p_all_true = result
.distribution
.get(&all_true)
.copied()
.unwrap_or(0.0);
assert!(
p_all_false > 0.3,
"GHZ should have significant |00000>, got {}",
p_all_false
);
assert!(
p_all_true > 0.3,
"GHZ should have significant |11111>, got {}",
p_all_true
);
}
#[test]
fn test_pipeline_config_default() {
let config = PipelineConfig::default();
assert_eq!(config.max_segment_qubits, 25);
assert_eq!(config.shots, 1024);
assert!(config.verify);
assert_eq!(config.seed, 42);
}
#[test]
fn test_pipeline_with_verification() {
let mut circ = QuantumCircuit::new(3);
circ.h(0).cnot(0, 1).cnot(1, 2);
let config = PipelineConfig {
verify: true,
shots: 512,
..PipelineConfig::default()
};
let result = Pipeline::execute(&circ, &config).unwrap();
assert!(
result.verification.is_some(),
"verification should be present when verify=true"
);
}
#[test]
fn test_resolve_backend() {
assert_eq!(
resolve_backend(BackendType::Auto),
BackendType::StateVector
);
assert_eq!(
resolve_backend(BackendType::StateVector),
BackendType::StateVector
);
assert_eq!(
resolve_backend(BackendType::Stabilizer),
BackendType::Stabilizer
);
assert_eq!(
resolve_backend(BackendType::TensorNetwork),
BackendType::TensorNetwork
);
assert_eq!(
resolve_backend(BackendType::CliffordT),
BackendType::StateVector
);
}
#[test]
fn test_estimate_fidelity_single_segment() {
let segments = vec![SegmentResult {
index: 0,
backend: BackendType::StateVector,
num_qubits: 5,
distribution: vec![(vec![false; 5], 1.0)],
}];
let partition = CircuitPartition {
segments: vec![],
total_qubits: 5,
strategy: DecompositionStrategy::None,
};
assert_eq!(
estimate_pipeline_fidelity(&segments, &partition),
1.0
);
}
#[test]
fn test_estimate_fidelity_two_spatial_segments() {
let segments = vec![
SegmentResult {
index: 0,
backend: BackendType::StateVector,
num_qubits: 2,
distribution: vec![
(vec![false, false], 0.5),
(vec![true, true], 0.5),
],
},
SegmentResult {
index: 1,
backend: BackendType::StateVector,
num_qubits: 2,
distribution: vec![
(vec![false, false], 0.5),
(vec![true, true], 0.5),
],
},
];
let partition = CircuitPartition {
segments: vec![],
total_qubits: 4,
strategy: DecompositionStrategy::Spatial,
};
let fidelity = estimate_pipeline_fidelity(&segments, &partition);
assert!(
(fidelity - 0.95).abs() < 1e-10,
"expected fidelity 0.95, got {}",
fidelity
);
}
#[test]
fn test_estimate_fidelity_temporal() {
let segments = vec![
SegmentResult {
index: 0,
backend: BackendType::StateVector,
num_qubits: 2,
distribution: vec![(vec![false, false], 1.0)],
},
SegmentResult {
index: 1,
backend: BackendType::StateVector,
num_qubits: 2,
distribution: vec![(vec![false, false], 1.0)],
},
];
let partition = CircuitPartition {
segments: vec![],
total_qubits: 2,
strategy: DecompositionStrategy::Temporal,
};
let fidelity = estimate_pipeline_fidelity(&segments, &partition);
assert!(
(fidelity - 0.99).abs() < 1e-10,
"expected fidelity 0.99, got {}",
fidelity
);
}
#[test]
fn test_counts_to_distribution_empty() {
let counts: HashMap<Vec<bool>, usize> = HashMap::new();
let dist = counts_to_distribution(&counts);
assert!(dist.is_empty());
}
#[test]
fn test_counts_to_distribution_uniform() {
let mut counts: HashMap<Vec<bool>, usize> = HashMap::new();
counts.insert(vec![false], 500);
counts.insert(vec![true], 500);
let dist = counts_to_distribution(&counts);
assert_eq!(dist.len(), 2);
let total_prob: f64 = dist.iter().map(|(_, p)| p).sum();
assert!(
(total_prob - 1.0).abs() < 1e-10,
"distribution should sum to 1.0, got {}",
total_prob
);
}
#[test]
fn test_pipeline_no_verification_large_qubit() {
let mut circ = QuantumCircuit::new(26);
circ.h(0);
let config = PipelineConfig {
verify: true,
shots: 64,
..PipelineConfig::default()
};
let result = Pipeline::execute(&circ, &config).unwrap();
assert!(
result.verification.is_none(),
"verification should be skipped for > 25 qubits"
);
}
#[test]
fn test_pipeline_preserves_plan() {
let mut circ = QuantumCircuit::new(3);
circ.h(0).cnot(0, 1).cnot(1, 2);
let config = PipelineConfig::default();
let result = Pipeline::execute(&circ, &config).unwrap();
assert!(
!result.plan.explanation.is_empty(),
"plan should have a non-empty explanation"
);
}
#[test]
fn test_pipeline_segment_results_match_decomposition() {
let mut circ = QuantumCircuit::new(4);
circ.h(0).cnot(0, 1);
circ.h(2).cnot(2, 3);
let config = PipelineConfig::default();
let result = Pipeline::execute(&circ, &config).unwrap();
assert_eq!(
result.segment_results.len(),
result.decomposition.num_segments,
"segment_results count should match decomposition num_segments"
);
}
}