use num_complex::Complex64;
use std::f64::consts::LN_2;
#[derive(Debug, Clone)]
pub struct DensityMatrix {
pub dim_a: usize,
pub dim_b: usize,
pub rho: Vec<Vec<Complex64>>,
}
impl DensityMatrix {
pub fn new(dim_a: usize, dim_b: usize) -> Self {
let d = dim_a * dim_b;
let val = Complex64::new(1.0 / d as f64, 0.0);
let rho = (0..d)
.map(|i| {
(0..d)
.map(|j| {
if i == j {
val
} else {
Complex64::new(0.0, 0.0)
}
})
.collect()
})
.collect();
Self { dim_a, dim_b, rho }
}
pub fn pure_state(state: &[Complex64]) -> Self {
let d = state.len();
let dim_a = (d as f64).sqrt() as usize;
let dim_b = if dim_a * dim_a == d { dim_a } else { d };
let norm_sq: f64 = state.iter().map(|v| v.norm_sqr()).sum();
let norm = norm_sq.sqrt().max(1e-30);
let normed: Vec<Complex64> = state.iter().map(|v| v / norm).collect();
let rho: Vec<Vec<Complex64>> = (0..d)
.map(|i| (0..d).map(|j| normed[i] * normed[j].conj()).collect())
.collect();
Self { dim_a, dim_b, rho }
}
pub fn bell_phi_plus() -> Self {
let s = 1.0_f64 / 2.0_f64.sqrt();
let state = [
Complex64::new(s, 0.0), Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0), Complex64::new(s, 0.0), ];
Self::pure_state(&state)
}
pub fn bell_phi_minus() -> Self {
let s = 1.0_f64 / 2.0_f64.sqrt();
let state = [
Complex64::new(s, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(-s, 0.0),
];
Self::pure_state(&state)
}
pub fn bell_psi_plus() -> Self {
let s = 1.0_f64 / 2.0_f64.sqrt();
let state = [
Complex64::new(0.0, 0.0),
Complex64::new(s, 0.0),
Complex64::new(s, 0.0),
Complex64::new(0.0, 0.0),
];
Self::pure_state(&state)
}
pub fn bell_psi_minus() -> Self {
let s = 1.0_f64 / 2.0_f64.sqrt();
let state = [
Complex64::new(0.0, 0.0),
Complex64::new(s, 0.0),
Complex64::new(-s, 0.0),
Complex64::new(0.0, 0.0),
];
Self::pure_state(&state)
}
pub fn werner_state(p: f64) -> Self {
let p = p.clamp(0.0, 1.0);
let psi_minus = Self::bell_psi_minus();
let mixed = Self::new(2, 2);
let d = 4;
let rho: Vec<Vec<Complex64>> = (0..d)
.map(|i| {
(0..d)
.map(|j| {
Complex64::new(p, 0.0) * psi_minus.rho[i][j]
+ Complex64::new(1.0 - p, 0.0) * mixed.rho[i][j]
})
.collect()
})
.collect();
Self {
dim_a: 2,
dim_b: 2,
rho,
}
}
pub fn partial_trace_b(&self) -> Vec<Vec<Complex64>> {
let da = self.dim_a;
let db = self.dim_b;
let mut rho_a = vec![vec![Complex64::new(0.0, 0.0); da]; da];
for (a1, rho_a_row) in rho_a.iter_mut().enumerate().take(da) {
for (a2, elem) in rho_a_row.iter_mut().enumerate().take(da) {
let mut sum = Complex64::new(0.0, 0.0);
for b in 0..db {
let row = a1 * db + b;
let col = a2 * db + b;
sum += self.rho[row][col];
}
*elem = sum;
}
}
rho_a
}
pub fn partial_trace_a(&self) -> Vec<Vec<Complex64>> {
let da = self.dim_a;
let db = self.dim_b;
let mut rho_b = vec![vec![Complex64::new(0.0, 0.0); db]; db];
for (b1, rho_b_row) in rho_b.iter_mut().enumerate().take(db) {
for (b2, elem) in rho_b_row.iter_mut().enumerate().take(db) {
let mut sum = Complex64::new(0.0, 0.0);
for a in 0..da {
let row = a * db + b1;
let col = a * db + b2;
sum += self.rho[row][col];
}
*elem = sum;
}
}
rho_b
}
pub fn von_neumann_entropy(&self) -> f64 {
let d = self.dim_a * self.dim_b;
let eigenvalues = hermitian_eigenvalues_real(&self.rho, d);
shannon_entropy_from_eigenvalues(&eigenvalues)
}
pub fn entanglement_entropy(&self) -> f64 {
let rho_a = self.partial_trace_b();
let eigenvalues = hermitian_eigenvalues_real(&rho_a, self.dim_a);
shannon_entropy_from_eigenvalues(&eigenvalues)
}
pub fn concurrence(&self) -> f64 {
if self.dim_a != 2 || self.dim_b != 2 {
return 0.0; }
let r = spin_flipped_matrix(&self.rho);
let eigenvalues = positive_eigenvalues_4x4(&r);
let mut sqrt_eigs: Vec<f64> = eigenvalues
.iter()
.map(|&e| if e > 0.0 { e.sqrt() } else { 0.0 })
.collect();
sqrt_eigs.resize(4, 0.0);
sqrt_eigs.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
let c = sqrt_eigs[0] - sqrt_eigs[1] - sqrt_eigs[2] - sqrt_eigs[3];
c.max(0.0)
}
pub fn entanglement_of_formation(&self) -> f64 {
let c = self.concurrence();
let x = (1.0 + (1.0 - c * c).max(0.0).sqrt()) / 2.0;
binary_entropy(x)
}
pub fn negativity(&self) -> f64 {
if self.dim_a != 2 || self.dim_b != 2 {
return 0.0; }
let pt = partial_transpose_b(&self.rho, self.dim_a, self.dim_b);
let trace_norm = trace_norm_4x4(&pt);
(trace_norm - 1.0).max(0.0) / 2.0
}
pub fn log_negativity(&self) -> f64 {
if self.dim_a != 2 || self.dim_b != 2 {
return 0.0;
}
let pt = partial_transpose_b(&self.rho, self.dim_a, self.dim_b);
let trace_norm = trace_norm_4x4(&pt);
trace_norm.max(1e-30).log2()
}
pub fn fidelity_to_pure(&self, target: &[Complex64]) -> f64 {
let d = self.dim_a * self.dim_b;
if target.len() != d {
return 0.0;
}
let norm_sq: f64 = target.iter().map(|v| v.norm_sqr()).sum();
let norm = norm_sq.sqrt().max(1e-30);
let normed: Vec<Complex64> = target.iter().map(|v| v / norm).collect();
let mut f = Complex64::new(0.0, 0.0);
for i in 0..d {
for j in 0..d {
f += normed[i].conj() * self.rho[i][j] * normed[j];
}
}
f.re.clamp(0.0, 1.0)
}
pub fn is_separable(&self, tol: f64) -> bool {
self.negativity() < tol
}
}
pub fn binary_entropy(x: f64) -> f64 {
let x = x.clamp(0.0, 1.0);
let h = |p: f64| {
if !(1e-15..=1.0 - 1e-15).contains(&p) {
0.0
} else {
-p * p.log2()
}
};
h(x) + h(1.0 - x)
}
fn shannon_entropy_from_eigenvalues(eigenvalues: &[f64]) -> f64 {
eigenvalues.iter().fold(0.0, |acc, &lam| {
if lam > 1e-15 {
acc - lam * (lam.ln() / LN_2)
} else {
acc
}
})
}
fn hermitian_eigenvalues_real(m: &[Vec<Complex64>], d: usize) -> Vec<f64> {
if d == 0 {
return vec![];
}
if d == 1 {
return vec![m[0][0].re];
}
if d == 2 {
return eigenvalues_2x2_symmetric(m[0][0].re, m[0][1].re, m[1][1].re);
}
if d == 4 {
return eigenvalues_4x4_real_symmetric(m);
}
let real_m: Vec<Vec<f64>> = m
.iter()
.map(|row| row.iter().map(|v| v.re).collect())
.collect();
power_deflation_eigenvalues(&real_m, d, d)
}
fn eigenvalues_2x2_symmetric(a: f64, b: f64, c: f64) -> Vec<f64> {
let trace = a + c;
let det = a * c - b * b;
let disc = ((trace * trace) / 4.0 - det).max(0.0).sqrt();
vec![trace / 2.0 + disc, trace / 2.0 - disc]
}
fn eigenvalues_4x4_real_symmetric(m: &[Vec<Complex64>]) -> Vec<f64> {
let real_m: Vec<Vec<f64>> = m
.iter()
.map(|row| row.iter().map(|v| v.re).collect())
.collect();
power_deflation_eigenvalues(&real_m, 4, 4)
}
fn power_deflation_eigenvalues(m: &[Vec<f64>], n: usize, n_eigs: usize) -> Vec<f64> {
let mut mat: Vec<Vec<f64>> = m.to_vec();
let mut eigenvalues = Vec::with_capacity(n_eigs);
for _ in 0..n_eigs {
let (lam, v) = power_iteration_largest(&mat, n, 500, 1e-13);
eigenvalues.push(lam);
for i in 0..n {
for j in 0..n {
mat[i][j] -= lam * v[i] * v[j];
}
}
}
eigenvalues
}
fn power_iteration_largest(m: &[Vec<f64>], n: usize, max_iter: usize, tol: f64) -> (f64, Vec<f64>) {
let mut v: Vec<f64> = (0..n)
.map(|i| if i == 0 { 1.0 } else { 0.1 / (i as f64) })
.collect();
normalise_vec(&mut v);
let mut lam = 0.0_f64;
for _ in 0..max_iter {
let mv = matvec(m, &v, n);
let new_lam: f64 = v.iter().zip(mv.iter()).map(|(a, b)| a * b).sum();
let norm: f64 = mv.iter().map(|x| x * x).sum::<f64>().sqrt().max(1e-30);
let v_new: Vec<f64> = mv.iter().map(|x| x / norm).collect();
let diff: f64 = v_new.iter().zip(v.iter()).map(|(a, b)| (a - b).abs()).sum();
v = v_new;
let converged = (new_lam - lam).abs() < tol && diff < tol;
lam = new_lam;
if converged {
break;
}
}
(lam, v)
}
fn normalise_vec(v: &mut [f64]) {
let norm: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt().max(1e-30);
for x in v.iter_mut() {
*x /= norm;
}
}
fn matvec(m: &[Vec<f64>], v: &[f64], n: usize) -> Vec<f64> {
let mut result = vec![0.0_f64; n];
for i in 0..n {
for j in 0..n {
result[i] += m[i][j] * v[j];
}
}
result
}
fn spin_flipped_matrix(rho: &[Vec<Complex64>]) -> Vec<Vec<Complex64>> {
let sigma_y_tp_sigma_y = |i: usize, j: usize| -> Complex64 {
let map = [
(0usize, 3usize, -1.0_f64),
(1, 2, 1.0),
(2, 1, 1.0),
(3, 0, -1.0),
];
for &(r, c, sign) in &map {
if i == r && j == c {
return Complex64::new(sign, 0.0);
}
}
Complex64::new(0.0, 0.0)
};
let n = 4;
let mut tilde_rho = vec![vec![Complex64::new(0.0, 0.0); n]; n];
for (i, tilde_row) in tilde_rho.iter_mut().enumerate().take(n) {
for (j, elem) in tilde_row.iter_mut().enumerate().take(n) {
let mut val = Complex64::new(0.0, 0.0);
for (k, rho_row) in rho.iter().enumerate().take(n) {
for (l, &rho_kl) in rho_row.iter().enumerate().take(n) {
val += sigma_y_tp_sigma_y(i, k) * rho_kl.conj() * sigma_y_tp_sigma_y(l, j);
}
}
*elem = val;
}
}
let mut r = vec![vec![Complex64::new(0.0, 0.0); n]; n];
for i in 0..n {
for j in 0..n {
for k in 0..n {
r[i][j] += rho[i][k] * tilde_rho[k][j];
}
}
}
r
}
fn positive_eigenvalues_4x4(r: &[Vec<Complex64>]) -> Vec<f64> {
let real_r: Vec<Vec<f64>> = r
.iter()
.map(|row| row.iter().map(|v| v.re).collect())
.collect();
let mut eigs = power_deflation_eigenvalues(&real_r, 4, 4);
for e in eigs.iter_mut() {
if *e < 0.0 {
*e = 0.0;
}
}
eigs
}
fn partial_transpose_b(rho: &[Vec<Complex64>], dim_a: usize, dim_b: usize) -> Vec<Vec<Complex64>> {
let d = dim_a * dim_b;
let mut pt = vec![vec![Complex64::new(0.0, 0.0); d]; d];
for a1 in 0..dim_a {
for b1 in 0..dim_b {
for a2 in 0..dim_a {
for b2 in 0..dim_b {
let row = a1 * dim_b + b1;
let col = a2 * dim_b + b2;
let src_row = a1 * dim_b + b2;
let src_col = a2 * dim_b + b1;
pt[row][col] = rho[src_row][src_col];
}
}
}
}
pt
}
fn trace_norm_4x4(m: &[Vec<Complex64>]) -> f64 {
let real_m: Vec<Vec<f64>> = m
.iter()
.map(|row| row.iter().map(|v| v.re).collect())
.collect();
let eigs = hermitian_4x4_all_eigenvalues(&real_m);
eigs.iter().map(|&e| e.abs()).sum()
}
fn hermitian_4x4_all_eigenvalues(m: &[Vec<f64>]) -> Vec<f64> {
let n = 4;
let spectral_radius: f64 = m
.iter()
.map(|row| row.iter().map(|x| x.abs()).sum::<f64>())
.fold(0.0_f64, f64::max);
let shift = spectral_radius + 1.0;
let mut shifted = m.to_vec();
for (i, row) in shifted.iter_mut().enumerate().take(n) {
row[i] += shift;
}
let mut shifted_eigs = Vec::with_capacity(n);
let mut mat = shifted.clone();
for _ in 0..n {
let (nu, v) = power_iteration_largest(&mat, n, 600, 1e-13);
shifted_eigs.push(nu);
for i in 0..n {
for j in 0..n {
mat[i][j] -= nu * v[i] * v[j];
}
}
}
shifted_eigs.iter().map(|&nu| nu - shift).collect()
}
#[derive(Debug, Clone)]
pub struct PolarizationEntanglement {
pub visibility_hv: f64,
pub visibility_da: f64,
pub visibility_rl: f64,
}
impl PolarizationEntanglement {
pub fn new(v_hv: f64, v_da: f64, v_rl: f64) -> Self {
Self {
visibility_hv: v_hv.clamp(0.0, 1.0),
visibility_da: v_da.clamp(0.0, 1.0),
visibility_rl: v_rl.clamp(0.0, 1.0),
}
}
pub fn tangle(&self) -> f64 {
let f = self.bell_state_fidelity();
(2.0 * f - 1.0).max(0.0)
}
pub fn concurrence(&self) -> f64 {
self.tangle().sqrt()
}
pub fn bell_state_fidelity(&self) -> f64 {
((1.0 + self.visibility_hv + self.visibility_da + self.visibility_rl) / 4.0).clamp(0.0, 1.0)
}
pub fn is_entangled(&self) -> bool {
self.bell_state_fidelity() > 0.5
}
}
#[derive(Debug, Clone)]
pub struct TimeBinEntanglement {
pub visibility_z: f64,
pub visibility_x: f64,
pub phi: f64,
}
impl TimeBinEntanglement {
pub fn new(v_z: f64, v_x: f64, phi: f64) -> Self {
Self {
visibility_z: v_z.clamp(0.0, 1.0),
visibility_x: v_x.clamp(0.0, 1.0),
phi,
}
}
pub fn concurrence(&self) -> f64 {
self.visibility_x
}
pub fn qber(&self) -> f64 {
(1.0 - self.visibility_x) / 2.0
}
pub fn secret_key_fraction(&self) -> f64 {
let qber = self.qber();
let r = 1.0 - 2.0 * binary_entropy(qber);
r.max(0.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bell_state_purity() {
let rho = DensityMatrix::bell_phi_plus();
let d = 4;
let tr_rho_sq: f64 = (0..d)
.map(|i| (0..d).map(|j| rho.rho[i][j].norm_sqr()).sum::<f64>())
.sum();
assert!((tr_rho_sq - 1.0).abs() < 1e-10, "Bell state must be pure");
}
#[test]
fn test_bell_state_entanglement_entropy() {
let rho = DensityMatrix::bell_phi_plus();
let s = rho.entanglement_entropy();
assert!(
(s - 1.0).abs() < 1e-6,
"Bell state entropy = 1 ebit, got {s}"
);
}
#[test]
fn test_concurrence_bell_state() {
let rho = DensityMatrix::bell_psi_minus();
let c = rho.concurrence();
assert!(
(c - 1.0).abs() < 1e-6,
"Concurrence of Bell state = 1, got {c}"
);
}
#[test]
fn test_concurrence_product_state() {
let state = [
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
];
let rho = DensityMatrix::pure_state(&state);
let c = rho.concurrence();
assert!(c < 1e-6, "Product state concurrence = 0, got {c}");
}
#[test]
fn test_negativity_bell_state() {
let rho = DensityMatrix::bell_psi_minus();
let n = rho.negativity();
assert!(
(n - 0.5).abs() < 1e-6,
"Negativity of Bell state = 0.5, got {n}"
);
}
#[test]
fn test_werner_state_separability() {
let rho = DensityMatrix::werner_state(0.1);
assert!(rho.is_separable(1e-6), "Werner(0.1) should be separable");
let rho2 = DensityMatrix::werner_state(0.9);
assert!(!rho2.is_separable(1e-6), "Werner(0.9) should be entangled");
}
#[test]
fn test_fidelity_bell_state() {
let rho = DensityMatrix::bell_phi_plus();
let s = 1.0_f64 / 2.0_f64.sqrt();
let target = [
Complex64::new(s, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(s, 0.0),
];
let f = rho.fidelity_to_pure(&target);
assert!(
(f - 1.0).abs() < 1e-6,
"Fidelity of state to itself = 1, got {f}"
);
}
#[test]
fn test_polarization_entanglement_perfect() {
let pe = PolarizationEntanglement::new(1.0, 1.0, 1.0);
assert!(
(pe.bell_state_fidelity() - 1.0).abs() < 1e-9,
"F={}",
pe.bell_state_fidelity()
);
assert!(pe.is_entangled());
assert!(
(pe.concurrence() - 1.0).abs() < 1e-9,
"C={}",
pe.concurrence()
);
}
#[test]
fn test_time_bin_qber() {
let tbe = TimeBinEntanglement::new(1.0, 0.96, 0.0);
let qber = tbe.qber();
assert!((qber - 0.02).abs() < 1e-9, "QBER = (1-V_X)/2");
}
#[test]
fn test_binary_entropy_extremes() {
assert!(binary_entropy(0.0).abs() < 1e-12, "h(0) = 0");
assert!(binary_entropy(1.0).abs() < 1e-12, "h(1) = 0");
assert!((binary_entropy(0.5) - 1.0).abs() < 1e-10, "h(0.5) = 1");
}
}