use std::collections::{HashMap, HashSet, VecDeque};
use crate::backend::BackendType;
use crate::circuit::QuantumCircuit;
use crate::gate::Gate;
use crate::stabilizer::StabilizerState;
#[derive(Debug, Clone)]
pub struct CircuitPartition {
pub segments: Vec<CircuitSegment>,
pub total_qubits: u32,
pub strategy: DecompositionStrategy,
}
#[derive(Debug, Clone)]
pub struct CircuitSegment {
pub circuit: QuantumCircuit,
pub backend: BackendType,
pub qubit_range: (u32, u32),
pub gate_range: (usize, usize),
pub estimated_cost: SegmentCost,
}
#[derive(Debug, Clone)]
pub struct SegmentCost {
pub memory_bytes: u64,
pub estimated_flops: u64,
pub qubit_count: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecompositionStrategy {
Temporal,
Spatial,
Hybrid,
None,
}
#[derive(Debug, Clone)]
pub struct InteractionGraph {
pub num_qubits: u32,
pub edges: Vec<(u32, u32, usize)>,
pub adjacency: Vec<Vec<u32>>,
}
pub fn build_interaction_graph(circuit: &QuantumCircuit) -> InteractionGraph {
let n = circuit.num_qubits();
let mut edge_counts: HashMap<(u32, u32), usize> = HashMap::new();
for gate in circuit.gates() {
let qubits = gate.qubits();
if qubits.len() == 2 {
let (a, b) = if qubits[0] <= qubits[1] {
(qubits[0], qubits[1])
} else {
(qubits[1], qubits[0])
};
*edge_counts.entry((a, b)).or_insert(0) += 1;
}
}
let mut adjacency: Vec<Vec<u32>> = vec![Vec::new(); n as usize];
let mut edges: Vec<(u32, u32, usize)> = Vec::with_capacity(edge_counts.len());
for (&(a, b), &count) in &edge_counts {
edges.push((a, b, count));
if !adjacency[a as usize].contains(&b) {
adjacency[a as usize].push(b);
}
if !adjacency[b as usize].contains(&a) {
adjacency[b as usize].push(a);
}
}
for adj in &mut adjacency {
adj.sort_unstable();
}
InteractionGraph {
num_qubits: n,
edges,
adjacency,
}
}
pub fn find_connected_components(graph: &InteractionGraph) -> Vec<Vec<u32>> {
let n = graph.num_qubits as usize;
let mut visited = vec![false; n];
let mut components: Vec<Vec<u32>> = Vec::new();
for start in 0..n {
if visited[start] {
continue;
}
visited[start] = true;
let mut component = vec![start as u32];
let mut queue = VecDeque::new();
queue.push_back(start as u32);
while let Some(node) = queue.pop_front() {
for &neighbor in &graph.adjacency[node as usize] {
if !visited[neighbor as usize] {
visited[neighbor as usize] = true;
component.push(neighbor);
queue.push_back(neighbor);
}
}
}
component.sort_unstable();
components.push(component);
}
components
}
pub fn temporal_decomposition(circuit: &QuantumCircuit) -> Vec<QuantumCircuit> {
let gates = circuit.gates();
if gates.is_empty() {
return vec![QuantumCircuit::new(circuit.num_qubits())];
}
let n = circuit.num_qubits();
let mut slices: Vec<QuantumCircuit> = Vec::new();
let mut current = QuantumCircuit::new(n);
let mut current_has_gates = false;
let mut active_qubits: HashSet<u32> = HashSet::new();
let mut measured_qubits: HashSet<u32> = HashSet::new();
for gate in gates {
match gate {
Gate::Barrier => {
if current_has_gates {
slices.push(current);
current = QuantumCircuit::new(n);
current_has_gates = false;
active_qubits.clear();
measured_qubits.clear();
}
}
_ => {
let qubits = gate.qubits();
if current_has_gates
&& !active_qubits.is_empty()
&& active_qubits.iter().all(|q| measured_qubits.contains(q))
{
slices.push(current);
current = QuantumCircuit::new(n);
active_qubits.clear();
measured_qubits.clear();
}
match gate {
Gate::Measure(q) => {
measured_qubits.insert(*q);
}
Gate::Reset(q) => {
measured_qubits.insert(*q);
}
_ => {}
}
for &q in &qubits {
active_qubits.insert(q);
}
current.add_gate(gate.clone());
current_has_gates = true;
}
}
}
if current_has_gates {
slices.push(current);
}
if slices.is_empty() {
slices.push(QuantumCircuit::new(n));
}
slices
}
#[derive(Debug, Clone)]
pub struct MinCutResult {
pub cut_value: usize,
pub partition_a: Vec<u32>,
pub partition_b: Vec<u32>,
}
pub fn stoer_wagner_mincut(graph: &InteractionGraph) -> Option<MinCutResult> {
let n = graph.num_qubits as usize;
if n <= 1 {
return None;
}
let mut adj = vec![vec![0usize; n]; n];
for &(a, b, w) in &graph.edges {
let (a, b) = (a as usize, b as usize);
adj[a][b] += w;
adj[b][a] += w;
}
let mut merged: Vec<Vec<u32>> = (0..n).map(|i| vec![i as u32]).collect();
let mut active: Vec<bool> = vec![true; n];
let mut best_cut_value = usize::MAX;
let mut best_partition: Vec<u32> = Vec::new();
for _ in 0..(n - 1) {
let active_nodes: Vec<usize> = (0..n).filter(|&i| active[i]).collect();
if active_nodes.len() < 2 {
break;
}
let mut in_a = vec![false; n];
let mut weight_to_a = vec![0usize; n];
let start = active_nodes[0];
in_a[start] = true;
for &node in &active_nodes {
if node != start {
weight_to_a[node] = adj[start][node];
}
}
let mut prev = start;
let mut last = start;
for _ in 1..active_nodes.len() {
let next = active_nodes
.iter()
.filter(|&&v| !in_a[v])
.max_by_key(|&&v| weight_to_a[v])
.copied()
.unwrap();
prev = last;
last = next;
in_a[next] = true;
for &node in &active_nodes {
if !in_a[node] {
weight_to_a[node] += adj[next][node];
}
}
}
let cut_of_phase = weight_to_a[last];
if cut_of_phase < best_cut_value {
best_cut_value = cut_of_phase;
best_partition = merged[last].clone();
}
for &node in &active_nodes {
if node != last && node != prev {
adj[prev][node] += adj[last][node];
adj[node][prev] += adj[node][last];
}
}
active[last] = false;
let last_merged = std::mem::take(&mut merged[last]);
merged[prev].extend(last_merged);
}
let partition_a_set: HashSet<u32> = best_partition.iter().copied().collect();
let mut partition_a: Vec<u32> = best_partition;
partition_a.sort_unstable();
let mut partition_b: Vec<u32> = (0..n as u32)
.filter(|q| !partition_a_set.contains(q))
.collect();
partition_b.sort_unstable();
Some(MinCutResult {
cut_value: best_cut_value,
partition_a,
partition_b,
})
}
pub fn spatial_decomposition_mincut(
circuit: &QuantumCircuit,
graph: &InteractionGraph,
max_qubits: u32,
) -> Vec<(Vec<u32>, QuantumCircuit)> {
let n = graph.num_qubits;
if n == 0 || max_qubits == 0 {
return Vec::new();
}
if n <= max_qubits {
let all_qubits: Vec<u32> = (0..n).collect();
return vec![(all_qubits, circuit.clone())];
}
let mut result = Vec::new();
recursive_mincut_partition(circuit, graph, max_qubits, &mut result);
result
}
fn recursive_mincut_partition(
circuit: &QuantumCircuit,
graph: &InteractionGraph,
max_qubits: u32,
result: &mut Vec<(Vec<u32>, QuantumCircuit)>,
) {
let n = graph.num_qubits;
if n <= max_qubits {
let all_qubits: Vec<u32> = (0..n).collect();
result.push((all_qubits, circuit.clone()));
return;
}
match stoer_wagner_mincut(graph) {
Some(cut) => {
let set_a: HashSet<u32> = cut.partition_a.iter().copied().collect();
let set_b: HashSet<u32> = cut.partition_b.iter().copied().collect();
let circ_a = extract_component_circuit(circuit, &set_a);
let circ_b = extract_component_circuit(circuit, &set_b);
let graph_a = build_interaction_graph(&circ_a);
let graph_b = build_interaction_graph(&circ_b);
if cut.partition_a.len() as u32 > max_qubits {
recursive_mincut_partition(&circ_a, &graph_a, max_qubits, result);
} else {
result.push((cut.partition_a, circ_a));
}
if cut.partition_b.len() as u32 > max_qubits {
recursive_mincut_partition(&circ_b, &graph_b, max_qubits, result);
} else {
result.push((cut.partition_b, circ_b));
}
}
None => {
let all_qubits: Vec<u32> = (0..n).collect();
result.push((all_qubits, circuit.clone()));
}
}
}
pub fn spatial_decomposition(
circuit: &QuantumCircuit,
graph: &InteractionGraph,
max_qubits: u32,
) -> Vec<(Vec<u32>, QuantumCircuit)> {
let n = graph.num_qubits;
if n == 0 || max_qubits == 0 {
return Vec::new();
}
if n <= max_qubits {
let all_qubits: Vec<u32> = (0..n).collect();
return vec![(all_qubits, circuit.clone())];
}
let mut degree: Vec<usize> = vec![0; n as usize];
for &(a, b, count) in &graph.edges {
degree[a as usize] += count;
degree[b as usize] += count;
}
let mut assigned = vec![false; n as usize];
let mut groups: Vec<Vec<u32>> = Vec::new();
while assigned.iter().any(|&a| !a) {
let seed = (0..n as usize)
.filter(|&q| !assigned[q])
.max_by_key(|&q| degree[q])
.unwrap() as u32;
let mut group = vec![seed];
assigned[seed as usize] = true;
while (group.len() as u32) < max_qubits {
let mut best_candidate: Option<u32> = Option::None;
let mut best_score: usize = 0;
for &member in &group {
for &neighbor in &graph.adjacency[member as usize] {
if assigned[neighbor as usize] {
continue;
}
let score: usize = graph
.adjacency[neighbor as usize]
.iter()
.filter(|&&adj| group.contains(&adj))
.count();
if score > best_score
|| (score == best_score
&& best_candidate.map_or(true, |bc| neighbor < bc))
{
best_score = score;
best_candidate = Some(neighbor);
}
}
}
match best_candidate {
Some(candidate) => {
assigned[candidate as usize] = true;
group.push(candidate);
}
Option::None => break, }
}
group.sort_unstable();
groups.push(group);
}
let mut result: Vec<(Vec<u32>, QuantumCircuit)> = Vec::new();
let mut qubit_to_group: Vec<usize> = vec![0; n as usize];
for (gi, group) in groups.iter().enumerate() {
for &q in group {
qubit_to_group[q as usize] = gi;
}
}
for group in &groups {
let group_set: HashSet<u32> = group.iter().copied().collect();
let mut local_qubits: Vec<u32> = group.clone();
for gate in circuit.gates() {
let gate_qubits = gate.qubits();
if gate_qubits.is_empty() {
continue;
}
let in_group = gate_qubits.iter().filter(|q| group_set.contains(q)).count();
let out_group = gate_qubits.len() - in_group;
if in_group > 0 && out_group > 0 {
if in_group >= out_group {
for &q in &gate_qubits {
if !local_qubits.contains(&q) {
local_qubits.push(q);
}
}
}
}
}
local_qubits.sort_unstable();
let num_local = local_qubits.len() as u32;
let remap: HashMap<u32, u32> = local_qubits
.iter()
.enumerate()
.map(|(i, &q)| (q, i as u32))
.collect();
let mut sub_circuit = QuantumCircuit::new(num_local);
for gate in circuit.gates() {
let gate_qubits = gate.qubits();
if matches!(gate, Gate::Barrier) {
sub_circuit.add_gate(Gate::Barrier);
continue;
}
if gate_qubits.is_empty() {
continue;
}
let in_group = gate_qubits.iter().filter(|q| group_set.contains(q)).count();
if in_group == 0 {
continue; }
let out_group = gate_qubits.len() - in_group;
if out_group > 0 && in_group < out_group {
continue; }
if gate_qubits.iter().all(|q| remap.contains_key(q)) {
let remapped = remap_gate(gate, &remap);
sub_circuit.add_gate(remapped);
}
}
result.push((group.clone(), sub_circuit));
}
result
}
fn remap_gate(gate: &Gate, remap: &HashMap<u32, u32>) -> Gate {
match gate {
Gate::H(q) => Gate::H(remap[q]),
Gate::X(q) => Gate::X(remap[q]),
Gate::Y(q) => Gate::Y(remap[q]),
Gate::Z(q) => Gate::Z(remap[q]),
Gate::S(q) => Gate::S(remap[q]),
Gate::Sdg(q) => Gate::Sdg(remap[q]),
Gate::T(q) => Gate::T(remap[q]),
Gate::Tdg(q) => Gate::Tdg(remap[q]),
Gate::Rx(q, a) => Gate::Rx(remap[q], *a),
Gate::Ry(q, a) => Gate::Ry(remap[q], *a),
Gate::Rz(q, a) => Gate::Rz(remap[q], *a),
Gate::Phase(q, a) => Gate::Phase(remap[q], *a),
Gate::CNOT(c, t) => Gate::CNOT(remap[c], remap[t]),
Gate::CZ(a, b) => Gate::CZ(remap[a], remap[b]),
Gate::SWAP(a, b) => Gate::SWAP(remap[a], remap[b]),
Gate::Rzz(a, b, angle) => Gate::Rzz(remap[a], remap[b], *angle),
Gate::Measure(q) => Gate::Measure(remap[q]),
Gate::Reset(q) => Gate::Reset(remap[q]),
Gate::Barrier => Gate::Barrier,
Gate::Unitary1Q(q, m) => Gate::Unitary1Q(remap[q], *m),
}
}
pub fn classify_segment(segment: &QuantumCircuit) -> BackendType {
let mut has_non_clifford = false;
let mut t_count: usize = 0;
for gate in segment.gates() {
if gate.is_non_unitary() {
continue;
}
if !StabilizerState::is_clifford_gate(gate) {
has_non_clifford = true;
t_count += 1;
}
}
if !has_non_clifford {
return BackendType::Stabilizer;
}
if segment.num_qubits() <= 25 {
return BackendType::StateVector;
}
if t_count <= 40 {
return BackendType::CliffordT;
}
BackendType::TensorNetwork
}
pub fn estimate_segment_cost(segment: &QuantumCircuit, backend: BackendType) -> SegmentCost {
let n = segment.num_qubits();
let gate_count = segment.gate_count() as u64;
match backend {
BackendType::StateVector => {
let state_size = if n <= 63 { 1u64 << n } else { u64::MAX / 16 };
let memory_bytes = state_size.saturating_mul(16);
let flops_per_gate = if n <= 60 {
8u64.saturating_mul(1u64 << n)
} else {
u64::MAX / gate_count.max(1)
};
let estimated_flops = gate_count.saturating_mul(flops_per_gate);
SegmentCost {
memory_bytes,
estimated_flops,
qubit_count: n,
}
}
BackendType::Stabilizer => {
let tableau_size = 2 * (n as u64) * (2 * (n as u64) + 1);
let memory_bytes = tableau_size; let flops_per_gate = 4 * (n as u64) * (n as u64);
let estimated_flops = gate_count.saturating_mul(flops_per_gate);
SegmentCost {
memory_bytes,
estimated_flops,
qubit_count: n,
}
}
BackendType::TensorNetwork => {
let chi: u64 = 64;
let tensor_bytes = (n as u64) * chi * chi * 16; let memory_bytes = tensor_bytes;
let flops_per_gate = chi * chi * chi;
let estimated_flops = gate_count.saturating_mul(flops_per_gate);
SegmentCost {
memory_bytes,
estimated_flops,
qubit_count: n,
}
}
BackendType::CliffordT => {
let analysis = crate::backend::analyze_circuit(segment);
let t = analysis.non_clifford_gates as u32;
let terms: u64 = 1u64.checked_shl(t).unwrap_or(u64::MAX);
let tableau_bytes = (n as u64).saturating_mul(n as u64) / 4;
let memory_bytes = terms.saturating_mul(tableau_bytes).max(1);
let flops_per_gate = 4 * (n as u64) * (n as u64);
let estimated_flops = terms
.saturating_mul(gate_count)
.saturating_mul(flops_per_gate);
SegmentCost {
memory_bytes,
estimated_flops,
qubit_count: n,
}
}
BackendType::Auto => {
let resolved = classify_segment(segment);
estimate_segment_cost(segment, resolved)
}
}
}
pub fn stitch_results(
partitions: &[(Vec<bool>, f64)],
) -> HashMap<Vec<bool>, f64> {
if partitions.is_empty() {
return HashMap::new();
}
let mut segments: Vec<Vec<(Vec<bool>, f64)>> = Vec::new();
let mut current_segment: Vec<(Vec<bool>, f64)> = Vec::new();
let mut current_len: Option<usize> = Option::None;
for (bits, prob) in partitions {
match current_len {
Some(l) if l == bits.len() => {
current_segment.push((bits.clone(), *prob));
}
_ => {
if !current_segment.is_empty() {
segments.push(current_segment);
current_segment = Vec::new();
}
current_len = Some(bits.len());
current_segment.push((bits.clone(), *prob));
}
}
}
if !current_segment.is_empty() {
segments.push(current_segment);
}
let mut combined: Vec<(Vec<bool>, f64)> = vec![(Vec::new(), 1.0)];
for segment in &segments {
let mut next_combined: Vec<(Vec<bool>, f64)> = Vec::new();
for (base_bits, base_prob) in &combined {
for (seg_bits, seg_prob) in segment {
let mut merged = base_bits.clone();
merged.extend_from_slice(seg_bits);
next_combined.push((merged, base_prob * seg_prob));
}
}
combined = next_combined;
}
let mut result: HashMap<Vec<bool>, f64> = HashMap::new();
for (bits, prob) in combined {
*result.entry(bits).or_insert(0.0) += prob;
}
result
}
#[derive(Debug, Clone)]
pub struct StitchFidelity {
pub fidelity: f64,
pub cut_gates: usize,
pub per_cut_fidelity: Vec<f64>,
}
pub fn stitch_with_fidelity(
partitions: &[(Vec<bool>, f64)],
partition_info: &CircuitPartition,
original_circuit: &QuantumCircuit,
) -> (HashMap<Vec<bool>, f64>, StitchFidelity) {
let distribution = stitch_results(partitions);
let fidelity = estimate_stitch_fidelity(partition_info, original_circuit);
(distribution, fidelity)
}
fn estimate_stitch_fidelity(
partition_info: &CircuitPartition,
original_circuit: &QuantumCircuit,
) -> StitchFidelity {
if partition_info.segments.len() <= 1 {
return StitchFidelity {
fidelity: 1.0,
cut_gates: 0,
per_cut_fidelity: Vec::new(),
};
}
let mut qubit_to_segment: HashMap<u32, usize> = HashMap::new();
for (seg_idx, segment) in partition_info.segments.iter().enumerate() {
let (lo, hi) = segment.qubit_range;
for q in lo..=hi {
qubit_to_segment.entry(q).or_insert(seg_idx);
}
}
let mut boundary_cuts: HashMap<(usize, usize), usize> = HashMap::new();
let mut total_cut_gates = 0usize;
for gate in original_circuit.gates() {
let qubits = gate.qubits();
if qubits.len() != 2 {
continue;
}
let seg_a = qubit_to_segment.get(&qubits[0]).copied();
let seg_b = qubit_to_segment.get(&qubits[1]).copied();
if let (Some(a), Some(b)) = (seg_a, seg_b) {
if a != b {
let key = if a < b { (a, b) } else { (b, a) };
*boundary_cuts.entry(key).or_insert(0) += 1;
total_cut_gates += 1;
}
}
}
let per_cut_fidelity: Vec<f64> = boundary_cuts
.values()
.map(|&k| {
if k == 0 {
1.0
} else {
2.0_f64.powf(-(k as f64) / 2.0)
}
})
.collect();
let overall_fidelity = per_cut_fidelity.iter().product::<f64>();
StitchFidelity {
fidelity: overall_fidelity,
cut_gates: total_cut_gates,
per_cut_fidelity,
}
}
pub fn decompose(circuit: &QuantumCircuit, max_segment_qubits: u32) -> CircuitPartition {
let n = circuit.num_qubits();
let gates = circuit.gates();
if gates.is_empty() || n <= 1 {
let backend = classify_segment(circuit);
let cost = estimate_segment_cost(circuit, backend);
return CircuitPartition {
segments: vec![CircuitSegment {
circuit: circuit.clone(),
backend,
qubit_range: (0, n.saturating_sub(1)),
gate_range: (0, gates.len()),
estimated_cost: cost,
}],
total_qubits: n,
strategy: DecompositionStrategy::None,
};
}
let graph = build_interaction_graph(circuit);
let components = find_connected_components(&graph);
let mut used_spatial = false;
let mut used_temporal = false;
let mut final_segments: Vec<CircuitSegment> = Vec::new();
if components.len() > 1 {
used_spatial = true;
}
for component in &components {
let comp_set: HashSet<u32> = component.iter().copied().collect();
let comp_circuit = extract_component_circuit(circuit, &comp_set);
let gate_indices = gate_indices_for_component(circuit, &comp_set);
let gate_range_start = gate_indices.first().copied().unwrap_or(0);
let _gate_range_end = gate_indices
.last()
.map(|&i| i + 1)
.unwrap_or(0);
let time_slices = temporal_decomposition(&comp_circuit);
if time_slices.len() > 1 {
used_temporal = true;
}
let mut slice_gate_offset = gate_range_start;
for slice_circuit in &time_slices {
let slice_gate_count = slice_circuit.gate_count();
let backend = classify_segment(slice_circuit);
if slice_circuit.num_qubits() > max_segment_qubits
&& active_qubit_count(slice_circuit) > max_segment_qubits
{
used_spatial = true;
let sub_graph = build_interaction_graph(slice_circuit);
let sub_parts =
spatial_decomposition(slice_circuit, &sub_graph, max_segment_qubits);
for (qubit_group, sub_circ) in &sub_parts {
let sub_backend = classify_segment(sub_circ);
let cost = estimate_segment_cost(sub_circ, sub_backend);
let qmin = qubit_group.iter().copied().min().unwrap_or(0);
let qmax = qubit_group.iter().copied().max().unwrap_or(0);
final_segments.push(CircuitSegment {
circuit: sub_circ.clone(),
backend: sub_backend,
qubit_range: (qmin, qmax),
gate_range: (slice_gate_offset, slice_gate_offset + slice_gate_count),
estimated_cost: cost,
});
}
} else {
let cost = estimate_segment_cost(slice_circuit, backend);
let qmin = component.iter().copied().min().unwrap_or(0);
let qmax = component.iter().copied().max().unwrap_or(0);
final_segments.push(CircuitSegment {
circuit: slice_circuit.clone(),
backend,
qubit_range: (qmin, qmax),
gate_range: (slice_gate_offset, slice_gate_offset + slice_gate_count),
estimated_cost: cost,
});
}
slice_gate_offset += slice_gate_count;
}
}
let strategy = match (used_temporal, used_spatial) {
(true, true) => DecompositionStrategy::Hybrid,
(true, false) => DecompositionStrategy::Temporal,
(false, true) => DecompositionStrategy::Spatial,
(false, false) => DecompositionStrategy::None,
};
CircuitPartition {
segments: final_segments,
total_qubits: n,
strategy,
}
}
fn active_qubit_count(circuit: &QuantumCircuit) -> u32 {
let mut active: HashSet<u32> = HashSet::new();
for gate in circuit.gates() {
for &q in &gate.qubits() {
active.insert(q);
}
}
active.len() as u32
}
fn extract_component_circuit(
circuit: &QuantumCircuit,
component: &HashSet<u32>,
) -> QuantumCircuit {
let mut sorted_qubits: Vec<u32> = component.iter().copied().collect();
sorted_qubits.sort_unstable();
let remap: HashMap<u32, u32> = sorted_qubits
.iter()
.enumerate()
.map(|(i, &q)| (q, i as u32))
.collect();
let num_local = sorted_qubits.len() as u32;
let mut sub_circuit = QuantumCircuit::new(num_local);
for gate in circuit.gates() {
match gate {
Gate::Barrier => {
sub_circuit.add_gate(Gate::Barrier);
}
_ => {
let qubits = gate.qubits();
if qubits.is_empty() {
continue;
}
if qubits.iter().all(|q| component.contains(q)) {
sub_circuit.add_gate(remap_gate(gate, &remap));
}
}
}
}
sub_circuit
}
fn gate_indices_for_component(circuit: &QuantumCircuit, component: &HashSet<u32>) -> Vec<usize> {
circuit
.gates()
.iter()
.enumerate()
.filter_map(|(i, gate)| {
let qubits = gate.qubits();
if qubits.is_empty() {
return Some(i); }
if qubits.iter().any(|q| component.contains(q)) {
Some(i)
} else {
Option::None
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn two_bell_pairs() -> QuantumCircuit {
let mut circ = QuantumCircuit::new(4);
circ.h(0).cnot(0, 1); circ.h(2).cnot(2, 3); circ
}
#[test]
fn two_independent_bell_states_decompose_into_two_segments() {
let circ = two_bell_pairs();
let partition = decompose(&circ, 25);
assert_eq!(
partition.segments.len(),
2,
"expected 2 segments for two independent Bell pairs, got {}",
partition.segments.len()
);
assert_eq!(partition.strategy, DecompositionStrategy::Spatial);
for seg in &partition.segments {
assert_eq!(
seg.circuit.num_qubits(),
2,
"each Bell pair segment should have 2 qubits"
);
}
}
#[test]
fn pure_clifford_classified_as_stabilizer() {
let mut circ = QuantumCircuit::new(4);
circ.h(0).cnot(0, 1).s(2).cz(2, 3).x(1).y(3).z(0);
let backend = classify_segment(&circ);
assert_eq!(
backend,
BackendType::Stabilizer,
"all-Clifford circuit should be classified as Stabilizer"
);
}
#[test]
fn temporal_decomposition_splits_at_barriers() {
let mut circ = QuantumCircuit::new(2);
circ.h(0).cnot(0, 1);
circ.barrier();
circ.x(0).z(1);
let slices = temporal_decomposition(&circ);
assert_eq!(
slices.len(),
2,
"expected 2 time slices around barrier, got {}",
slices.len()
);
assert_eq!(slices[0].gate_count(), 2);
assert_eq!(slices[1].gate_count(), 2);
}
#[test]
fn connected_circuit_stays_as_single_segment() {
let mut circ = QuantumCircuit::new(4);
circ.h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3);
let partition = decompose(&circ, 25);
assert_eq!(
partition.segments.len(),
1,
"fully connected circuit should remain a single segment"
);
assert_eq!(partition.strategy, DecompositionStrategy::None);
}
#[test]
fn interaction_graph_counts_edges() {
let mut circ = QuantumCircuit::new(3);
circ.cnot(0, 1); circ.cnot(0, 1); circ.cz(1, 2);
let graph = build_interaction_graph(&circ);
assert_eq!(graph.num_qubits, 3);
assert_eq!(graph.edges.len(), 2, "should have 2 distinct edges");
let edge_01 = graph
.edges
.iter()
.find(|&&(a, b, _)| a == 0 && b == 1);
assert!(edge_01.is_some(), "edge (0,1) should exist");
assert_eq!(edge_01.unwrap().2, 2, "edge (0,1) should have count 2");
let edge_12 = graph
.edges
.iter()
.find(|&&(a, b, _)| a == 1 && b == 2);
assert!(edge_12.is_some(), "edge (1,2) should exist");
assert_eq!(edge_12.unwrap().2, 1, "edge (1,2) should have count 1");
assert!(graph.adjacency[0].contains(&1));
assert!(graph.adjacency[1].contains(&0));
assert!(graph.adjacency[1].contains(&2));
assert!(graph.adjacency[2].contains(&1));
}
#[test]
fn spatial_decomposition_respects_max_qubits() {
let mut circ = QuantumCircuit::new(6);
for q in 0..5 {
circ.cnot(q, q + 1);
}
let graph = build_interaction_graph(&circ);
let parts = spatial_decomposition(&circ, &graph, 3);
for (group, _sub_circ) in &parts {
assert!(
group.len() <= 3,
"group {:?} has {} qubits, expected at most 3",
group,
group.len()
);
}
let mut all_qubits: Vec<u32> = parts
.iter()
.flat_map(|(group, _)| group.iter().copied())
.collect();
all_qubits.sort_unstable();
all_qubits.dedup();
assert_eq!(all_qubits.len(), 6, "all 6 qubits should be covered");
}
#[test]
fn segment_cost_estimation_reasonable() {
let mut circ = QuantumCircuit::new(10);
circ.h(0).cnot(0, 1).t(2);
let sv_cost = estimate_segment_cost(&circ, BackendType::StateVector);
assert_eq!(sv_cost.qubit_count, 10);
assert_eq!(sv_cost.memory_bytes, 16384);
assert!(sv_cost.estimated_flops > 0);
let stab_cost = estimate_segment_cost(&circ, BackendType::Stabilizer);
assert_eq!(stab_cost.qubit_count, 10);
assert_eq!(stab_cost.memory_bytes, 420);
assert!(stab_cost.estimated_flops > 0);
let tn_cost = estimate_segment_cost(&circ, BackendType::TensorNetwork);
assert_eq!(tn_cost.qubit_count, 10);
assert_eq!(tn_cost.memory_bytes, 655_360);
assert!(tn_cost.estimated_flops > 0);
assert!(stab_cost.memory_bytes < sv_cost.memory_bytes);
}
#[test]
fn ghz_10_qubit_single_segment() {
let mut circ = QuantumCircuit::new(10);
circ.h(0);
for q in 0..9 {
circ.cnot(q, q + 1);
}
let partition = decompose(&circ, 25);
assert_eq!(
partition.segments.len(),
1,
"10-qubit GHZ circuit should stay as one segment"
);
assert_eq!(partition.segments[0].backend, BackendType::Stabilizer);
}
#[test]
fn disconnected_20_qubit_circuit_decomposes() {
let mut circ = QuantumCircuit::new(20);
circ.h(0);
for q in 0..9 {
circ.cnot(q, q + 1);
}
circ.h(10);
for q in 10..19 {
circ.cnot(q, q + 1);
}
let partition = decompose(&circ, 25);
assert_eq!(
partition.segments.len(),
2,
"two disconnected 10-qubit blocks should yield 2 segments, got {}",
partition.segments.len()
);
assert_eq!(partition.total_qubits, 20);
assert_eq!(partition.strategy, DecompositionStrategy::Spatial);
for seg in &partition.segments {
assert_eq!(seg.circuit.num_qubits(), 10);
}
}
#[test]
fn empty_circuit_produces_single_segment() {
let circ = QuantumCircuit::new(4);
let partition = decompose(&circ, 25);
assert_eq!(partition.segments.len(), 1);
assert_eq!(partition.strategy, DecompositionStrategy::None);
}
#[test]
fn single_qubit_circuit() {
let mut circ = QuantumCircuit::new(1);
circ.h(0).t(0);
let partition = decompose(&circ, 25);
assert_eq!(partition.segments.len(), 1);
assert_eq!(partition.segments[0].backend, BackendType::StateVector);
}
#[test]
fn mixed_clifford_non_clifford_classification() {
let mut circ = QuantumCircuit::new(5);
circ.h(0).cnot(0, 1).t(2).s(3);
let backend = classify_segment(&circ);
assert_eq!(
backend,
BackendType::StateVector,
"mixed circuit with <= 25 qubits should use StateVector"
);
}
#[test]
fn connected_components_isolated_qubits() {
let mut circ = QuantumCircuit::new(3);
circ.cnot(0, 1).h(2);
let graph = build_interaction_graph(&circ);
let components = find_connected_components(&graph);
assert_eq!(
components.len(),
2,
"qubit 2 is isolated, should form its own component"
);
let has_pair = components.iter().any(|c| c == &vec![0, 1]);
let has_single = components.iter().any(|c| c == &vec![2]);
assert!(has_pair, "component {{0, 1}} should exist");
assert!(has_single, "component {{2}} should exist");
}
#[test]
fn stitch_results_independent_segments() {
let partitions = vec![
(vec![false], 0.5),
(vec![true], 0.5),
(vec![false, false], 0.25),
(vec![true, true], 0.75),
];
let combined = stitch_results(&partitions);
assert_eq!(combined.len(), 4);
let prob_fff = combined.get(&vec![false, false, false]).copied().unwrap_or(0.0);
let prob_ftt = combined.get(&vec![false, true, true]).copied().unwrap_or(0.0);
let prob_tff = combined.get(&vec![true, false, false]).copied().unwrap_or(0.0);
let prob_ttt = combined.get(&vec![true, true, true]).copied().unwrap_or(0.0);
assert!((prob_fff - 0.125).abs() < 1e-10);
assert!((prob_ftt - 0.375).abs() < 1e-10);
assert!((prob_tff - 0.125).abs() < 1e-10);
assert!((prob_ttt - 0.375).abs() < 1e-10);
}
#[test]
fn stitch_results_empty() {
let combined = stitch_results(&[]);
assert!(combined.is_empty());
}
#[test]
fn classify_large_moderate_t_as_clifford_t() {
let mut circ = QuantumCircuit::new(30);
circ.h(0);
circ.t(1); for q in 0..29 {
circ.cnot(q, q + 1);
}
let backend = classify_segment(&circ);
assert_eq!(
backend,
BackendType::CliffordT,
"moderate T-count on > 25 qubits should use CliffordT"
);
}
#[test]
fn classify_large_high_t_as_tensor_network() {
let mut circ = QuantumCircuit::new(30);
for q in 0..29 {
circ.cnot(q, q + 1);
}
for _ in 0..50 {
circ.rx(0, 1.0); }
let backend = classify_segment(&circ);
assert_eq!(
backend,
BackendType::TensorNetwork,
"high T-count on > 25 qubits should use TensorNetwork"
);
}
#[test]
fn temporal_decomposition_no_barriers_single_slice() {
let mut circ = QuantumCircuit::new(2);
circ.h(0).cnot(0, 1);
let slices = temporal_decomposition(&circ);
assert_eq!(
slices.len(),
1,
"circuit without barriers should produce a single time slice"
);
assert_eq!(slices[0].gate_count(), 2);
}
#[test]
fn temporal_decomposition_multiple_barriers() {
let mut circ = QuantumCircuit::new(2);
circ.h(0);
circ.barrier();
circ.cnot(0, 1);
circ.barrier();
circ.x(0);
let slices = temporal_decomposition(&circ);
assert_eq!(
slices.len(),
3,
"two barriers should produce three time slices"
);
}
#[test]
fn cost_auto_backend_resolves() {
let mut circ = QuantumCircuit::new(4);
circ.h(0).cnot(0, 1);
let cost = estimate_segment_cost(&circ, BackendType::Auto);
let stab_cost = estimate_segment_cost(&circ, BackendType::Stabilizer);
assert_eq!(cost.memory_bytes, stab_cost.memory_bytes);
assert_eq!(cost.estimated_flops, stab_cost.estimated_flops);
}
#[test]
fn decompose_with_measurements() {
let mut circ = QuantumCircuit::new(4);
circ.h(0).cnot(0, 1).measure(0).measure(1);
circ.h(2).cnot(2, 3).measure(2).measure(3);
let partition = decompose(&circ, 25);
assert_eq!(partition.segments.len(), 2);
}
#[test]
fn interaction_graph_empty_circuit() {
let circ = QuantumCircuit::new(5);
let graph = build_interaction_graph(&circ);
assert_eq!(graph.num_qubits, 5);
assert!(graph.edges.is_empty());
for adj in &graph.adjacency {
assert!(adj.is_empty());
}
}
#[test]
fn connected_components_fully_connected() {
let mut circ = QuantumCircuit::new(4);
circ.cnot(0, 1).cnot(1, 2).cnot(2, 3);
let graph = build_interaction_graph(&circ);
let components = find_connected_components(&graph);
assert_eq!(
components.len(),
1,
"fully connected chain should be one component"
);
assert_eq!(components[0], vec![0, 1, 2, 3]);
}
#[test]
fn spatial_decomposition_returns_single_group_if_fits() {
let mut circ = QuantumCircuit::new(4);
circ.cnot(0, 1).cnot(2, 3);
let graph = build_interaction_graph(&circ);
let parts = spatial_decomposition(&circ, &graph, 10);
assert_eq!(parts.len(), 1);
assert_eq!(parts[0].0, vec![0, 1, 2, 3]);
}
#[test]
fn segment_qubit_ranges_are_valid() {
let circ = two_bell_pairs();
let partition = decompose(&circ, 25);
for seg in &partition.segments {
let (qmin, qmax) = seg.qubit_range;
assert!(qmin <= qmax, "qubit_range should be non-inverted");
assert!(
qmax < partition.total_qubits,
"qubit_range max should be within total_qubits"
);
}
}
#[test]
fn classify_segment_measure_only() {
let mut circ = QuantumCircuit::new(3);
circ.measure(0).measure(1).measure(2);
let backend = classify_segment(&circ);
assert_eq!(backend, BackendType::Stabilizer);
}
#[test]
fn classify_segment_empty_circuit() {
let circ = QuantumCircuit::new(5);
let backend = classify_segment(&circ);
assert_eq!(
backend,
BackendType::Stabilizer,
"empty circuit has no non-Clifford gates"
);
}
#[test]
fn test_stoer_wagner_mincut_linear() {
let mut circ = QuantumCircuit::new(5);
circ.cnot(0, 1).cnot(1, 2).cnot(2, 3).cnot(3, 4);
let graph = build_interaction_graph(&circ);
let cut = stoer_wagner_mincut(&graph).unwrap();
assert_eq!(cut.cut_value, 1);
assert!(!cut.partition_a.is_empty());
assert!(!cut.partition_b.is_empty());
}
#[test]
fn test_stoer_wagner_mincut_triangle() {
let mut circ = QuantumCircuit::new(3);
circ.cnot(0, 1).cnot(1, 2).cnot(0, 2);
let graph = build_interaction_graph(&circ);
let cut = stoer_wagner_mincut(&graph).unwrap();
assert_eq!(cut.cut_value, 2);
}
#[test]
fn test_stoer_wagner_mincut_barbell() {
let mut circ = QuantumCircuit::new(6);
circ.cnot(0, 1).cnot(1, 2).cnot(0, 2);
circ.cnot(2, 3);
circ.cnot(3, 4).cnot(4, 5).cnot(3, 5);
let graph = build_interaction_graph(&circ);
let cut = stoer_wagner_mincut(&graph).unwrap();
assert_eq!(cut.cut_value, 1);
}
#[test]
fn test_spatial_decomposition_mincut() {
let mut circ = QuantumCircuit::new(6);
circ.cnot(0, 1).cnot(1, 2).cnot(0, 2);
circ.cnot(2, 3);
circ.cnot(3, 4).cnot(4, 5).cnot(3, 5);
let graph = build_interaction_graph(&circ);
let parts = spatial_decomposition_mincut(&circ, &graph, 3);
assert!(parts.len() >= 2, "Should partition into at least 2 groups");
for (qubits, _sub_circ) in &parts {
assert!(qubits.len() as u32 <= 3, "Each group should have at most 3 qubits");
}
}
#[test]
fn test_stitch_with_fidelity_single_segment() {
let circ = QuantumCircuit::new(2);
let partition = CircuitPartition {
segments: vec![CircuitSegment {
circuit: circ.clone(),
backend: BackendType::Stabilizer,
qubit_range: (0, 1),
gate_range: (0, 0),
estimated_cost: SegmentCost {
memory_bytes: 0,
estimated_flops: 0,
qubit_count: 2,
},
}],
total_qubits: 2,
strategy: DecompositionStrategy::None,
};
let partitions = vec![(vec![false, false], 1.0)];
let (dist, fidelity) = stitch_with_fidelity(&partitions, &partition, &circ);
assert_eq!(fidelity.fidelity, 1.0);
assert_eq!(fidelity.cut_gates, 0);
assert!(!dist.is_empty());
}
#[test]
fn test_stitch_with_fidelity_cut_circuit() {
let mut circ = QuantumCircuit::new(4);
circ.h(0).cnot(0, 1); circ.h(2).cnot(2, 3); circ.cnot(1, 2);
let partition = CircuitPartition {
segments: vec![
CircuitSegment {
circuit: {
let mut c = QuantumCircuit::new(2);
c.h(0).cnot(0, 1);
c
},
backend: BackendType::Stabilizer,
qubit_range: (0, 1),
gate_range: (0, 2),
estimated_cost: SegmentCost { memory_bytes: 0, estimated_flops: 0, qubit_count: 2 },
},
CircuitSegment {
circuit: {
let mut c = QuantumCircuit::new(2);
c.h(0).cnot(0, 1);
c
},
backend: BackendType::Stabilizer,
qubit_range: (2, 3),
gate_range: (2, 4),
estimated_cost: SegmentCost { memory_bytes: 0, estimated_flops: 0, qubit_count: 2 },
},
],
total_qubits: 4,
strategy: DecompositionStrategy::Spatial,
};
let partitions = vec![
(vec![false, false], 0.5),
(vec![true, true], 0.5),
(vec![false, false], 0.5),
(vec![true, true], 0.5),
];
let (_dist, fidelity) = stitch_with_fidelity(&partitions, &partition, &circ);
assert!(fidelity.fidelity < 1.0, "Cut circuit should have fidelity < 1.0");
assert!(fidelity.cut_gates >= 1, "Should detect at least 1 cut gate");
}
}