use num_complex::Complex64;
use smallvec::SmallVec;
use super::Instruction;
use crate::gates::Gate;
use crate::gates::spectral::unitary_eigen;
const NEGLIGIBLE: f64 = 1e-12;
pub(crate) fn dense_unitary(matrix: &[Complex64], targets: &[usize]) -> Vec<Instruction> {
match targets.len() {
1 => vec![instruction(
Gate::Fused(Box::new([[matrix[0], matrix[1]], [matrix[2], matrix[3]]])),
targets,
)],
2 => {
let mut block = [[Complex64::new(0.0, 0.0); 4]; 4];
for (row, entries) in block.iter_mut().enumerate() {
entries.copy_from_slice(&matrix[row * 4..row * 4 + 4]);
}
vec![instruction(Gate::Fused2q(Box::new(block)), targets)]
}
_ => two_level_sequence(matrix, targets),
}
}
pub(crate) fn controlled_dense(
matrix: &[Complex64],
controls: &[usize],
targets: &[usize],
) -> Vec<Instruction> {
debug_assert!(!controls.is_empty(), "a controlled gate needs a control");
let dim = 1usize << targets.len();
let (values, vectors) = unitary_eigen(&transpose(matrix, dim), dim);
let basis = from_columns(&vectors, dim);
let mut out = dense_unitary(&adjoint(&basis, dim), targets);
out.extend(controlled_diagonal(&values, controls, targets));
out.extend(dense_unitary(&basis, targets));
out
}
pub(crate) const MAX_COMPOSED_QUBITS: usize = 4;
pub(crate) fn expansion_matrix(instrs: &[Instruction]) -> Option<(Vec<usize>, Vec<Complex64>)> {
let mut span: Vec<usize> = Vec::new();
for instr in instrs {
let Instruction::Gate { targets, .. } = instr else {
return None;
};
for &qubit in targets {
if !span.contains(&qubit) {
span.push(qubit);
}
}
}
span.sort_unstable();
if span.is_empty() || span.len() > MAX_COMPOSED_QUBITS {
return None;
}
let dim = 1usize << span.len();
let mut composed = vec![Complex64::new(0.0, 0.0); dim * dim];
for index in 0..dim {
composed[index * dim + index] = Complex64::new(1.0, 0.0);
}
for instr in instrs {
let Instruction::Gate { gate, targets } = instr else {
return None;
};
let block = gate.dense_matrix()?;
let lifted = embed(&block, targets, &span);
composed = multiply(&lifted, &composed, dim);
}
Some((span, composed))
}
fn embed(block: &[Complex64], targets: &[usize], span: &[usize]) -> Vec<Complex64> {
let width = span.len();
let dim = 1usize << width;
let inner = targets.len();
let positions: Vec<usize> = targets
.iter()
.map(|target| span.iter().position(|q| q == target).expect("in span"))
.collect();
let named = positions
.iter()
.fold(0usize, |mask, &at| mask | 1 << (width - 1 - at));
let local = |index: usize| {
positions
.iter()
.enumerate()
.fold(0usize, |acc, (bit, &at)| {
acc | (index >> (width - 1 - at) & 1) << (inner - 1 - bit)
})
};
let mut out = vec![Complex64::new(0.0, 0.0); dim * dim];
for row in 0..dim {
for column in 0..dim {
if row & !named != column & !named {
continue;
}
out[row * dim + column] = block[local(row) * (1 << inner) + local(column)];
}
}
out
}
fn multiply(left: &[Complex64], right: &[Complex64], dim: usize) -> Vec<Complex64> {
let mut out = vec![Complex64::new(0.0, 0.0); dim * dim];
for row in 0..dim {
for inner in 0..dim {
let scale = left[row * dim + inner];
if scale.norm() <= NEGLIGIBLE {
continue;
}
for column in 0..dim {
out[row * dim + column] += scale * right[inner * dim + column];
}
}
}
out
}
fn controlled_diagonal(
values: &[Complex64],
controls: &[usize],
targets: &[usize],
) -> Vec<Instruction> {
let width = targets.len();
let mut out = Vec::new();
for (state, value) in values.iter().enumerate() {
let angle = value.arg();
if (value - Complex64::new(1.0, 0.0)).norm() <= NEGLIGIBLE {
continue;
}
let low: Vec<usize> = targets
.iter()
.enumerate()
.filter(|(index, _)| state >> (width - 1 - index) & 1 == 0)
.map(|(_, &qubit)| qubit)
.collect();
for &qubit in &low {
out.push(instruction(Gate::X, &[qubit]));
}
let mut wires: Vec<usize> = controls.to_vec();
wires.extend_from_slice(&targets[..width - 1]);
let num_controls = wires.len();
wires.push(targets[width - 1]);
out.push(instruction(
Gate::mcu(Gate::P(angle).matrix_2x2(), num_controls as u8),
&wires,
));
for &qubit in &low {
out.push(instruction(Gate::X, &[qubit]));
}
}
out
}
fn two_level_sequence(matrix: &[Complex64], targets: &[usize]) -> Vec<Instruction> {
let width = targets.len();
let dim = 1usize << width;
let mut work = matrix.to_vec();
let order: Vec<usize> = (0..dim).map(|index| index ^ (index >> 1)).collect();
let mut rotations: Vec<(usize, usize, [[Complex64; 2]; 2])> = Vec::new();
for column in 0..dim - 1 {
let pivot = order[column];
for position in (column + 1..dim).rev() {
let (upper, lower) = (order[position - 1], order[position]);
let above = work[upper * dim + pivot];
let below = work[lower * dim + pivot];
if below.norm() <= NEGLIGIBLE {
continue;
}
let scale = (above.norm_sqr() + below.norm_sqr()).sqrt();
let block = [
[above.conj() / scale, below.conj() / scale],
[-below / scale, above / scale],
];
for index in 0..dim {
let left = work[upper * dim + index];
let right = work[lower * dim + index];
work[upper * dim + index] = block[0][0] * left + block[0][1] * right;
work[lower * dim + index] = block[1][0] * left + block[1][1] * right;
}
rotations.push((upper, lower, block));
}
}
let diagonal: Vec<Complex64> = (0..dim).map(|index| work[index * dim + index]).collect();
let mut out = diagonal_phases(&diagonal, targets);
for (upper, lower, block) in rotations.into_iter().rev() {
let adjoint = [
[block[0][0].conj(), block[1][0].conj()],
[block[0][1].conj(), block[1][1].conj()],
];
out.extend(two_level_instruction(upper, lower, &adjoint, targets));
}
out
}
fn two_level_instruction(
upper: usize,
lower: usize,
block: &[[Complex64; 2]; 2],
targets: &[usize],
) -> Vec<Instruction> {
let width = targets.len();
let differing = upper ^ lower;
debug_assert!(
differing.count_ones() == 1,
"gray-code neighbours differ in one bit"
);
let position = differing.trailing_zeros() as usize;
let moved = targets[width - 1 - position];
let oriented = if upper >> position & 1 == 0 {
*block
} else {
[[block[1][1], block[1][0]], [block[0][1], block[0][0]]]
};
if (oriented[0][0] - Complex64::new(1.0, 0.0)).norm() <= NEGLIGIBLE
&& (oriented[1][1] - Complex64::new(1.0, 0.0)).norm() <= NEGLIGIBLE
&& oriented[0][1].norm() <= NEGLIGIBLE
&& oriented[1][0].norm() <= NEGLIGIBLE
{
return Vec::new();
}
let mut held = Vec::with_capacity(width - 1);
let mut low = Vec::new();
for (index, &qubit) in targets.iter().enumerate() {
let bit = width - 1 - index;
if bit == position {
continue;
}
held.push(qubit);
if upper >> bit & 1 == 0 {
low.push(qubit);
}
}
let mut out = Vec::new();
for &qubit in &low {
out.push(instruction(Gate::X, &[qubit]));
}
let mut wires = held.clone();
wires.push(moved);
out.push(instruction(Gate::mcu(oriented, held.len() as u8), &wires));
for &qubit in &low {
out.push(instruction(Gate::X, &[qubit]));
}
out
}
fn diagonal_phases(values: &[Complex64], targets: &[usize]) -> Vec<Instruction> {
let width = targets.len();
let mut out = Vec::new();
for (state, value) in values.iter().enumerate() {
if (value - Complex64::new(1.0, 0.0)).norm() <= NEGLIGIBLE {
continue;
}
let low: Vec<usize> = targets
.iter()
.enumerate()
.filter(|(index, _)| state >> (width - 1 - index) & 1 == 0)
.map(|(_, &qubit)| qubit)
.collect();
for &qubit in &low {
out.push(instruction(Gate::X, &[qubit]));
}
out.push(instruction(
Gate::mcu(Gate::P(value.arg()).matrix_2x2(), (width - 1) as u8),
targets,
));
for &qubit in &low {
out.push(instruction(Gate::X, &[qubit]));
}
}
out
}
fn instruction(gate: Gate, targets: &[usize]) -> Instruction {
Instruction::Gate {
gate,
targets: SmallVec::from_slice(targets),
}
}
fn transpose(matrix: &[Complex64], dim: usize) -> Vec<Complex64> {
let mut out = vec![Complex64::new(0.0, 0.0); dim * dim];
for row in 0..dim {
for column in 0..dim {
out[column * dim + row] = matrix[row * dim + column];
}
}
out
}
fn from_columns(vectors: &[Complex64], dim: usize) -> Vec<Complex64> {
let mut out = vec![Complex64::new(0.0, 0.0); dim * dim];
for column in 0..dim {
for row in 0..dim {
out[row * dim + column] = vectors[column * dim + row];
}
}
out
}
fn adjoint(matrix: &[Complex64], dim: usize) -> Vec<Complex64> {
let mut out = vec![Complex64::new(0.0, 0.0); dim * dim];
for row in 0..dim {
for column in 0..dim {
out[row * dim + column] = matrix[column * dim + row].conj();
}
}
out
}
#[cfg(test)]
#[path = "synthesis_tests.rs"]
mod tests;