use crate::quantum::ONE;
use crate::quantum_info::DensityMatrix;
fn log2_reproducible(x: f64) -> f64 {
if x <= 0.0 {
return f64::NEG_INFINITY;
}
let bits = x.to_bits();
let exp = (((bits >> 52) & 0x7FF) as i32) - 1023;
let mut m = f64::from_bits((bits & 0x000F_FFFF_FFFF_FFFF) | 0x3FF0_0000_0000_0000);
let mut frac = 0.0f64;
let mut weight = 0.5f64;
for _ in 0..52 {
m *= m;
if m >= 2.0 {
m *= 0.5;
frac += weight;
}
weight *= 0.5;
}
exp as f64 + frac
}
pub fn eigenvalues(rho: &DensityMatrix) -> Vec<f64> {
let d = rho.d;
let n = 2 * d;
let scale = ONE as f64;
let mut m = vec![vec![0.0f64; n]; n];
for r in 0..d {
for c in 0..d {
let e = rho.get(r, c);
let (a, b) = (e.re as f64 / scale, e.im as f64 / scale);
m[r][c] = a;
m[r + d][c + d] = a;
m[r][c + d] = -b;
m[r + d][c] = b;
}
}
for _ in 0..100 {
let mut off = 0.0f64;
for r in 0..n {
for c in 0..n {
if r != c {
off += m[r][c] * m[r][c];
}
}
}
if off <= 1e-30 {
break;
}
for p in 0..n - 1 {
for q in p + 1..n {
if m[p][q] == 0.0 {
continue;
}
let theta = (m[q][q] - m[p][p]) / (2.0 * m[p][q]);
let t = if theta >= 0.0 {
1.0 / (theta + (1.0 + theta * theta).sqrt())
} else {
-1.0 / (-theta + (1.0 + theta * theta).sqrt())
};
let c = 1.0 / (1.0 + t * t).sqrt();
let s = t * c;
for k in 0..n {
let (mkp, mkq) = (m[k][p], m[k][q]);
m[k][p] = c * mkp - s * mkq;
m[k][q] = s * mkp + c * mkq;
}
for k in 0..n {
let (mpk, mqk) = (m[p][k], m[q][k]);
m[p][k] = c * mpk - s * mqk;
m[q][k] = s * mpk + c * mqk;
}
}
}
}
let mut ev: Vec<f64> = (0..n).map(|i| m[i][i]).collect();
ev.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
ev.into_iter().step_by(2).collect()
}
pub fn von_neumann_entropy(rho: &DensityMatrix) -> f64 {
let mut s = 0.0f64;
for l in eigenvalues(rho) {
if l > 1e-12 {
s -= l * log2_reproducible(l);
}
}
if s < 0.0 { 0.0 } else { s }
}
pub fn schumacher_limit(rho: &DensityMatrix) -> f64 {
von_neumann_entropy(rho)
}
pub fn holevo_bound(ensemble: &[(i64, DensityMatrix)]) -> Option<f64> {
let avg = DensityMatrix::mixture(ensemble)?;
let mut mixed = von_neumann_entropy(&avg);
for (w, r) in ensemble {
mixed -= (*w as f64 / ONE as f64) * von_neumann_entropy(r);
}
Some(if mixed < 0.0 { 0.0 } else { mixed })
}
pub fn purity(rho: &DensityMatrix) -> f64 {
rho.purity() as f64 / ONE as f64
}
#[cfg(test)]
mod tests {
use super::*;
use crate::quantum::Circuit;
use crate::quantum_info::DensityMatrix;
fn pure(c: &mut Circuit) -> DensityMatrix {
DensityMatrix::from_pure(&c.simulate().unwrap())
}
#[test]
fn log2_matches_the_library_without_using_it() {
for x in [0.5f64, 0.25, 1.0, 0.75, 1.0 / 3.0, 0.1, 0.999, 1e-6] {
let got = log2_reproducible(x);
assert!((got - x.log2()).abs() < 1e-12, "log2({x}) = {got}, want {}", x.log2());
}
}
#[test]
fn a_pure_state_has_no_entropy() {
let mut c = Circuit::new(2);
c.h(0).cx(0, 1);
let s = von_neumann_entropy(&pure(&mut c));
assert!(s.abs() < 1e-6, "pure state entropy {s}, want 0");
}
#[test]
fn half_a_bell_pair_costs_exactly_one_qubit() {
let mut c = Circuit::new(2);
c.h(0).cx(0, 1);
let red = pure(&mut c).partial_trace(&[0]);
let s = von_neumann_entropy(&red);
assert!((s - 1.0).abs() < 1e-4, "maximally mixed entropy {s}, want 1 bit");
assert!((schumacher_limit(&red) - 1.0).abs() < 1e-4);
assert!((purity(&red) - 0.5).abs() < 1e-3);
}
#[test]
fn orthogonal_states_carry_a_full_bit() {
let mut z = Circuit::new(1);
let mut o = Circuit::new(1);
o.x(0);
let chi = holevo_bound(&[(ONE / 2, pure(&mut z)), (ONE / 2, pure(&mut o))]).unwrap();
assert!((chi - 1.0).abs() < 1e-4, "χ = {chi}, want 1 bit");
}
#[test]
fn non_orthogonal_encoding_loses_information_permanently() {
let mut z = Circuit::new(1);
let mut p = Circuit::new(1);
p.h(0);
let chi = holevo_bound(&[(ONE / 2, pure(&mut z)), (ONE / 2, pure(&mut p))]).unwrap();
assert!(chi < 0.7, "χ = {chi} must fall short of a bit");
assert!(chi > 0.5, "χ = {chi} should still be substantial");
}
#[test]
fn a_qubit_never_carries_more_than_one_bit() {
let mut z = Circuit::new(1);
let mut o = Circuit::new(1);
o.x(0);
let mut p = Circuit::new(1);
p.h(0);
let third = ONE / 3;
let chi = holevo_bound(&[
(third, pure(&mut z)),
(third, pure(&mut o)),
(ONE - 2 * third, pure(&mut p)),
])
.unwrap();
assert!(chi <= 1.0 + 1e-6, "χ = {chi} would beat Holevo, which is impossible");
}
#[test]
fn entropy_is_bit_identical_across_architectures() {
let mut c = Circuit::new(2);
c.h(0).cx(0, 1);
let half = DensityMatrix::from_pure(&c.simulate().unwrap()).partial_trace(&[0]);
let s = von_neumann_entropy(&half);
assert_eq!(
s.to_bits(),
1.000000000824584f64.to_bits(),
"entropy drifted from the pinned bits: {s:.17}"
);
}
#[test]
fn entropy_is_reproducible() {
let mut c = Circuit::new(3);
c.h(0).cx(0, 1).cx(1, 2);
let r = pure(&mut c).partial_trace(&[0, 1]);
assert_eq!(von_neumann_entropy(&r).to_bits(), von_neumann_entropy(&r).to_bits());
}
}