use crate::qis::QisError;
use crate::qis::state::{DensityMatrix, Statevector};
use faer::{c64, mat::Mat};
use num_complex::Complex64;
use rayon::prelude::*;
pub fn purity_pure(sv: &Statevector) -> Result<f64, QisError> {
let norm_sqr: f64 = sv.data().par_iter().map(|c| c.norm_sqr()).sum();
Ok(norm_sqr)
}
pub fn purity_mixed(dm: &DensityMatrix) -> Result<f64, QisError> {
let purity: f64 = dm.data().par_iter().map(|c| c.norm_sqr()).sum();
Ok(purity)
}
pub fn state_fidelity_pure(sv1: &Statevector, sv2: &Statevector) -> Result<f64, QisError> {
if sv1.num_qubits != sv2.num_qubits {
return Err(QisError::QubitMismatch {
expected: sv1.num_qubits,
actual: sv2.num_qubits,
});
}
let inner_product: Complex64 = sv1
.data()
.par_iter()
.zip(sv2.data().par_iter())
.map(|(a, b)| a.conj() * b)
.sum();
Ok(inner_product.norm_sqr())
}
pub fn trace_distance_pure(sv1: &Statevector, sv2: &Statevector) -> Result<f64, QisError> {
let fidelity = state_fidelity_pure(sv1, sv2)?;
let val = (1.0 - fidelity).max(0.0);
Ok(val.sqrt())
}
pub fn state_fidelity_pure_mixed(sv: &Statevector, dm: &DensityMatrix) -> Result<f64, QisError> {
if sv.num_qubits != dm.num_qubits {
return Err(QisError::QubitMismatch {
expected: sv.num_qubits,
actual: dm.num_qubits,
});
}
let dim = 1 << sv.num_qubits;
let mut rho_psi = vec![Complex64::new(0.0, 0.0); dim];
rho_psi.par_iter_mut().enumerate().for_each(|(i, res)| {
let mut sum = Complex64::new(0.0, 0.0);
for j in 0..dim {
sum += dm.data()[i * dim + j] * sv.data()[j];
}
*res = sum;
});
let exp_val: Complex64 = sv
.data()
.par_iter()
.zip(rho_psi.par_iter())
.map(|(a, b)| a.conj() * b)
.sum();
Ok(exp_val.re.max(0.0))
}
fn to_c64(c: Complex64) -> c64 {
c64::new(c.re, c.im)
}
pub fn density_matrix_to_faer(dm: &DensityMatrix) -> Mat<c64> {
let dim = 1 << dm.num_qubits;
Mat::from_fn(dim, dim, |row, col| to_c64(dm.data()[row * dim + col]))
}
pub fn entropy(dm: &DensityMatrix) -> Result<f64, QisError> {
let mat = density_matrix_to_faer(dm);
let s: Vec<f64> = mat
.self_adjoint_eigenvalues(faer::Side::Lower)
.map_err(|e| {
QisError::UnsupportedOperation(format!("Eigendecomposition failed: {:?}", e))
})?;
let mut ent = 0.0;
for &eigval in &s {
if eigval > 1e-12 {
ent -= eigval * eigval.log2();
}
}
Ok(ent)
}
pub fn trace_distance_mixed(dm1: &DensityMatrix, dm2: &DensityMatrix) -> Result<f64, QisError> {
if dm1.num_qubits != dm2.num_qubits {
return Err(QisError::QubitMismatch {
expected: dm1.num_qubits,
actual: dm2.num_qubits,
});
}
let mat1 = density_matrix_to_faer(dm1);
let mat2 = density_matrix_to_faer(dm2);
let diff = mat1 - mat2;
let s: Vec<f64> = diff
.self_adjoint_eigenvalues(faer::Side::Lower)
.map_err(|e| {
QisError::UnsupportedOperation(format!("Eigendecomposition failed: {:?}", e))
})?;
let mut sum_pos = 0.0;
for &eigval in &s {
if eigval > 0.0 {
sum_pos += eigval;
}
}
Ok(sum_pos)
}
pub fn state_fidelity_mixed(dm1: &DensityMatrix, dm2: &DensityMatrix) -> Result<f64, QisError> {
if dm1.num_qubits != dm2.num_qubits {
return Err(QisError::QubitMismatch {
expected: dm1.num_qubits,
actual: dm2.num_qubits,
});
}
let rho = density_matrix_to_faer(dm1);
let sigma = density_matrix_to_faer(dm2);
let decomp_rho = rho.self_adjoint_eigen(faer::Side::Lower).map_err(|e| {
QisError::UnsupportedOperation(format!("Eigendecomposition failed: {:?}", e))
})?;
let u = &decomp_rho.U();
let s = decomp_rho.S();
let s_vec: Vec<f64> = (0..s.dim()).map(|i| s[i].re).collect();
let dim = 1 << dm1.num_qubits;
let mut sqrt_d = Mat::<c64>::zeros(dim, dim);
for i in 0..dim {
let val = s_vec[i].max(0.0).sqrt();
sqrt_d[(i, i)] = c64::new(val, 0.0);
}
let sqrt_rho = u * &sqrt_d * u.adjoint();
let m = &sqrt_rho * sigma * &sqrt_rho;
let m_s: Vec<f64> = m.self_adjoint_eigenvalues(faer::Side::Lower).map_err(|e| {
QisError::UnsupportedOperation(format!("Eigendecomposition failed: {:?}", e))
})?;
let mut trace_sqrt_m = 0.0;
for &eigval in &m_s {
trace_sqrt_m += eigval.max(0.0).sqrt();
}
Ok(trace_sqrt_m * trace_sqrt_m)
}
pub fn partial_transpose(
dm: &DensityMatrix,
target_qubits: &[usize],
) -> Result<DensityMatrix, QisError> {
dm.partial_transpose(target_qubits)
}
pub fn logarithmic_negativity(dm: &DensityMatrix, sys_a: &[usize]) -> Result<f64, QisError> {
let pt_dm = partial_transpose(dm, sys_a)?;
let mat = density_matrix_to_faer(&pt_dm);
let eigenvalues: Vec<f64> = mat
.self_adjoint_eigenvalues(faer::Side::Lower)
.map_err(|e| {
QisError::UnsupportedOperation(format!("Eigendecomposition failed: {:?}", e))
})?;
let mut trace_norm = 0.0;
for &eigval in &eigenvalues {
trace_norm += eigval.abs();
}
let log_neg = trace_norm.max(1.0).log2();
Ok(log_neg)
}
#[cfg(test)]
#[path = "metrics_test.rs"]
mod metrics_test;