use crate::qis::entropy::{
concurrence, entanglement_entropy_pure, entanglement_of_formation, linear_entropy, negativity,
renyi_entropy,
};
use crate::qis::state::{DensityMatrix, Statevector};
use num_complex::Complex64;
const EPSILON: f64 = 1e-10;
fn werner_state(p: f64) -> DensityMatrix {
let mut bell = DensityMatrix::new(2);
bell.apply_h(0).unwrap();
bell.apply_cx(0, 1).unwrap();
let dim = 4;
let mut data: Vec<_> = bell.data().iter().map(|value| *value * p).collect();
for i in 0..dim {
data[i * dim + i] += Complex64::new((1.0 - p) / dim as f64, 0.0);
}
DensityMatrix::from_density_matrix_state(2, data).unwrap()
}
#[test]
fn test_linear_entropy_pure_state() {
let dm = DensityMatrix::new(1);
let s_l = linear_entropy(&dm).unwrap();
assert!(
s_l.abs() < EPSILON,
"Pure state linear entropy should be 0, got {}",
s_l
);
}
#[test]
fn test_linear_entropy_bell_state() {
let mut dm = DensityMatrix::new(2);
dm.apply_h(0).unwrap();
dm.apply_cx(0, 1).unwrap();
let s_l = linear_entropy(&dm).unwrap();
assert!(
s_l.abs() < EPSILON,
"Bell state linear entropy should be 0, got {}",
s_l
);
}
#[test]
fn test_linear_entropy_maximally_mixed() {
let n = 2;
let dim = 1 << n;
let mut data = vec![Complex64::new(0.0, 0.0); dim * dim];
for i in 0..dim {
data[i * dim + i] = Complex64::new(1.0 / dim as f64, 0.0);
}
let dm = DensityMatrix::from_density_matrix_state(n, data).unwrap();
let s_l = linear_entropy(&dm).unwrap();
let expected = 1.0 - 1.0 / (dim as f64);
assert!(
(s_l - expected).abs() < EPSILON,
"Maximally mixed state linear entropy should be {}, got {}",
expected,
s_l
);
}
#[test]
fn test_linear_entropy_single_qubit_mixed() {
let mut data = vec![Complex64::new(0.0, 0.0); 4];
data[0] = Complex64::new(0.75, 0.0); data[3] = Complex64::new(0.25, 0.0); let dm = DensityMatrix::from_density_matrix_state(1, data).unwrap();
let s_l = linear_entropy(&dm).unwrap();
let expected = 1.0 - (0.75f64.powi(2) + 0.25f64.powi(2));
assert!(
(s_l - expected).abs() < EPSILON,
"Mixed state linear entropy should be {}, got {}",
expected,
s_l
);
}
#[test]
fn test_renyi_entropy_alpha_positive_check() {
let dm = DensityMatrix::new(1);
let result = renyi_entropy(&dm, -1.0);
assert!(
result.is_err(),
"Rényi entropy with α <= 0 should return error"
);
let result = renyi_entropy(&dm, 0.0);
assert!(
result.is_err(),
"Rényi entropy with α = 0 should return error"
);
}
#[test]
fn test_renyi_entropy_pure_state() {
let dm = DensityMatrix::new(2);
let s2 = renyi_entropy(&dm, 2.0).unwrap();
assert!(
s2.abs() < EPSILON,
"Collision entropy of pure state should be 0, got {}",
s2
);
let s_half = renyi_entropy(&dm, 0.5).unwrap();
assert!(
s_half.abs() < EPSILON,
"Rényi entropy (α=0.5) of pure state should be 0, got {}",
s_half
);
let s_large = renyi_entropy(&dm, 10.0).unwrap();
assert!(
s_large.abs() < EPSILON,
"Rényi entropy (α=10) of pure state should be 0, got {}",
s_large
);
}
#[test]
fn test_renyi_entropy_graceful_degradation() {
let mut dm = DensityMatrix::new(1);
dm.apply_h(0).unwrap();
let vn_entropy = crate::qis::metrics::entropy(&dm).unwrap();
let alpha = 1.0 + f64::EPSILON;
let renyi = renyi_entropy(&dm, alpha).unwrap();
assert!(
(renyi - vn_entropy).abs() < EPSILON,
"Rényi entropy near α=1 should match Von Neumann entropy: {} vs {}",
renyi,
vn_entropy
);
}
#[test]
fn test_renyi_entropy_maximally_mixed() {
let n = 2;
let dim = 1 << n;
let mut data = vec![Complex64::new(0.0, 0.0); dim * dim];
for i in 0..dim {
data[i * dim + i] = Complex64::new(1.0 / dim as f64, 0.0);
}
let dm = DensityMatrix::from_density_matrix_state(n, data).unwrap();
for alpha in [0.5, 2.0, 5.0] {
let s_alpha = renyi_entropy(&dm, alpha).unwrap();
assert!(
(s_alpha - n as f64).abs() < 1e-6,
"Rényi entropy (α={}) of maximally mixed state should be {}, got {}",
alpha,
n,
s_alpha
);
}
}
#[test]
fn test_renyi_entropy_partially_mixed() {
let p = 0.5;
let dm = werner_state(p);
let s2 = renyi_entropy(&dm, 2.0).unwrap();
let expected_s2 = (16.0f64 / 7.0).log2();
assert!(
(s2 - expected_s2).abs() < 1e-6,
"Rényi entropy (α=2) should be {}, got {}",
expected_s2,
s2
);
}
#[test]
fn test_entanglement_entropy_empty_subsys() {
let sv = Statevector::new(2);
let result = entanglement_entropy_pure(&sv, &[]);
assert!(result.is_err(), "Empty subsystem should return error");
}
#[test]
fn test_entanglement_entropy_full_subsys() {
let sv = Statevector::new(2);
let result = entanglement_entropy_pure(&sv, &[0, 1]);
assert!(result.is_err(), "Full subsystem should return error");
}
#[test]
fn test_entanglement_entropy_duplicate_indices() {
let sv = Statevector::new(2);
let result = entanglement_entropy_pure(&sv, &[0, 0]);
assert!(result.is_err(), "Duplicate indices should return error");
}
#[test]
fn test_entanglement_entropy_out_of_bounds() {
let sv = Statevector::new(2);
let result = entanglement_entropy_pure(&sv, &[10]);
assert!(result.is_err(), "Out of bounds index should return error");
}
#[test]
fn test_entanglement_entropy_bell_state() {
let mut sv = Statevector::new(2);
sv.apply_h(0).unwrap();
sv.apply_cx(0, 1).unwrap();
let ee_0 = entanglement_entropy_pure(&sv, &[0]).unwrap();
assert!(
(ee_0 - 1.0).abs() < EPSILON,
"Bell state entanglement entropy should be 1.0, got {}",
ee_0
);
let ee_1 = entanglement_entropy_pure(&sv, &[1]).unwrap();
assert!(
(ee_1 - 1.0).abs() < EPSILON,
"Bell state entanglement entropy should be 1.0, got {}",
ee_1
);
}
#[test]
fn test_entanglement_entropy_separable_state() {
let mut sv = Statevector::new(2);
sv.apply_h(0).unwrap();
let ee = entanglement_entropy_pure(&sv, &[0]).unwrap();
assert!(
ee.abs() < EPSILON,
"Separable state entanglement entropy should be 0, got {}",
ee
);
}
#[test]
fn test_entanglement_entropy_ghz_state() {
let mut sv = Statevector::new(3);
sv.apply_h(0).unwrap();
sv.apply_cx(0, 1).unwrap();
sv.apply_cx(0, 2).unwrap();
let ee_single = entanglement_entropy_pure(&sv, &[0]).unwrap();
assert!(
(ee_single - 1.0).abs() < EPSILON,
"GHZ state single-qubit entanglement entropy should be 1.0, got {}",
ee_single
);
let ee_double = entanglement_entropy_pure(&sv, &[0, 1]).unwrap();
assert!(
(ee_double - 1.0).abs() < EPSILON,
"GHZ state two-qubit entanglement entropy should be 1.0, got {}",
ee_double
);
}
#[test]
fn test_entanglement_entropy_w_state() {
let n = 3;
let dim = 1 << n;
let mut data = vec![Complex64::new(0.0, 0.0); dim];
data[4] = Complex64::new(1.0 / 3.0f64.sqrt(), 0.0);
data[2] = Complex64::new(1.0 / 3.0f64.sqrt(), 0.0);
data[1] = Complex64::new(1.0 / 3.0f64.sqrt(), 0.0);
let sv = Statevector::from_state(n, data).unwrap();
let ee = entanglement_entropy_pure(&sv, &[0]).unwrap();
let expected = (3.0f64).log2() - 2.0 / 3.0;
assert!(
(ee - expected).abs() < 1e-6,
"W state entanglement entropy should be {}, got {}",
expected,
ee
);
}
#[test]
fn test_entanglement_entropy_partially_entangled() {
let theta = std::f64::consts::PI / 6.0;
let cos_theta = theta.cos();
let sin_theta = theta.sin();
let cos_sq = cos_theta * cos_theta;
let sin_sq = sin_theta * sin_theta;
let mut data = vec![Complex64::new(0.0, 0.0); 4];
data[0] = Complex64::new(cos_theta, 0.0);
data[3] = Complex64::new(sin_theta, 0.0);
let sv = Statevector::from_state(2, data).unwrap();
let ee = entanglement_entropy_pure(&sv, &[0]).unwrap();
let expected = -cos_sq * cos_sq.log2() - sin_sq * sin_sq.log2();
assert!(
(ee - expected).abs() < EPSILON,
"Partially entangled state entropy should be {}, got {}",
expected,
ee
);
}
#[test]
fn test_negativity_bell_state() {
let mut dm = DensityMatrix::new(2);
dm.apply_h(0).unwrap();
dm.apply_cx(0, 1).unwrap();
let neg = negativity(&dm, &[0]).unwrap();
assert!(
(neg - 0.5).abs() < EPSILON,
"Bell state negativity should be 0.5, got {}",
neg
);
}
#[test]
fn test_negativity_separable_state() {
let dm = DensityMatrix::new(2);
let neg = negativity(&dm, &[0]).unwrap();
assert!(
neg.abs() < EPSILON,
"Separable state negativity should be 0, got {}",
neg
);
}
#[test]
fn test_negativity_maximally_mixed() {
let n = 2;
let dim = 1 << n;
let mut data = vec![Complex64::new(0.0, 0.0); dim * dim];
for i in 0..dim {
data[i * dim + i] = Complex64::new(1.0 / dim as f64, 0.0);
}
let dm = DensityMatrix::from_density_matrix_state(n, data).unwrap();
let neg = negativity(&dm, &[0]).unwrap();
assert!(
neg.abs() < EPSILON,
"Maximally mixed state negativity should be 0, got {}",
neg
);
}
#[test]
fn test_negativity_werner_state() {
let p = 0.5;
let dm = werner_state(p);
let neg = negativity(&dm, &[0]).unwrap();
assert!(
neg > 0.0,
"Werner state with p=0.5 should have positive negativity"
);
let p_sep = 0.2;
let dm_sep = werner_state(p_sep);
let neg_sep = negativity(&dm_sep, &[0]).unwrap();
assert!(
neg_sep.abs() < 1e-6,
"Werner state with p=0.2 should have negativity ≈ 0, got {}",
neg_sep
);
}
#[test]
fn test_negativity_different_subsystems() {
let mut dm = DensityMatrix::new(2);
dm.apply_h(0).unwrap();
dm.apply_cx(0, 1).unwrap();
let neg_0 = negativity(&dm, &[0]).unwrap();
assert!((neg_0 - 0.5).abs() < EPSILON);
let neg_1 = negativity(&dm, &[1]).unwrap();
assert!((neg_1 - 0.5).abs() < EPSILON);
}
#[test]
fn test_negativity_ghz_state() {
let mut dm = DensityMatrix::new(3);
dm.apply_h(0).unwrap();
dm.apply_cx(0, 1).unwrap();
dm.apply_cx(0, 2).unwrap();
let neg = negativity(&dm, &[0]).unwrap();
assert!(
(neg - 0.5).abs() < EPSILON,
"GHZ state negativity should be 0.5, got {}",
neg
);
let neg_2q = negativity(&dm, &[0, 1]).unwrap();
assert!(
(neg_2q - 0.5).abs() < EPSILON,
"GHZ state (2q vs 1q) negativity should be 0.5, got {}",
neg_2q
);
}
#[test]
fn test_concurrence_not_2qubit() {
let dm_1q = DensityMatrix::new(1);
let result = concurrence(&dm_1q);
assert!(
result.is_err(),
"Concurrence for non-2-qubit state should return error"
);
let dm_3q = DensityMatrix::new(3);
let result = concurrence(&dm_3q);
assert!(
result.is_err(),
"Concurrence for 3-qubit state should return error"
);
}
#[test]
fn test_concurrence_bell_states() {
let bell_states = [
(
"|Φ+⟩",
vec![(0, 1.0 / 2.0f64.sqrt()), (3, 1.0 / 2.0f64.sqrt())],
),
(
"|Φ-⟩",
vec![(0, 1.0 / 2.0f64.sqrt()), (3, -1.0 / 2.0f64.sqrt())],
),
(
"|Ψ+⟩",
vec![(1, 1.0 / 2.0f64.sqrt()), (2, 1.0 / 2.0f64.sqrt())],
),
(
"|Ψ-⟩",
vec![(1, 1.0 / 2.0f64.sqrt()), (2, -1.0 / 2.0f64.sqrt())],
),
];
for (name, amps) in &bell_states {
let mut data = vec![Complex64::new(0.0, 0.0); 4];
for (idx, amp) in amps {
data[*idx] = Complex64::new(*amp, 0.0);
}
let _sv = Statevector::from_state(2, data.clone()).unwrap();
let dm = DensityMatrix::from_state(2, data).unwrap();
let c = concurrence(&dm).unwrap();
assert!(
(c - 1.0).abs() < EPSILON,
"Bell state {} should have concurrence = 1.0, got {}",
name,
c
);
}
}
#[test]
fn test_concurrence_separable_states() {
let separable_states = [
("|00⟩", vec![(0, 1.0)]),
("|01⟩", vec![(1, 1.0)]),
("|10⟩", vec![(2, 1.0)]),
("|11⟩", vec![(3, 1.0)]),
(
"|+0⟩",
vec![(0, 1.0 / 2.0f64.sqrt()), (2, 1.0 / 2.0f64.sqrt())],
),
(
"|0+⟩",
vec![(0, 1.0 / 2.0f64.sqrt()), (1, 1.0 / 2.0f64.sqrt())],
),
];
for (name, amps) in &separable_states {
let mut data = vec![Complex64::new(0.0, 0.0); 4];
for (idx, amp) in amps {
data[*idx] = Complex64::new(*amp, 0.0);
}
let dm = DensityMatrix::from_state(2, data).unwrap();
let c = concurrence(&dm).unwrap();
assert!(
c.abs() < EPSILON,
"Separable state {} should have concurrence = 0, got {}",
name,
c
);
}
}
#[test]
fn test_concurrence_maximally_mixed() {
let dim = 4;
let mut data = vec![Complex64::new(0.0, 0.0); dim * dim];
for i in 0..dim {
data[i * dim + i] = Complex64::new(1.0 / dim as f64, 0.0);
}
let dm = DensityMatrix::from_density_matrix_state(2, data).unwrap();
let c = concurrence(&dm).unwrap();
assert!(
c.abs() < EPSILON,
"Maximally mixed state should have concurrence = 0, got {}",
c
);
}
#[test]
fn test_concurrence_partially_entangled() {
let test_angles = [
(std::f64::consts::PI / 12.0, 0.5), (std::f64::consts::PI / 6.0, 3.0f64.sqrt() / 2.0), (std::f64::consts::PI / 4.0, 1.0), ];
for (theta, expected_c) in &test_angles {
let mut data = vec![Complex64::new(0.0, 0.0); 4];
data[0] = Complex64::new(theta.cos(), 0.0);
data[3] = Complex64::new(theta.sin(), 0.0);
let dm = DensityMatrix::from_state(2, data).unwrap();
let c = concurrence(&dm).unwrap();
assert!(
(c - *expected_c).abs() < 1e-6,
"Partially entangled state with θ={:?} should have concurrence = {}, got {}",
theta,
expected_c,
c
);
}
}
#[test]
fn test_concurrence_werner_state() {
let p = 0.5;
let expected_c = (3.0 * p - 1.0) / 2.0;
let dm = werner_state(p);
let c = concurrence(&dm).unwrap();
assert!(
(c - expected_c).abs() < 1e-6,
"Werner state with p={} should have concurrence ≈ {}, got {}",
p,
expected_c,
c
);
let p_sep = 0.2;
let dm_sep = werner_state(p_sep);
let c_sep = concurrence(&dm_sep).unwrap();
assert!(
c_sep.abs() < EPSILON,
"Werner state with p={} should have concurrence = 0, got {}",
p_sep,
c_sep
);
}
#[test]
fn test_eof_bell_states() {
let mut dm = DensityMatrix::new(2);
dm.apply_h(0).unwrap();
dm.apply_cx(0, 1).unwrap();
let eof = entanglement_of_formation(&dm).unwrap();
assert!(
(eof - 1.0).abs() < EPSILON,
"Bell state EOF should be 1.0, got {}",
eof
);
}
#[test]
fn test_eof_clamps_concurrence_roundoff_before_square_root() {
for c in [1.0, 1.0 + f64::EPSILON, 1.0 + 8.0 * f64::EPSILON] {
let eof = super::entanglement_of_formation_from_concurrence(c);
assert_eq!(eof, 1.0, "concurrence={c}, entropy={eof}");
}
for c in [0.0, -f64::EPSILON] {
assert_eq!(super::entanglement_of_formation_from_concurrence(c), 0.0);
}
let expected = -0.9_f64 * 0.9_f64.log2() - 0.1_f64 * 0.1_f64.log2();
let actual = super::entanglement_of_formation_from_concurrence(0.6);
assert!((actual - expected).abs() < 1e-14);
}
#[test]
fn test_eof_separable_states() {
let dm = DensityMatrix::new(2);
let eof = entanglement_of_formation(&dm).unwrap();
assert!(
eof.abs() < EPSILON,
"Separable state EOF should be 0, got {}",
eof
);
}
#[test]
fn test_eof_maximally_mixed() {
let dim = 4;
let mut data = vec![Complex64::new(0.0, 0.0); dim * dim];
for i in 0..dim {
data[i * dim + i] = Complex64::new(1.0 / dim as f64, 0.0);
}
let dm = DensityMatrix::from_density_matrix_state(2, data).unwrap();
let eof = entanglement_of_formation(&dm).unwrap();
assert!(
eof.abs() < EPSILON,
"Maximally mixed state EOF should be 0, got {}",
eof
);
}
#[test]
fn test_eof_partially_entangled() {
let theta = std::f64::consts::PI / 6.0;
let c = (2.0 * theta).sin();
let mut data = vec![Complex64::new(0.0, 0.0); 4];
data[0] = Complex64::new(theta.cos(), 0.0);
data[3] = Complex64::new(theta.sin(), 0.0);
let dm = DensityMatrix::from_state(2, data).unwrap();
let eof = entanglement_of_formation(&dm).unwrap();
let sqrt_term = (1.0 - c * c).sqrt();
let x = (1.0 + sqrt_term) / 2.0;
let expected_eof = -x * x.log2() - (1.0 - x) * (1.0 - x).log2();
assert!(
(eof - expected_eof).abs() < 1e-6,
"Partially entangled state EOF should be {}, got {}",
expected_eof,
eof
);
}
#[test]
fn test_eof_concurrence_relationship() {
let mut dm_bell = DensityMatrix::new(2);
dm_bell.apply_h(0).unwrap();
dm_bell.apply_cx(0, 1).unwrap();
let c_bell = concurrence(&dm_bell).unwrap();
let eof_bell = entanglement_of_formation(&dm_bell).unwrap();
assert!((c_bell - 1.0).abs() < EPSILON);
assert!((eof_bell - 1.0).abs() < EPSILON);
let dm_sep = DensityMatrix::new(2);
let c_sep = concurrence(&dm_sep).unwrap();
let eof_sep = entanglement_of_formation(&dm_sep).unwrap();
assert!(c_sep.abs() < EPSILON);
assert!(eof_sep.abs() < EPSILON);
}
#[test]
fn formation_entropy_of_rotated_maximally_entangled_state() {
let state = vec![
Complex64::new(-0.5917562523120286, -0.14345273890626087),
Complex64::new(0.12739228627789673, 0.3361800930257551),
Complex64::new(-0.06600502193827436, 0.35339664207352467),
Complex64::new(-0.5571108451004554, 0.2457267928442915),
];
let dm = DensityMatrix::from_state(2, state).unwrap();
let eof = entanglement_of_formation(&dm).unwrap();
assert!(
eof.is_finite() && (0.0..=1.0).contains(&eof),
"entropy={eof}"
);
let tolerance = 8.0 * f64::EPSILON.sqrt();
assert!(
(eof - 1.0).abs() < tolerance,
"entropy={eof}, tolerance={tolerance}"
);
}
#[test]
fn renyi_extreme_orders_and_small_positive_eigenvalues() {
for n in 1..=3 {
let dm = DensityMatrix::maximally_mixed(n);
for alpha in [
0.1,
1.0 - 1e-12,
1.0,
1.0 + 1e-12,
2000.0,
f64::MAX,
f64::INFINITY,
] {
assert!(
(renyi_entropy(&dm, alpha).unwrap() - n as f64).abs() < 1e-10,
"n={n}, alpha={alpha}"
);
assert!(renyi_entropy(&DensityMatrix::new(n), alpha).unwrap().abs() < 1e-10);
}
}
let dm = DensityMatrix::from_density_matrix_state(
1,
vec![(1.0 - 1e-14).into(), 0.0.into(), 0.0.into(), 1e-14.into()],
)
.unwrap();
assert!(
(renyi_entropy(&dm, 0.1).unwrap() - 0.06257881098503021).abs() < 1e-12,
"got {}",
renyi_entropy(&dm, 0.1).unwrap()
);
assert!(renyi_entropy(&dm, f64::NAN).is_err());
}
#[test]
fn renyi_entropy_of_nondiagonal_pure_state_is_zero() {
let state = [1.0, 2.0, 3.0, 4.0]
.map(|v| Complex64::new(v / 30.0_f64.sqrt(), 0.0))
.to_vec();
let dm = DensityMatrix::from_state(2, state).unwrap();
for alpha in [0.01, 0.1, 0.5, 1.0, 2.0, f64::INFINITY] {
let actual = renyi_entropy(&dm, alpha).unwrap();
assert!(actual.abs() < 1e-10, "alpha={alpha}, entropy={actual}");
}
}
#[test]
fn renyi_entropy_preserves_rank_deficient_spectrum_under_unitary_rotation() {
let column = [
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 1.0),
Complex64::new(-1.0, 0.0),
Complex64::new(0.0, -1.0),
];
let mut rho = Vec::new();
let mut diagonal = vec![Complex64::new(0.0, 0.0); 16];
diagonal[0] = 0.8.into();
diagonal[5] = 0.2.into();
for i in 0..4 {
for j in 0..4 {
rho.push((Complex64::new(0.8, 0.0) + 0.2 * column[i] * column[j].conj()) / 4.0);
}
}
for data in [diagonal, rho] {
let dm = DensityMatrix::from_density_matrix_state(2, data).unwrap();
for alpha in [0.01, 0.1, 0.5, 1.0, 2.0, f64::INFINITY] {
let expected = if alpha == 1.0 {
-0.8_f64 * 0.8_f64.log2() - 0.2_f64 * 0.2_f64.log2()
} else if alpha == f64::INFINITY {
-0.8_f64.log2()
} else {
(0.8_f64.powf(alpha) + 0.2_f64.powf(alpha)).log2() / (1.0 - alpha)
};
assert!((renyi_entropy(&dm, alpha).unwrap() - expected).abs() < 1e-10);
}
}
}