use crate::circuit::{Circuit, CircuitParam, Operation, Qubit};
use std::ops::Range;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct OperationReplacement {
pub(crate) old: Range<usize>,
pub(crate) new: Range<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct QubitBijection {
pub(crate) pairs: Vec<(Qubit, Qubit)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum RewriteEdits {
Linear {
old_len: usize,
new_len: usize,
replacements: Vec<OperationReplacement>,
},
LinearModuloQubitBijection {
old_len: usize,
new_len: usize,
replacements: Vec<OperationReplacement>,
clean_gap_bijections: Vec<QubitBijection>,
},
Unknown,
}
impl RewriteEdits {
pub(crate) fn between_linear_circuits(before: &Circuit, after: &Circuit) -> Self {
if !before.has_same_qubits(after) {
return Self::Unknown;
}
const RESYNC_WINDOW: usize = 64;
const CONFIRMING_RUN: usize = 2;
let old = before.operations();
let new = after.operations();
let mut old_end = old.len();
let mut new_end = new.len();
while old_end > 0
&& new_end > 0
&& operations_are_equivalent(before, &old[old_end - 1], after, &new[new_end - 1])
{
old_end -= 1;
new_end -= 1;
}
let mut replacements = Vec::new();
let mut old_cursor = 0usize;
let mut new_cursor = 0usize;
while old_cursor < old_end && new_cursor < new_end {
if operations_are_equivalent(before, &old[old_cursor], after, &new[new_cursor]) {
old_cursor += 1;
new_cursor += 1;
continue;
}
let old_limit = old_end.min(old_cursor.saturating_add(RESYNC_WINDOW + 1));
let new_limit = new_end.min(new_cursor.saturating_add(RESYNC_WINDOW + 1));
let mut best = None::<(usize, usize, usize)>;
for candidate_old in old_cursor..old_limit {
for candidate_new in new_cursor..new_limit {
if candidate_old == old_cursor && candidate_new == new_cursor {
continue;
}
if !operations_are_equivalent(
before,
&old[candidate_old],
after,
&new[candidate_new],
) {
continue;
}
let available = (old_end - candidate_old).min(new_end - candidate_new);
let required = CONFIRMING_RUN.min(available);
if (0..required).any(|offset| {
!operations_are_equivalent(
before,
&old[candidate_old + offset],
after,
&new[candidate_new + offset],
)
}) {
continue;
}
let score = (candidate_old - old_cursor) + (candidate_new - new_cursor);
if best.is_none_or(|(best_score, best_old, best_new)| {
(score, candidate_old, candidate_new) < (best_score, best_old, best_new)
}) {
best = Some((score, candidate_old, candidate_new));
}
}
}
let Some((_, next_old, next_new)) = best else {
replacements.push(OperationReplacement {
old: old_cursor..old_end,
new: new_cursor..new_end,
});
old_cursor = old_end;
new_cursor = new_end;
break;
};
replacements.push(OperationReplacement {
old: old_cursor..next_old,
new: new_cursor..next_new,
});
old_cursor = next_old;
new_cursor = next_new;
}
if old_cursor < old_end || new_cursor < new_end {
replacements.push(OperationReplacement {
old: old_cursor..old_end,
new: new_cursor..new_end,
});
}
Self::linear(old.len(), new.len(), replacements)
}
pub(crate) fn linear(
old_len: usize,
new_len: usize,
replacements: Vec<OperationReplacement>,
) -> Self {
Self::Linear {
old_len,
new_len,
replacements,
}
}
pub(crate) fn linear_modulo_qubit_bijection(
old_len: usize,
new_len: usize,
replacements: Vec<OperationReplacement>,
clean_gap_bijections: Vec<QubitBijection>,
) -> Self {
Self::LinearModuloQubitBijection {
old_len,
new_len,
replacements,
clean_gap_bijections,
}
}
pub(crate) fn from_route_provenance(old_len: usize, provenance: &[Option<usize>]) -> Self {
Self::from_operation_provenance(old_len, provenance)
}
pub(crate) fn from_operation_provenance(old_len: usize, provenance: &[Option<usize>]) -> Self {
let mut seen = vec![false; old_len];
let mut candidates = Vec::<(usize, usize)>::new();
for (new_order, source_order) in provenance.iter().copied().enumerate() {
let Some(source_order) = source_order else {
continue;
};
if source_order >= old_len || seen[source_order] {
return Self::Unknown;
}
seen[source_order] = true;
candidates.push((source_order, new_order));
}
let kept = longest_increasing_source_subsequence(&candidates);
let mut replacements = Vec::new();
let mut old_cursor = 0usize;
let mut new_cursor = 0usize;
for (old_order, new_order) in kept {
if old_cursor < old_order || new_cursor < new_order {
replacements.push(OperationReplacement {
old: old_cursor..old_order,
new: new_cursor..new_order,
});
}
old_cursor = old_order + 1;
new_cursor = new_order + 1;
}
if old_cursor < old_len || new_cursor < provenance.len() {
replacements.push(OperationReplacement {
old: old_cursor..old_len,
new: new_cursor..provenance.len(),
});
}
Self::linear(old_len, provenance.len(), replacements)
}
}
pub(crate) fn operations_are_equivalent(
before: &Circuit,
source: &Operation,
after: &Circuit,
target: &Operation,
) -> bool {
source.instruction == target.instruction
&& source.label == target.label
&& source.qubits == target.qubits
&& resolved_params_are_equal(before, &source.params, after, &target.params)
}
pub(crate) fn resolved_params_are_equal(
before: &Circuit,
source: &[CircuitParam],
after: &Circuit,
target: &[CircuitParam],
) -> bool {
source.len() == target.len()
&& source.iter().zip(target).all(|(left, right)| {
match (
before.resolve_parameter(left),
after.resolve_parameter(right),
) {
(Ok(left), Ok(right)) => left == right,
_ => false,
}
})
}
fn longest_increasing_source_subsequence(candidates: &[(usize, usize)]) -> Vec<(usize, usize)> {
let mut tails = Vec::<usize>::new();
let mut previous = vec![None; candidates.len()];
for (candidate_index, &(source_order, _)) in candidates.iter().enumerate() {
let position = tails.partition_point(|&tail_index| candidates[tail_index].0 < source_order);
if position > 0 {
previous[candidate_index] = Some(tails[position - 1]);
}
if position == tails.len() {
tails.push(candidate_index);
} else {
tails[position] = candidate_index;
}
}
let Some(mut cursor) = tails.last().copied() else {
return Vec::new();
};
let mut kept = Vec::with_capacity(tails.len());
loop {
kept.push(candidates[cursor]);
let Some(parent) = previous[cursor] else {
break;
};
cursor = parent;
}
kept.reverse();
kept
}
#[cfg(test)]
#[path = "edit_test.rs"]
mod edit_test;