#[cfg(test)]
use binar::{Bitwise, vec::AlignedBitVec};
#[cfg(test)]
use paulimer::{
Clifford, CliffordUnitary, DensePauli, Pauli, PauliBinaryOps,
clifford::{MutablePreImages, PreimageViews},
};
#[inline]
fn logical_words(n: usize) -> usize {
n.div_ceil(64).max(1)
}
#[inline]
fn words_for(n: usize) -> usize {
match logical_words(n) {
1 => 1,
2 => 2,
3 | 4 => 4,
5..=8 => 8,
wide => wide,
}
}
#[inline(always)]
const fn width<const W: usize>(dynamic: usize) -> usize {
if W == 0 { dynamic } else { W }
}
macro_rules! by_width {
($words:expr, $w:ident => $body:expr) => {
match $words {
1 => {
const $w: usize = 1;
$body
}
2 => {
const $w: usize = 2;
$body
}
4 => {
const $w: usize = 4;
$body
}
8 => {
const $w: usize = 8;
$body
}
_ => {
const $w: usize = 0;
$body
}
}
};
}
#[inline]
#[cfg(target_arch = "x86_64")]
pub(crate) fn has_popcnt() -> bool {
std::arch::is_x86_feature_detected!("popcnt")
}
#[inline]
const fn px(qubit: usize) -> usize {
2 * qubit
}
#[inline]
const fn pz(qubit: usize) -> usize {
2 * qubit + 1
}
#[inline]
fn dot_parity<const W: usize>(a: &[u64], b: &[u64]) -> bool {
debug_assert_eq!(a.len(), b.len());
let words = width::<W>(a.len());
let mut acc = 0u64;
for (&x, &y) in a[..words].iter().zip(&b[..words]) {
acc ^= x & y;
}
acc.count_ones() & 1 == 1
}
#[inline]
fn xor_assign<const W: usize>(dst: &mut [u64], src: &[u64]) {
debug_assert_eq!(dst.len(), src.len());
let words = width::<W>(dst.len());
for (d, &s) in dst[..words].iter_mut().zip(&src[..words]) {
*d ^= s;
}
}
#[inline]
fn mul_right<const W: usize>(
dst_x: &mut [u64],
dst_z: &mut [u64],
dst_phase: &mut u8,
src_x: &[u64],
src_z: &[u64],
src_phase: u8,
) {
let cross = u8::from(dot_parity::<W>(dst_z, src_x)) << 1;
xor_assign::<W>(dst_x, src_x);
xor_assign::<W>(dst_z, src_z);
*dst_phase = (*dst_phase + cross + src_phase) & 3;
}
#[inline]
fn mul_left<const W: usize>(
dst_x: &mut [u64],
dst_z: &mut [u64],
dst_phase: &mut u8,
src_x: &[u64],
src_z: &[u64],
src_phase: u8,
) {
let cross = u8::from(dot_parity::<W>(dst_x, src_z)) << 1;
xor_assign::<W>(dst_x, src_x);
xor_assign::<W>(dst_z, src_z);
*dst_phase = (*dst_phase + cross + src_phase) & 3;
}
#[inline]
fn mul_row_right<const W: usize>(dst: &mut [u64], dst_phase: &mut u8, src: &[u64], src_phase: u8) {
let words = width::<W>(dst.len() / 2);
let (dst_x, dst_z) = dst.split_at_mut(words);
let (src_x, src_z) = src.split_at(words);
mul_right::<W>(dst_x, dst_z, dst_phase, src_x, src_z, src_phase);
}
#[inline]
fn mul_row_left<const W: usize>(dst: &mut [u64], dst_phase: &mut u8, src: &[u64], src_phase: u8) {
let words = width::<W>(dst.len() / 2);
let (dst_x, dst_z) = dst.split_at_mut(words);
let (src_x, src_z) = src.split_at(words);
mul_left::<W>(dst_x, dst_z, dst_phase, src_x, src_z, src_phase);
}
#[inline]
fn rows2_mut(
data: &mut [u64],
stride: usize,
first: usize,
second: usize,
) -> (&mut [u64], &mut [u64]) {
debug_assert_ne!(first, second);
if first < second {
let (head, tail) = data.split_at_mut(second * stride);
(&mut head[first * stride..][..stride], &mut tail[..stride])
} else {
let (head, tail) = data.split_at_mut(first * stride);
(&mut tail[..stride], &mut head[second * stride..][..stride])
}
}
#[inline]
fn set_bits(mask: &[u64]) -> impl Iterator<Item = usize> + '_ {
mask.iter().enumerate().flat_map(|(word_index, &word)| {
let mut rest = word;
std::iter::from_fn(move || {
(rest != 0).then(|| {
let bit = rest.trailing_zeros() as usize;
rest &= rest - 1;
word_index * 64 + bit
})
})
})
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct PauliWords<'a> {
pub(crate) x: &'a [u64],
pub(crate) z: &'a [u64],
}
impl PauliWords<'_> {
#[inline]
fn xz_phase_exponent(self) -> u8 {
let y_sites: u32 = self
.x
.iter()
.zip(self.z)
.map(|(&x_word, &z_word)| (x_word & z_word).count_ones())
.sum();
(y_sites & 3) as u8
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Axis {
X,
Y,
Z,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct RowPauli {
pub(crate) x: Vec<u64>,
pub(crate) z: Vec<u64>,
pub(crate) phase: u8,
}
#[cfg(test)]
impl RowPauli {
#[allow(dead_code)] pub(crate) fn to_dense(&self) -> DensePauli {
DensePauli::from_bits(
AlignedBitVec::from_words(&self.x),
AlignedBitVec::from_words(&self.z),
self.phase,
)
}
}
#[derive(Clone, Debug)]
pub(crate) struct Frame {
n: usize,
words: usize,
data: Vec<u64>,
phases: Vec<u8>,
scratch: Vec<u64>,
scratch_phases: Vec<u8>,
}
impl PartialEq for Frame {
fn eq(&self, other: &Self) -> bool {
self.n == other.n && self.data == other.data && self.phases == other.phases
}
}
impl Eq for Frame {}
impl Frame {
pub(crate) fn identity(n: usize) -> Self {
let words = words_for(n);
let mut frame = Frame {
n,
words,
data: vec![0; 4 * n * words],
phases: vec![0; 2 * n],
scratch: vec![0; 4 * words],
scratch_phases: vec![0; 2],
};
for qubit in 0..n {
frame.set_identity_rows(qubit);
}
frame
}
pub(crate) fn num_qubits(&self) -> usize {
self.n
}
pub(crate) fn words(&self) -> usize {
self.words
}
#[inline]
fn stride(&self) -> usize {
2 * self.words
}
#[inline(always)]
fn stride_w<const W: usize>(&self) -> usize {
2 * width::<W>(self.words)
}
#[inline]
fn row(&self, index: usize) -> &[u64] {
let stride = self.stride();
&self.data[index * stride..][..stride]
}
#[inline]
fn x_bit(&self, row: usize, index: usize) -> bool {
(self.data[row * self.stride() + (index >> 6)] >> (index & 63)) & 1 == 1
}
#[inline]
fn z_bit(&self, row: usize, index: usize) -> bool {
(self.data[row * self.stride() + self.words + (index >> 6)] >> (index & 63)) & 1 == 1
}
fn set_identity_rows(&mut self, qubit: usize) {
let stride = self.stride();
let (word, bit) = (qubit >> 6, 1u64 << (qubit & 63));
self.data[px(qubit) * stride + word] = bit;
self.data[pz(qubit) * stride + self.words + word] = bit;
self.phases[px(qubit)] = 0;
self.phases[pz(qubit)] = 0;
}
pub(crate) fn resize(&mut self, new_n: usize) {
match new_n.cmp(&self.n) {
std::cmp::Ordering::Equal => (),
std::cmp::Ordering::Greater => self.grow(new_n),
std::cmp::Ordering::Less => self.shrink(new_n),
}
}
fn restride(&self, rows: usize, new_words: usize, live: usize) -> Vec<u64> {
debug_assert!(live <= new_words && live <= self.words);
let new_stride = 2 * new_words;
let old_stride = self.stride();
let mut data = vec![0u64; rows * new_stride];
for row in 0..rows {
let src = &self.data[row * old_stride..][..old_stride];
let dst = &mut data[row * new_stride..][..new_stride];
dst[..live].copy_from_slice(&src[..live]);
dst[new_words..][..live].copy_from_slice(&src[self.words..][..live]);
}
data
}
fn grow(&mut self, new_n: usize) {
let new_words = words_for(new_n);
let live = logical_words(self.n);
self.data = self.restride(2 * self.n, new_words, live);
self.data.resize(4 * new_n * new_words, 0);
self.phases.resize(2 * new_n, 0);
self.words = new_words;
let old_n = std::mem::replace(&mut self.n, new_n);
for qubit in old_n..new_n {
self.set_identity_rows(qubit);
}
self.reset_scratch();
}
fn shrink(&mut self, new_n: usize) {
for qubit in new_n..self.n {
assert!(
self.is_basis_pair(qubit),
"cannot drop qubit {qubit}: its preimages are not X/Z"
);
}
let new_words = words_for(new_n);
let live = logical_words(new_n);
self.data = self.restride(2 * new_n, new_words, live);
self.phases.truncate(2 * new_n);
self.words = new_words;
self.n = new_n;
self.mask_tail();
self.reset_scratch();
}
fn mask_tail(&mut self) {
let tail = self.n & 63;
if tail == 0 {
return;
}
let mask = (1u64 << tail) - 1;
let (words, live) = (self.words, logical_words(self.n));
for row in self.data.chunks_exact_mut(2 * words) {
debug_assert_eq!(row[live - 1] & !mask, 0);
debug_assert_eq!(row[words + live - 1] & !mask, 0);
row[live - 1] &= mask;
row[words + live - 1] &= mask;
}
}
fn is_basis_pair(&self, qubit: usize) -> bool {
if self.phases[px(qubit)] != 0 || self.phases[pz(qubit)] != 0 {
return false;
}
let (word, bit) = (qubit >> 6, 1u64 << (qubit & 63));
let is_unit = |row: &[u64], unit_word: usize| {
row.iter()
.enumerate()
.all(|(i, &w)| w == if i == unit_word { bit } else { 0 })
};
is_unit(self.row(px(qubit)), word) && is_unit(self.row(pz(qubit)), self.words + word)
}
fn reset_scratch(&mut self) {
self.scratch.clear();
self.scratch.resize(4 * self.words, 0);
self.scratch_phases.clear();
self.scratch_phases.resize(2, 0);
}
pub(crate) fn left_h(&mut self, qubit: usize) {
let stride = self.stride();
let Frame { data, phases, .. } = self;
let (x_row, z_row) = rows2_mut(data, stride, px(qubit), pz(qubit));
x_row.swap_with_slice(z_row);
phases.swap(px(qubit), pz(qubit));
}
pub(crate) fn left_s(&mut self, qubit: usize) {
self.left_root_z(qubit, 1);
}
pub(crate) fn left_s_dag(&mut self, qubit: usize) {
self.left_root_z(qubit, 3);
}
pub(crate) fn left_sqrt_x(&mut self, qubit: usize) {
self.left_root_x(qubit, 1);
}
pub(crate) fn left_sqrt_x_dag(&mut self, qubit: usize) {
self.left_root_x(qubit, 3);
}
pub(crate) fn left_sqrt_y(&mut self, qubit: usize) {
self.left_z(qubit);
self.left_h(qubit);
}
pub(crate) fn left_sqrt_y_dag(&mut self, qubit: usize) {
self.left_h(qubit);
self.left_z(qubit);
}
fn left_root_z(&mut self, qubit: usize, delta: u8) {
let stride = self.stride();
let Frame { data, phases, .. } = self;
let src_phase = phases[pz(qubit)];
let (x_row, z_row) = rows2_mut(data, stride, px(qubit), pz(qubit));
let dst_phase = &mut phases[px(qubit)];
mul_row_left::<0>(x_row, dst_phase, z_row, src_phase);
*dst_phase = (*dst_phase + delta) & 3;
}
fn left_root_x(&mut self, qubit: usize, delta: u8) {
let stride = self.stride();
let Frame { data, phases, .. } = self;
let src_phase = phases[px(qubit)];
let (z_row, x_row) = rows2_mut(data, stride, pz(qubit), px(qubit));
let dst_phase = &mut phases[pz(qubit)];
mul_row_left::<0>(z_row, dst_phase, x_row, src_phase);
*dst_phase = (*dst_phase + delta) & 3;
}
pub(crate) fn left_x(&mut self, qubit: usize) {
self.phases[pz(qubit)] ^= 2;
}
pub(crate) fn left_y(&mut self, qubit: usize) {
self.phases[px(qubit)] ^= 2;
self.phases[pz(qubit)] ^= 2;
}
pub(crate) fn left_z(&mut self, qubit: usize) {
self.phases[px(qubit)] ^= 2;
}
pub(crate) fn left_cx(&mut self, control: usize, target: usize) {
assert_ne!(control, target, "cx needs distinct qubits");
self.left_mul_row_by_row::<0>(px(control), px(target));
self.left_mul_row_by_row::<0>(pz(target), pz(control));
}
pub(crate) fn left_cz(&mut self, first: usize, second: usize) {
assert_ne!(first, second, "cz needs distinct qubits");
self.left_mul_row_by_row::<0>(px(first), pz(second));
self.left_mul_row_by_row::<0>(px(second), pz(first));
}
pub(crate) fn left_swap(&mut self, first: usize, second: usize) {
if first == second {
return;
}
let stride = self.stride();
let Frame { data, phases, .. } = self;
let (a, b) = rows2_mut(data, stride, px(first), px(second));
a.swap_with_slice(b);
let (a, b) = rows2_mut(data, stride, pz(first), pz(second));
a.swap_with_slice(b);
phases.swap(px(first), px(second));
phases.swap(pz(first), pz(second));
}
fn left_mul_row_by_row<const W: usize>(&mut self, dst: usize, src: usize) {
let stride = self.stride_w::<W>();
let Frame { data, phases, .. } = self;
let src_phase = phases[src];
let (dst_row, src_row) = rows2_mut(data, stride, dst, src);
mul_row_left::<W>(dst_row, &mut phases[dst], src_row, src_phase);
}
pub(crate) fn left_pauli(&mut self, pauli: PauliWords<'_>) {
let phases = &mut self.phases;
for qubit in set_bits(pauli.x) {
phases[pz(qubit)] ^= 2;
}
for qubit in set_bits(pauli.z) {
phases[px(qubit)] ^= 2;
}
}
pub(crate) fn left_controlled_pauli(
&mut self,
control: PauliWords<'_>,
target: PauliWords<'_>,
) {
by_width!(self.words, W => self.left_controlled_pauli_w::<W>(control, target));
}
fn left_controlled_pauli_w<const W: usize>(
&mut self,
control: PauliWords<'_>,
target: PauliWords<'_>,
) {
let stride = self.stride_w::<W>();
let mut scratch = std::mem::take(&mut self.scratch);
let mut phases = std::mem::take(&mut self.scratch_phases);
{
let (target_row, rest) = scratch.split_at_mut(stride);
let control_row = &mut rest[..stride];
phases[0] = self.preimage_row::<W>(target, target_row);
phases[1] = self.preimage_row::<W>(control, control_row);
}
let (target_row, rest) = scratch.split_at(stride);
let control_row = &rest[..stride];
for qubit in set_bits(control.x) {
self.mul_row_right_by::<W>(pz(qubit), target_row, phases[0]);
}
for qubit in set_bits(control.z) {
self.mul_row_right_by::<W>(px(qubit), target_row, phases[0]);
}
for qubit in set_bits(target.x) {
self.mul_row_left_by::<W>(pz(qubit), control_row, phases[1]);
}
for qubit in set_bits(target.z) {
self.mul_row_left_by::<W>(px(qubit), control_row, phases[1]);
}
self.scratch = scratch;
self.scratch_phases = phases;
}
#[cfg(test)]
pub(crate) fn left_clifford(&mut self, cl: &CliffordUnitary, support: &[usize]) {
assert_eq!(
support.len(),
cl.num_qubits(),
"support width must match the Clifford's"
);
by_width!(self.words, W => self.left_clifford_w::<W>(cl, support));
}
#[cfg(test)]
fn left_clifford_w<const W: usize>(&mut self, cl: &CliffordUnitary, support: &[usize]) {
let k = cl.num_qubits();
let stride = self.stride_w::<W>();
let mut scratch = std::mem::take(&mut self.scratch);
let mut phases = std::mem::take(&mut self.scratch_phases);
if scratch.len() < 2 * k * stride {
scratch.resize(2 * k * stride, 0);
phases.resize(2 * k, 0);
}
for slot in 0..k {
let x_preimage = cl.preimage_x_view(slot);
let z_preimage = cl.preimage_z_view(slot);
let (head, tail) = scratch.split_at_mut((2 * slot + 1) * stride);
phases[2 * slot] = self.remapped_preimage_row::<W>(
x_preimage.x_bits(),
x_preimage.z_bits(),
x_preimage.xz_phase_exponent(),
support,
&mut head[2 * slot * stride..],
);
phases[2 * slot + 1] = self.remapped_preimage_row::<W>(
z_preimage.x_bits(),
z_preimage.z_bits(),
z_preimage.xz_phase_exponent(),
support,
&mut tail[..stride],
);
}
for (slot, &qubit) in support.iter().enumerate() {
self.assign_row(
px(qubit),
&scratch[2 * slot * stride..][..stride],
phases[2 * slot],
);
self.assign_row(
pz(qubit),
&scratch[(2 * slot + 1) * stride..][..stride],
phases[2 * slot + 1],
);
}
self.scratch = scratch;
self.scratch_phases = phases;
}
#[cfg(test)]
fn remapped_preimage_row<const W: usize>(
&self,
x_bits: &impl Bitwise,
z_bits: &impl Bitwise,
pauli_phase: u8,
support: &[usize],
out: &mut [u64],
) -> u8 {
let words = width::<W>(self.words);
let (out_x, out_z) = out[..2 * words].split_at_mut(words);
out_x.fill(0);
out_z.fill(0);
let mut phase = 0u8;
for (local, &qubit) in support.iter().enumerate() {
if x_bits.index(local) {
self.accumulate::<W>(px(qubit), out_x, out_z, &mut phase);
}
}
for (local, &qubit) in support.iter().enumerate() {
if z_bits.index(local) {
self.accumulate::<W>(pz(qubit), out_x, out_z, &mut phase);
}
}
(phase + pauli_phase) & 3
}
#[cfg(test)]
fn assign_row(&mut self, row: usize, src: &[u64], phase: u8) {
let stride = self.stride();
self.data[row * stride..][..stride].copy_from_slice(src);
self.phases[row] = phase;
}
fn mul_row_right_by<const W: usize>(&mut self, row: usize, src: &[u64], src_phase: u8) {
let stride = self.stride_w::<W>();
let Frame { data, phases, .. } = self;
mul_row_right::<W>(
&mut data[row * stride..][..stride],
&mut phases[row],
src,
src_phase,
);
}
fn mul_row_left_by<const W: usize>(&mut self, row: usize, src: &[u64], src_phase: u8) {
let stride = self.stride_w::<W>();
let Frame { data, phases, .. } = self;
mul_row_left::<W>(
&mut data[row * stride..][..stride],
&mut phases[row],
src,
src_phase,
);
}
#[inline]
fn accumulate<const W: usize>(
&self,
row: usize,
out_x: &mut [u64],
out_z: &mut [u64],
out_phase: &mut u8,
) {
let words = width::<W>(self.words);
let stride = 2 * words;
let (src_x, src_z) = self.data[row * stride..][..stride].split_at(words);
mul_right::<W>(out_x, out_z, out_phase, src_x, src_z, self.phases[row]);
}
pub(crate) fn preimage_into(
&self,
pauli: PauliWords<'_>,
out_x: &mut [u64],
out_z: &mut [u64],
) -> u8 {
assert_eq!(
out_x.len(),
self.words,
"output width must match the frame's"
);
assert_eq!(
out_z.len(),
self.words,
"output width must match the frame's"
);
by_width!(self.words, W => self.preimage_into_w::<W>(pauli, out_x, out_z))
}
fn preimage_into_w<const W: usize>(
&self,
pauli: PauliWords<'_>,
out_x: &mut [u64],
out_z: &mut [u64],
) -> u8 {
out_x.fill(0);
out_z.fill(0);
let mut phase = 0u8;
for qubit in set_bits(pauli.x) {
self.accumulate::<W>(px(qubit), out_x, out_z, &mut phase);
}
for qubit in set_bits(pauli.z) {
self.accumulate::<W>(pz(qubit), out_x, out_z, &mut phase);
}
(phase + pauli.xz_phase_exponent()) & 3
}
pub(crate) fn preimage_basis_into(
&self,
axis: Axis,
qubit: usize,
out_x: &mut [u64],
out_z: &mut [u64],
) -> u8 {
assert!(
qubit < self.n,
"qubit {qubit} out of range for {} qubits",
self.n
);
assert_eq!(
out_x.len(),
self.words,
"output width must match the frame's"
);
assert_eq!(
out_z.len(),
self.words,
"output width must match the frame's"
);
by_width!(self.words, W => self.preimage_basis_into_w::<W>(axis, qubit, out_x, out_z))
}
fn preimage_basis_into_w<const W: usize>(
&self,
axis: Axis,
qubit: usize,
out_x: &mut [u64],
out_z: &mut [u64],
) -> u8 {
let words = width::<W>(self.words);
let row = if axis == Axis::Z {
pz(qubit)
} else {
px(qubit)
};
let (src_x, src_z) = self.data[row * 2 * words..][..2 * words].split_at(words);
out_x.copy_from_slice(src_x);
out_z.copy_from_slice(src_z);
let mut phase = self.phases[row];
if axis == Axis::Y {
self.accumulate::<W>(pz(qubit), out_x, out_z, &mut phase);
phase += 1;
}
phase & 3
}
fn preimage_row<const W: usize>(&self, pauli: PauliWords<'_>, out: &mut [u64]) -> u8 {
let words = width::<W>(self.words);
let (out_x, out_z) = out[..2 * words].split_at_mut(words);
self.preimage_into_w::<W>(pauli, out_x, out_z)
}
pub(crate) fn right_pauli_exp(&mut self, g_x: &[u64], g_z: &[u64], g_phase: u8) {
assert_eq!(g_x.len(), self.words, "mask width must match the frame's");
assert_eq!(g_z.len(), self.words, "mask width must match the frame's");
by_width!(self.words, W => self.right_pauli_exp_w::<W>(g_x, g_z, g_phase));
}
fn right_pauli_exp_w<const W: usize>(&mut self, g_x: &[u64], g_z: &[u64], g_phase: u8) {
let words = width::<W>(self.words);
let Frame { data, phases, .. } = self;
for (row, phase) in data.chunks_exact_mut(2 * words).zip(phases.iter_mut()) {
let (row_x, row_z) = row.split_at_mut(words);
let cross = dot_parity::<W>(row_z, g_x);
if cross != dot_parity::<W>(row_x, g_z) {
xor_assign::<W>(row_x, g_x);
xor_assign::<W>(row_z, g_z);
*phase = (*phase + (u8::from(cross) << 1) + g_phase + 1) & 3;
}
}
}
pub(crate) fn right_pauli_z(&mut self, pivot: usize) {
assert!(
pivot < self.n,
"pivot {pivot} out of range for {} qubits",
self.n
);
by_width!(self.words, W => self.right_pauli_z_w::<W>(pivot));
}
fn right_pauli_z_w<const W: usize>(&mut self, pivot: usize) {
let (word, bit) = (pivot >> 6, 1u64 << (pivot & 63));
let words = width::<W>(self.words);
let Frame { data, phases, .. } = self;
for (row, phase) in data.chunks_exact(2 * words).zip(phases.iter_mut()) {
if row[word] & bit != 0 {
*phase ^= 2;
}
}
}
#[allow(dead_code)]
pub(crate) fn preimage_x(&self, qubit: usize) -> RowPauli {
self.dense_row(px(qubit))
}
#[allow(dead_code)]
pub(crate) fn preimage_z(&self, qubit: usize) -> RowPauli {
self.dense_row(pz(qubit))
}
fn dense_row(&self, row: usize) -> RowPauli {
let live = logical_words(self.n);
let (x_bits, z_bits) = self.row(row).split_at(self.words);
RowPauli {
x: x_bits[..live].to_vec(),
z: z_bits[..live].to_vec(),
phase: self.phases[row],
}
}
pub(crate) fn image_x(&self, qubit: usize) -> RowPauli {
self.image(qubit, |frame, row, index| frame.z_bit(row, index))
}
pub(crate) fn image_z(&self, qubit: usize) -> RowPauli {
self.image(qubit, |frame, row, index| frame.x_bit(row, index))
}
fn image(&self, qubit: usize, read: impl Fn(&Self, usize, usize) -> bool) -> RowPauli {
let live = logical_words(self.n);
let mut x_bits = vec![0u64; live];
let mut z_bits = vec![0u64; live];
for j in 0..self.n {
let (word, bit) = (j >> 6, 1u64 << (j & 63));
if read(self, pz(j), qubit) {
x_bits[word] |= bit;
}
if read(self, px(j), qubit) {
z_bits[word] |= bit;
}
}
let mut scratch_x = vec![0u64; self.words];
let mut scratch_z = vec![0u64; self.words];
let mut phase = 0u8;
for j in set_bits(&x_bits) {
self.accumulate::<0>(px(j), &mut scratch_x, &mut scratch_z, &mut phase);
}
for j in set_bits(&z_bits) {
self.accumulate::<0>(pz(j), &mut scratch_x, &mut scratch_z, &mut phase);
}
RowPauli {
x: x_bits,
z: z_bits,
phase: (4 - phase) & 3,
}
}
#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn to_clifford_unitary(&self) -> CliffordUnitary {
let mut clifford = CliffordUnitary::identity(self.n);
for qubit in 0..self.n {
clifford
.preimage_x_view_mut(qubit)
.assign(&self.preimage_x(qubit).to_dense());
clifford
.preimage_z_view_mut(qubit)
.assign(&self.preimage_z(qubit).to_dense());
}
clifford
}
#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn from_clifford_unitary(clifford: &CliffordUnitary) -> Self {
let mut frame = Frame::identity(clifford.num_qubits());
for qubit in 0..frame.n {
frame.assign_dense_row(px(qubit), &clifford.preimage_x(qubit));
frame.assign_dense_row(pz(qubit), &clifford.preimage_z(qubit));
}
frame
}
#[cfg(test)]
#[allow(dead_code)]
fn assign_dense_row(&mut self, row: usize, pauli: &DensePauli) {
let words = self.words;
let stride = self.stride();
let live = logical_words(self.n);
let target = &mut self.data[row * stride..][..stride];
target.fill(0);
target[..live].copy_from_slice(&pauli.x_bits().as_words()[..live]);
target[words..][..live].copy_from_slice(&pauli.z_bits().as_words()[..live]);
self.phases[row] = pauli.xz_phase_exponent() & 3;
}
}