use num_complex::Complex64;
use crate::backend::dense_statevector_len;
use crate::error::Result;
#[inline]
fn i_power(p: u8) -> Complex64 {
match p & 3 {
0 => Complex64::new(1.0, 0.0),
1 => Complex64::new(0.0, 1.0),
2 => Complex64::new(-1.0, 0.0),
_ => Complex64::new(0.0, -1.0),
}
}
#[inline]
fn generator(xz: &[u64], n: usize, nw: usize, i: usize) -> (&[u64], &[u64]) {
let base = (n + i) * 2 * nw;
(&xz[base..base + nw], &xz[base + nw..base + 2 * nw])
}
#[inline]
fn bit(words: &[u64], q: usize) -> bool {
words[q / 64] >> (q % 64) & 1 == 1
}
#[inline]
fn set_bit(words: &mut [u64], column: usize) {
words[column / 64] |= 1 << (column % 64);
}
fn smaller_side(n: usize, subsystem: &[usize]) -> Vec<usize> {
if 2 * subsystem.len() <= n {
return subsystem.to_vec();
}
let mut inside = vec![false; n];
for &q in subsystem {
inside[q] = true;
}
(0..n).filter(|&q| !inside[q]).collect()
}
fn eliminate(rows: &mut [u64], count: usize, words: usize, cols: usize) -> usize {
let mut rank = 0;
for column in 0..cols {
let (word, mask) = (column / 64, 1u64 << (column % 64));
let Some(pivot) = (rank..count).find(|&r| rows[r * words + word] & mask != 0) else {
continue;
};
for w in 0..words {
rows.swap(rank * words + w, pivot * words + w);
}
for r in rank + 1..count {
if rows[r * words + word] & mask == 0 {
continue;
}
for w in 0..words {
rows[r * words + w] ^= rows[rank * words + w];
}
}
rank += 1;
}
rank
}
pub(crate) fn subsystem_rank(xz: &[u64], n: usize, nw: usize, subsystem: &[usize]) -> usize {
let side = smaller_side(n, subsystem);
let k = side.len();
let words = (2 * k).div_ceil(64);
let mut rows = vec![0u64; n * words];
for i in 0..n {
let (x, z) = generator(xz, n, nw, i);
let row = &mut rows[i * words..(i + 1) * words];
for (column, &q) in side.iter().enumerate() {
if bit(x, q) {
set_bit(row, column);
}
if bit(z, q) {
set_bit(row, k + column);
}
}
}
let rank = eliminate(&mut rows, n, words, 2 * k);
debug_assert!(
rank >= k,
"a cut of a pure stabilizer state has rank at least the subsystem size"
);
rank - k
}
pub(crate) fn flat_spectrum(backend: &str, rank: usize) -> Result<Vec<f64>> {
let count = dense_statevector_len(backend, "Schmidt values", rank)?;
Ok(vec![(count as f64).sqrt().recip(); count])
}
#[inline]
fn multiply(a: (usize, usize, u8), b: (usize, usize, u8)) -> (usize, usize, u8) {
let cross = 2 * ((a.1 & b.0).count_ones() as u8 & 1);
(a.0 ^ b.0, a.1 ^ b.1, (a.2 + b.2 + cross) & 3)
}
#[inline]
fn accumulate(rho: &mut [Complex64], dim: usize, (x, z, p): (usize, usize, u8)) {
let coeff = i_power(p);
for y in 0..dim {
let sign = if (z & y).count_ones() & 1 == 0 {
1.0
} else {
-1.0
};
rho[(y ^ x) * dim + y] += coeff * sign;
}
}
fn interior_generators(
xz: &[u64],
phase: &[bool],
n: usize,
nw: usize,
subsystem: &[usize],
) -> Vec<(usize, usize, u8)> {
let stride = 2 * nw;
let mut rows = vec![0u64; n * stride];
let mut pow = vec![0u8; n];
for i in 0..n {
let (x, z) = generator(xz, n, nw, i);
let row = &mut rows[i * stride..(i + 1) * stride];
row[..nw].copy_from_slice(x);
row[nw..].copy_from_slice(z);
let y_count: u32 = x.iter().zip(z).map(|(a, b)| (a & b).count_ones()).sum();
pow[i] = ((2 * u32::from(phase[n + i]) + y_count) & 3) as u8;
}
let mut inside = vec![false; n];
for &q in subsystem {
inside[q] = true;
}
let mut rank = 0;
for q in (0..n).filter(|&q| !inside[q]) {
for half in [0, nw] {
let (word, mask) = (half + q / 64, 1u64 << (q % 64));
let Some(pivot) = (rank..n).find(|&r| rows[r * stride + word] & mask != 0) else {
continue;
};
for w in 0..stride {
rows.swap(rank * stride + w, pivot * stride + w);
}
pow.swap(rank, pivot);
let (head, tail) = rows.split_at_mut((rank + 1) * stride);
let pivot_row = &head[rank * stride..];
for (offset, target) in tail.chunks_exact_mut(stride).enumerate() {
if target[word] & mask == 0 {
continue;
}
let cross: u32 = (0..nw)
.map(|w| (target[nw + w] & pivot_row[w]).count_ones())
.sum();
let r = rank + 1 + offset;
pow[r] = (pow[r] + pow[rank] + 2 * (cross & 1) as u8) & 3;
for (entry, &pivot_word) in target.iter_mut().zip(pivot_row) {
*entry ^= pivot_word;
}
}
rank += 1;
}
}
(rank..n)
.map(|r| {
let row = &rows[r * stride..(r + 1) * stride];
let (mut x, mut z) = (0usize, 0usize);
for (i, &q) in subsystem.iter().enumerate() {
if bit(&row[..nw], q) {
x |= 1 << i;
}
if bit(&row[nw..], q) {
z |= 1 << i;
}
}
(x, z, pow[r])
})
.collect()
}
pub(crate) fn subsystem_density(
xz: &[u64],
phase: &[bool],
n: usize,
nw: usize,
subsystem: &[usize],
dim: usize,
) -> Vec<Complex64> {
let generators = interior_generators(xz, phase, n, nw, subsystem);
let mut rho = vec![Complex64::new(0.0, 0.0); dim * dim];
let mut element = (0usize, 0usize, 0u8);
accumulate(&mut rho, dim, element);
for t in 1..1usize << generators.len() {
element = multiply(element, generators[t.trailing_zeros() as usize]);
accumulate(&mut rho, dim, element);
}
let scale = 1.0 / dim as f64;
for entry in &mut rho {
*entry *= scale;
}
rho
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::Backend;
use crate::backend::stabilizer::StabilizerBackend;
use crate::circuit::Circuit;
use crate::gates::Gate;
fn tableau(circuit: &Circuit) -> StabilizerBackend {
let mut backend = StabilizerBackend::new(42);
crate::sim::run_on(&mut backend, circuit).unwrap();
backend
}
fn density(backend: &StabilizerBackend, subsystem: &[usize]) -> Vec<Complex64> {
let (xz, phase) = backend.raw_tableau();
subsystem_density(
xz,
phase,
backend.num_qubits(),
backend.num_qubits().div_ceil(64),
subsystem,
1 << subsystem.len(),
)
}
fn assert_entries(actual: &[Complex64], expected: &[Complex64]) {
assert_eq!(actual.len(), expected.len());
for (i, (a, e)) in actual.iter().zip(expected).enumerate() {
assert!((a - e).norm() < 1e-15, "entry {i} reads {a} against {e}");
}
}
#[test]
fn a_y_generator_gives_the_plus_i_eigenstate() {
let mut circuit = Circuit::new(1, 0);
circuit.add_gate(Gate::H, &[0]);
circuit.add_gate(Gate::S, &[0]);
let half = Complex64::new(0.5, 0.0);
assert_entries(
&density(&tableau(&circuit), &[0]),
&[
half,
Complex64::new(0.0, -0.5),
Complex64::new(0.0, 0.5),
half,
],
);
}
#[test]
fn a_negative_generator_moves_the_projector() {
let mut circuit = Circuit::new(1, 0);
circuit.add_gate(Gate::X, &[0]);
let (zero, one) = (Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0));
assert_entries(&density(&tableau(&circuit), &[0]), &[zero, zero, zero, one]);
}
#[test]
fn the_whole_register_gives_the_pure_state_projector() {
let mut circuit = Circuit::new(2, 0);
circuit.add_gate(Gate::H, &[0]);
circuit.add_gate(Gate::Cx, &[0, 1]);
let backend = tableau(&circuit);
let (half, zero) = (Complex64::new(0.5, 0.0), Complex64::new(0.0, 0.0));
assert_entries(
&density(&backend, &[0, 1]),
&[
half, zero, zero, half, zero, zero, zero, zero, zero, zero, zero, zero, half, zero,
zero, half,
],
);
let (xz, _) = backend.raw_tableau();
assert_eq!(subsystem_rank(xz, 2, 1, &[0]), 1);
}
}