use num_complex::Complex64;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use super::statevector::insert_zero_bit;
#[cfg(feature = "parallel")]
use super::{MIN_PAR_ITERS, PARALLEL_THRESHOLD_QUBITS};
use crate::error::Result;
pub(crate) fn reduced_density_side(backend: &str, k: usize) -> Result<usize> {
let width = 2 * k;
if width > super::schmidt::export_cap() {
return Err(super::schmidt::export_cap_exceeded(
backend,
format!("a reduced density matrix on {k} qubits"),
width,
));
}
Ok(1usize << k)
}
pub(crate) fn row_offsets(subsystem: &[usize]) -> Vec<usize> {
(0..1usize << subsystem.len())
.map(|t| {
subsystem
.iter()
.enumerate()
.fold(0, |off, (i, &q)| off | (((t >> i) & 1) << q))
})
.collect()
}
#[inline(always)]
pub(crate) fn traced_base(e: usize, ascending: &[usize]) -> usize {
ascending
.iter()
.fold(e, |base, &q| insert_zero_bit(base, q))
}
pub(crate) fn multiply_block_factor(
rho: &mut [Complex64],
dim: usize,
positions: &[usize],
factor: &[Complex64],
side: usize,
) {
let gather: Vec<usize> = (0..dim)
.map(|t| {
positions
.iter()
.enumerate()
.fold(0, |s, (j, &i)| s | (((t >> i) & 1) << j))
})
.collect();
for (t, &row) in gather.iter().enumerate() {
let out = &mut rho[t * dim..(t + 1) * dim];
for (entry, &col) in out.iter_mut().zip(&gather) {
*entry *= factor[row * side + col];
}
}
}
pub(crate) fn normalize_trace(rho: &mut [Complex64], dim: usize) {
let trace: f64 = (0..dim).map(|t| rho[t * dim + t].re).sum();
let scale = 1.0 / trace;
for entry in rho.iter_mut() {
*entry *= scale;
}
}
#[cfg(feature = "parallel")]
pub(crate) fn fold_min_len(groups: usize, work_per_index: usize) -> usize {
let jobs = 4 * rayon::current_num_threads();
(MIN_PAR_ITERS * 16 / work_per_index)
.max(groups / jobs)
.max(1)
}
#[cfg(feature = "parallel")]
const STRIPE_RATIO: usize = 64;
#[cfg(feature = "parallel")]
pub(crate) fn stripe_rows(dim: usize) -> usize {
(dim / (4 * rayon::current_num_threads())).max(1)
}
#[inline(always)]
fn accumulate_rows(
state: &[Complex64],
offsets: &[usize],
base: usize,
amps: &mut [Complex64],
rows: &mut [Complex64],
first: usize,
) {
let dim = amps.len();
let count = rows.len() / dim;
for (amp, &off) in amps[..first + count].iter_mut().zip(offsets) {
*amp = state[base | off];
}
for (r, row) in rows.chunks_exact_mut(dim).enumerate() {
let t = first + r;
let a = amps[t];
for (entry, b) in row[..=t].iter_mut().zip(&s[..=t]) {
*entry += a * b.conj();
}
}
}
pub(crate) fn dense_reduced_density(
state: &[Complex64],
num_qubits: usize,
subsystem: &[usize],
) -> Vec<Complex64> {
debug_assert_eq!(state.len(), 1usize << num_qubits);
let k = subsystem.len();
let dim = 1usize << k;
let offsets = row_offsets(subsystem);
let mut ascending = subsystem.to_vec();
ascending.sort_unstable();
let groups = state.len() >> k;
let zero = Complex64::new(0.0, 0.0);
let mut rho = vec![zero; dim * dim];
#[cfg(feature = "parallel")]
if num_qubits >= PARALLEL_THRESHOLD_QUBITS {
if groups < STRIPE_RATIO * dim {
let rows = stripe_rows(dim);
rho.par_chunks_mut(rows * dim)
.enumerate()
.for_each(|(stripe, out)| {
let mut amps = vec![zero; dim];
for e in 0..groups {
let base = traced_base(e, &ascending);
accumulate_rows(state, &offsets, base, &mut amps, out, stripe * rows);
}
});
} else {
let fresh = || (vec![zero; dim * dim], vec![zero; dim]);
let accumulate = |(mut acc, mut amps): (Vec<Complex64>, Vec<Complex64>), e: usize| {
let base = traced_base(e, &ascending);
accumulate_rows(state, &offsets, base, &mut amps, &mut acc, 0);
(acc, amps)
};
let add = |(mut a, amps): (Vec<Complex64>, Vec<Complex64>),
(b, _): (Vec<Complex64>, Vec<Complex64>)| {
for (x, y) in a.iter_mut().zip(&b) {
*x += y;
}
(a, amps)
};
rho = (0..groups)
.into_par_iter()
.with_min_len(fold_min_len(groups, dim * (dim + 1) / 2))
.fold(fresh, accumulate)
.reduce(fresh, add)
.0;
}
mirror_lower_triangle(&mut rho, dim);
return rho;
}
let mut amps = vec![zero; dim];
for e in 0..groups {
let base = traced_base(e, &ascending);
accumulate_rows(state, &offsets, base, &mut amps, &mut rho, 0);
}
mirror_lower_triangle(&mut rho, dim);
rho
}
fn mirror_lower_triangle(rho: &mut [Complex64], dim: usize) {
for t in 0..dim {
for tp in 0..t {
rho[tp * dim + t] = rho[t * dim + tp].conj();
}
}
}