use std::borrow::Cow;
use std::f64::consts::PI;
use num_complex::Complex64;
use super::{Circuit, Instruction};
use crate::gates::Gate;
const MIN_SUBRANGE_QFT_QUBITS: usize = 10;
const PHASE_EPS: f64 = 1e-12;
pub fn recognize_qft_blocks(circuit: &Circuit) -> Cow<'_, Circuit> {
let insts = &circuit.instructions;
let mut out: Option<Vec<Instruction>> = None;
let mut copied = 0;
let mut i = 0;
while i < insts.len() {
let found = match &insts[i] {
Instruction::Gate { gate: Gate::H, .. } => {
match_forward(insts, i).or_else(|| match_inverse(insts, i))
}
Instruction::Gate {
gate: Gate::Swap, ..
} => match_inverse(insts, i),
_ => None,
};
let Some(m) = found.filter(|m| {
m.start == 0
&& m.num <= u8::MAX as usize
&& (m.num == circuit.num_qubits || m.num >= MIN_SUBRANGE_QFT_QUBITS)
}) else {
i += 1;
continue;
};
let out = out.get_or_insert_with(|| Vec::with_capacity(insts.len()));
out.extend_from_slice(&insts[copied..i]);
out.push(Instruction::Gate {
gate: Gate::QftBlock {
start: m.start as u8,
num: m.num as u8,
inverse: m.inverse,
swaps: m.swaps,
big_endian: m.big_endian,
},
targets: (m.start..m.start + m.num).collect(),
});
i = m.end;
copied = i;
}
match out {
None => Cow::Borrowed(circuit),
Some(mut out) => {
out.extend_from_slice(&insts[copied..]);
Cow::Owned(circuit.with_instructions(out))
}
}
}
struct QftMatch {
end: usize,
start: usize,
num: usize,
inverse: bool,
swaps: bool,
big_endian: bool,
}
#[derive(Clone, Copy)]
enum Op {
H(usize),
Phase(usize, usize, Complex64),
Swap(usize, usize),
Other,
}
fn op(insts: &[Instruction], i: usize) -> Op {
let Some(Instruction::Gate { gate, targets }) = insts.get(i) else {
return Op::Other;
};
match gate {
Gate::H => Op::H(targets[0]),
Gate::Swap => Op::Swap(targets[0], targets[1]),
Gate::Cu(_) => gate
.controlled_phase()
.map_or(Op::Other, |phase| Op::Phase(targets[0], targets[1], phase)),
_ => Op::Other,
}
}
fn partner(a: usize, b: usize, q: usize) -> Option<usize> {
if a == q {
Some(b)
} else if b == q {
Some(a)
} else {
None
}
}
fn phase_matches(phase: Complex64, d: usize, sign: f64) -> bool {
let theta = sign * PI * 0.5f64.powi(d as i32);
(phase - Complex64::from_polar(1.0, theta)).norm() <= PHASE_EPS
}
#[derive(Default)]
struct Distances([u64; 4]);
impl Distances {
fn insert(&mut self, d: usize) -> bool {
if d >= 256 {
return false;
}
let (word, bit) = (d / 64, 1u64 << (d % 64));
let fresh = self.0[word] & bit == 0;
self.0[word] |= bit;
fresh
}
}
fn match_column(
insts: &[Instruction],
mut j: usize,
q: usize,
dir: isize,
len: usize,
sign: f64,
) -> Option<usize> {
let mut seen = Distances::default();
for _ in 0..len {
let Op::Phase(a, b, phase) = op(insts, j) else {
return None;
};
let delta = (partner(a, b, q)? as isize - q as isize) * dir;
if delta < 1 || delta as usize > len {
return None;
}
let d = delta as usize;
if !seen.insert(d) || !phase_matches(phase, d, sign) {
return None;
}
j += 1;
}
Some(j)
}
fn match_swap_set(insts: &[Instruction], mut j: usize, lo: usize, num: usize) -> Option<usize> {
let mirror = 2 * lo + num - 1;
let mut seen = Distances::default();
for _ in 0..num / 2 {
let Op::Swap(a, b) = op(insts, j) else {
return None;
};
let low = a.min(b);
if low < lo || a + b != mirror || !seen.insert(low - lo) {
return None;
}
j += 1;
}
Some(j)
}
fn match_forward(insts: &[Instruction], i: usize) -> Option<QftMatch> {
let Op::H(top) = op(insts, i) else {
return None;
};
let mut j = i + 1;
let mut dir = 0isize;
let mut seen = Distances::default();
let mut widest = 0;
while let Op::Phase(a, b, phase) = op(insts, j) {
let Some(other) = partner(a, b, top) else {
break;
};
let delta = other as isize - top as isize;
if dir == 0 {
dir = delta.signum();
} else if delta.signum() != dir {
break;
}
let d = delta.unsigned_abs();
if !seen.insert(d) || !phase_matches(phase, d, 1.0) {
return None;
}
widest = widest.max(d);
j += 1;
}
let num = j - i;
if num < 2 || widest != num - 1 {
return None;
}
let qubit = |c: usize| (top as isize + dir * c as isize) as usize;
for c in 1..num {
if !matches!(op(insts, j), Op::H(q) if q == qubit(c)) {
return None;
}
j = match_column(insts, j + 1, qubit(c), dir, num - 1 - c, 1.0)?;
}
let start = top.min(qubit(num - 1));
let after_swaps = match_swap_set(insts, j, start, num);
Some(QftMatch {
end: after_swaps.unwrap_or(j),
start,
num,
inverse: false,
swaps: after_swaps.is_some(),
big_endian: dir > 0,
})
}
fn match_inverse(insts: &[Instruction], i: usize) -> Option<QftMatch> {
let mut j = i;
let mut range = None;
if let Op::Swap(a, b) = op(insts, i) {
let mirror = a + b;
let mut lo = a.min(b);
while let Op::Swap(a, b) = op(insts, j + 1) {
if a + b != mirror {
break;
}
lo = lo.min(a.min(b));
j += 1;
}
let num = mirror - 2 * lo + 1;
j = match_swap_set(insts, i, lo, num)?;
range = Some((lo, num));
}
let Op::H(first) = op(insts, j) else {
return None;
};
j += 1;
let dir = match range {
Some((lo, _)) if first == lo => 1,
Some((lo, num)) if first == lo + num - 1 => -1,
Some(_) => return None,
None => {
let Op::Phase(a, b, _) = op(insts, j) else {
return None;
};
match partner(a, b, first)? as isize - first as isize {
1 => 1,
-1 => -1,
_ => return None,
}
}
};
let mut num = 1;
loop {
if range.is_some_and(|(_, width)| num == width) {
break;
}
let q = first as isize + dir * num as isize;
let column = if q < 0 {
None
} else {
match_column(insts, j, q as usize, -dir, num, -1.0)
.filter(|&k| matches!(op(insts, k), Op::H(h) if h == q as usize))
};
match column {
Some(k) => {
j = k + 1;
num += 1;
}
None if range.is_some() => return None,
None => break,
}
}
if num < 2 {
return None;
}
let last = (first as isize + dir * (num as isize - 1)) as usize;
Some(QftMatch {
end: j,
start: first.min(last),
num,
inverse: true,
swaps: range.is_some(),
big_endian: dir < 0,
})
}