use super::data::{StateVisualizationSource, checked_density_len, state_to_density_matrix};
use crate::visualization::VisualizationError;
use num_complex::Complex64;
#[derive(Debug, Clone, Copy)]
pub(crate) enum Pauli {
I,
X,
Y,
Z,
}
pub fn local_bloch_vectors<S: StateVisualizationSource + ?Sized>(
state: &S,
) -> Result<Vec<(usize, [f64; 3])>, VisualizationError> {
let (num_qubits, rho) = state_to_density_matrix(state)?;
let mut vectors = Vec::with_capacity(num_qubits);
for qubit in 0..num_qubits {
let x = pauli_expectation(num_qubits, &rho, &[(qubit, Pauli::X)]).re;
let y = pauli_expectation(num_qubits, &rho, &[(qubit, Pauli::Y)]).re;
let z = pauli_expectation(num_qubits, &rho, &[(qubit, Pauli::Z)]).re;
vectors.push((qubit, clamp_bloch([clean(x), clean(y), clean(z)])));
}
Ok(vectors)
}
pub(crate) fn pauli_coefficients(
num_qubits: usize,
rho: &[Complex64],
reverse_bits: bool,
) -> Result<Vec<(String, f64)>, VisualizationError> {
let total = checked_density_len(num_qubits)?;
if rho.len() != total {
return Err(VisualizationError::InvalidInput(format!(
"density matrix length {} does not match 4^{num_qubits}",
rho.len()
)));
}
let mut out = Vec::with_capacity(total);
for idx in 0..total {
let mut code = idx;
let mut ops = Vec::with_capacity(num_qubits);
for qubit in 0..num_qubits {
let op = match code % 4 {
0 => Pauli::I,
1 => Pauli::X,
2 => Pauli::Y,
_ => Pauli::Z,
};
ops.push((qubit, op));
code /= 4;
}
let display_qubits: Vec<usize> = if reverse_bits {
(0..num_qubits).rev().collect()
} else {
(0..num_qubits).collect()
};
let label = display_qubits
.iter()
.map(|qubit| match ops[*qubit].1 {
Pauli::I => 'I',
Pauli::X => 'X',
Pauli::Y => 'Y',
Pauli::Z => 'Z',
})
.collect();
let value = clean(pauli_expectation(num_qubits, rho, &ops).re);
out.push((label, value));
}
Ok(out)
}
pub(crate) fn pauli_expectation(
num_qubits: usize,
rho: &[Complex64],
ops: &[(usize, Pauli)],
) -> Complex64 {
let dim = 1usize << num_qubits;
let mut result = Complex64::new(0.0, 0.0);
for basis in 0..dim {
let mut mapped = basis;
let mut phase = Complex64::new(1.0, 0.0);
for &(qubit, op) in ops {
let bit = (basis >> qubit) & 1;
match op {
Pauli::I => {}
Pauli::X => mapped ^= 1usize << qubit,
Pauli::Y => {
mapped ^= 1usize << qubit;
phase *= if bit == 0 {
Complex64::new(0.0, 1.0)
} else {
Complex64::new(0.0, -1.0)
};
}
Pauli::Z => {
if bit == 1 {
phase = -phase;
}
}
}
}
result += phase * rho[basis * dim + mapped];
}
result
}
pub(crate) fn clean(value: f64) -> f64 {
if value.abs() < 1e-12 { 0.0 } else { value }
}
pub(crate) fn clamp_bloch(mut vector: [f64; 3]) -> [f64; 3] {
let norm = (vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]).sqrt();
if norm > 1.0 {
for item in &mut vector {
*item /= norm;
}
}
vector
}