use num_complex::Complex64;
use std::f64::consts::PI;
pub(crate) fn ln_factorial(n: usize) -> f64 {
match n {
0 | 1 => 0.0,
2..=20 => {
let mut acc = 0.0_f64;
for k in 2..=n {
acc += (k as f64).ln();
}
acc
}
_ => {
let nf = n as f64;
nf * nf.ln() - nf + 0.5 * (2.0 * PI * nf).ln() + 1.0 / (12.0 * nf)
}
}
}
#[allow(dead_code)]
pub(crate) fn factorial_small(n: usize) -> Option<u64> {
const TABLE: [u64; 21] = [
1,
1,
2,
6,
24,
120,
720,
5040,
40320,
362880,
3628800,
39916800,
479001600,
6227020800,
87178291200,
1307674368000,
20922789888000,
355687428096000,
6402373705728000,
121645100408832000,
2432902008176640000,
];
TABLE.get(n).copied()
}
pub(crate) fn binom(n: usize, k: usize) -> u64 {
if k > n {
return 0;
}
if k == 0 || k == n {
return 1;
}
let k = k.min(n - k); let mut result = 1u64;
for i in 0..k {
result = result.saturating_mul(n as u64 - i as u64) / (i as u64 + 1);
}
result
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BellState {
PhiPlus,
PhiMinus,
PsiPlus,
PsiMinus,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MultiModeFockState {
pub occupation: Vec<usize>,
}
impl MultiModeFockState {
pub fn new(occupation: Vec<usize>) -> Self {
Self { occupation }
}
#[inline]
pub fn n_modes(&self) -> usize {
self.occupation.len()
}
pub fn total_photons(&self) -> usize {
self.occupation.iter().sum()
}
pub fn vacuum(n_modes: usize) -> Self {
Self {
occupation: vec![0; n_modes],
}
}
pub fn single_photon(mode: usize, n_modes: usize) -> Self {
debug_assert!(mode < n_modes, "mode index {mode} >= n_modes {n_modes}");
let mut occ = vec![0usize; n_modes];
if mode < n_modes {
occ[mode] = 1;
}
Self { occupation: occ }
}
pub fn bell_basis_state(kind: BellState) -> Vec<(Complex64, MultiModeFockState)> {
let inv_sqrt2 = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
let neg_inv_sqrt2 = -inv_sqrt2;
let hh = MultiModeFockState::new(vec![1, 0, 1, 0]); let vv = MultiModeFockState::new(vec![0, 1, 0, 1]); let hv = MultiModeFockState::new(vec![1, 0, 0, 1]); let vh = MultiModeFockState::new(vec![0, 1, 1, 0]);
match kind {
BellState::PhiPlus => vec![(inv_sqrt2, hh), (inv_sqrt2, vv)],
BellState::PhiMinus => vec![(inv_sqrt2, hh), (neg_inv_sqrt2, vv)],
BellState::PsiPlus => vec![(inv_sqrt2, hv), (inv_sqrt2, vh)],
BellState::PsiMinus => vec![(inv_sqrt2, hv), (neg_inv_sqrt2, vh)],
}
}
}
#[derive(Debug, Clone)]
pub struct FockSuperposition {
pub terms: Vec<(Complex64, MultiModeFockState)>,
}
impl FockSuperposition {
pub fn new(terms: Vec<(Complex64, MultiModeFockState)>) -> Self {
Self { terms }
}
pub fn norm(&self) -> f64 {
self.terms
.iter()
.map(|(a, _)| a.norm_sqr())
.sum::<f64>()
.sqrt()
}
pub fn normalize(&mut self) {
let n = self.norm();
if n > f64::EPSILON {
for (amp, _) in &mut self.terms {
*amp /= n;
}
}
}
pub fn n_modes(&self) -> usize {
self.terms.first().map(|(_, s)| s.n_modes()).unwrap_or(0)
}
pub fn inner_product(&self, other: &Self) -> Complex64 {
let mut result = Complex64::new(0.0, 0.0);
for (a, sa) in &self.terms {
for (b, sb) in &other.terms {
if sa.occupation == sb.occupation {
result += a.conj() * b;
}
}
}
result
}
pub fn probability(&self, state: &MultiModeFockState) -> f64 {
let amp: Complex64 = self
.terms
.iter()
.filter(|(_, s)| s.occupation == state.occupation)
.map(|(a, _)| *a)
.sum();
amp.norm_sqr()
}
pub fn partial_trace(&self, keep_modes: &[usize]) -> Vec<Vec<Complex64>> {
let mut configs: Vec<Vec<usize>> = Vec::new();
for (_, state) in &self.terms {
let marginal: Vec<usize> = keep_modes
.iter()
.map(|&m| *state.occupation.get(m).unwrap_or(&0))
.collect();
if !configs.contains(&marginal) {
configs.push(marginal);
}
}
let dim = configs.len();
let mut rho = vec![vec![Complex64::new(0.0, 0.0); dim]; dim];
let all_modes = self.n_modes();
let env_modes: Vec<usize> = (0..all_modes).filter(|m| !keep_modes.contains(m)).collect();
let mut env_configs: Vec<Vec<usize>> = Vec::new();
for (_, state) in &self.terms {
let env_occ: Vec<usize> = env_modes
.iter()
.map(|&m| *state.occupation.get(m).unwrap_or(&0))
.collect();
if !env_configs.contains(&env_occ) {
env_configs.push(env_occ);
}
}
for env_occ in &env_configs {
for (i, cfg_i) in configs.iter().enumerate() {
let amp_i: Complex64 = self
.terms
.iter()
.filter(|(_, s)| {
let sys: Vec<usize> = keep_modes.iter().map(|&m| s.occupation[m]).collect();
let env: Vec<usize> = env_modes.iter().map(|&m| s.occupation[m]).collect();
sys == *cfg_i && env == *env_occ
})
.map(|(a, _)| *a)
.sum();
for (j, cfg_j) in configs.iter().enumerate() {
let amp_j: Complex64 = self
.terms
.iter()
.filter(|(_, s)| {
let sys: Vec<usize> =
keep_modes.iter().map(|&m| s.occupation[m]).collect();
let env: Vec<usize> =
env_modes.iter().map(|&m| s.occupation[m]).collect();
sys == *cfg_j && env == *env_occ
})
.map(|(a, _)| *a)
.sum();
rho[i][j] += amp_i * amp_j.conj();
}
}
}
rho
}
pub fn entanglement_entropy(&self, mode_a: &[usize]) -> f64 {
let rho = self.partial_trace(mode_a);
let dim = rho.len();
if dim == 0 {
return 0.0;
}
let eigenvalues = jacobi_eigenvalues_hermitian(&rho);
eigenvalues
.iter()
.filter(|&&ev| ev > 1e-15)
.map(|&ev| -ev * ev.ln())
.sum()
}
}
fn jacobi_eigenvalues_hermitian(a: &[Vec<Complex64>]) -> Vec<f64> {
let n = a.len();
if n == 0 {
return vec![];
}
if n == 1 {
return vec![a[0][0].re];
}
let mut diag: Vec<f64> = (0..n).map(|i| a[i][i].re).collect();
let off_diag_sq: f64 = (0..n)
.flat_map(|i| (0..n).map(move |j| (i, j)))
.filter(|&(i, j)| i != j)
.map(|(i, j)| a[i][j].norm_sqr())
.sum();
if off_diag_sq < 1e-28 {
return diag;
}
let mut sym: Vec<Vec<f64>> = (0..n)
.map(|i| {
(0..n)
.map(|j| {
if i == j {
a[i][j].re
} else {
a[i][j].re
}
})
.collect()
})
.collect();
let max_iter = 200 * n * n;
for _ in 0..max_iter {
let mut max_val = 0.0_f64;
let (mut p, mut q) = (0, 1);
for (i, row) in sym.iter().enumerate().take(n) {
for (j, _) in row.iter().enumerate().take(n).skip(i + 1) {
let v = sym[i][j].abs();
if v > max_val {
max_val = v;
p = i;
q = j;
}
}
}
if max_val < 1e-14 {
break;
}
let theta = 0.5 * (sym[q][q] - sym[p][p]).atan2(2.0 * sym[p][q]);
let (s, c) = theta.sin_cos();
let app = sym[p][p];
let aqq = sym[q][q];
let apq = sym[p][q];
sym[p][p] = c * c * app + s * s * aqq - 2.0 * s * c * apq;
sym[q][q] = s * s * app + c * c * aqq + 2.0 * s * c * apq;
sym[p][q] = 0.0;
sym[q][p] = 0.0;
for (r, _) in (0..n).zip(std::iter::repeat(())) {
if r != p && r != q {
let arp = sym[r][p];
let arq = sym[r][q];
sym[r][p] = c * arp - s * arq;
sym[p][r] = sym[r][p];
sym[r][q] = s * arp + c * arq;
sym[q][r] = sym[r][q];
}
}
}
diag = (0..n).map(|i| sym[i][i]).collect();
diag
}
pub struct PnrdMeasurement {
pub detection_efficiency: f64,
pub dark_count_rate: f64,
}
impl PnrdMeasurement {
pub fn new(eta: f64, dcr: f64) -> Self {
Self {
detection_efficiency: eta.clamp(0.0, 1.0),
dark_count_rate: dcr.max(0.0),
}
}
pub fn detection_probability(&self, n_detected: usize, n_incident: usize) -> f64 {
if n_detected > n_incident {
return 0.0;
}
let eta = self.detection_efficiency;
let c = binom(n_incident, n_detected) as f64;
let k = n_detected as f64;
let mk = (n_incident - n_detected) as f64;
c * eta.powf(k) * (1.0 - eta).powf(mk)
}
#[inline]
pub fn mean_detected(&self, n_incident: usize) -> f64 {
self.detection_efficiency * n_incident as f64
}
pub fn post_measurement_state(
&self,
state: &FockSuperposition,
mode: usize,
n_detected: usize,
) -> FockSuperposition {
let terms: Vec<(Complex64, MultiModeFockState)> = state
.terms
.iter()
.filter_map(|(amp, fock)| {
let n_in_mode = *fock.occupation.get(mode)?;
let prob = self.detection_probability(n_detected, n_in_mode);
if prob < f64::EPSILON {
return None;
}
Some((*amp * prob.sqrt(), fock.clone()))
})
.collect();
let mut result = FockSuperposition::new(terms);
result.normalize();
result
}
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn test_vacuum_state() {
let v = MultiModeFockState::vacuum(4);
assert_eq!(v.n_modes(), 4);
assert_eq!(v.total_photons(), 0);
}
#[test]
fn test_single_photon_state() {
let s = MultiModeFockState::single_photon(2, 5);
assert_eq!(s.occupation[2], 1);
assert_eq!(s.total_photons(), 1);
for i in [0, 1, 3, 4] {
assert_eq!(s.occupation[i], 0);
}
}
#[test]
fn test_bell_state_phi_plus_normalised() {
let terms = MultiModeFockState::bell_basis_state(BellState::PhiPlus);
let sup = FockSuperposition::new(terms);
let norm = sup.norm();
assert!(approx_eq(norm, 1.0, 1e-12));
}
#[test]
fn test_bell_states_orthogonal() {
let phi_plus =
FockSuperposition::new(MultiModeFockState::bell_basis_state(BellState::PhiPlus));
let phi_minus =
FockSuperposition::new(MultiModeFockState::bell_basis_state(BellState::PhiMinus));
let ip = phi_plus.inner_product(&phi_minus);
assert!(ip.norm() < 1e-12);
}
#[test]
fn test_fock_superposition_probability() {
let inv_sqrt2 = 1.0 / 2.0_f64.sqrt();
let s1 = MultiModeFockState::new(vec![1, 0]);
let s2 = MultiModeFockState::new(vec![0, 1]);
let sup = FockSuperposition::new(vec![
(Complex64::new(inv_sqrt2, 0.0), s1.clone()),
(Complex64::new(inv_sqrt2, 0.0), s2.clone()),
]);
assert!(approx_eq(sup.probability(&s1), 0.5, 1e-12));
assert!(approx_eq(sup.probability(&s2), 0.5, 1e-12));
}
#[test]
fn test_pnrd_detection_probability_perfect() {
let det = PnrdMeasurement::new(1.0, 0.0);
assert!(approx_eq(det.detection_probability(3, 3), 1.0, 1e-12));
assert!(approx_eq(det.detection_probability(2, 3), 0.0, 1e-12));
assert!(approx_eq(det.detection_probability(4, 3), 0.0, 1e-12));
}
#[test]
fn test_pnrd_detection_probability_50pct() {
let det = PnrdMeasurement::new(0.5, 0.0);
assert!(approx_eq(det.detection_probability(1, 2), 0.5, 1e-12));
assert!(approx_eq(det.detection_probability(0, 2), 0.25, 1e-12));
}
#[test]
fn test_pnrd_mean_detected() {
let det = PnrdMeasurement::new(0.8, 0.0);
assert!(approx_eq(det.mean_detected(5), 4.0, 1e-12));
}
#[test]
fn test_entanglement_entropy_product_state() {
let s = MultiModeFockState::new(vec![1, 0, 0, 1]);
let sup = FockSuperposition::new(vec![(Complex64::new(1.0, 0.0), s)]);
let entropy = sup.entanglement_entropy(&[0, 1]);
assert!(entropy < 1e-10);
}
#[test]
fn test_entanglement_entropy_bell_state() {
let terms = MultiModeFockState::bell_basis_state(BellState::PhiPlus);
let sup = FockSuperposition::new(terms);
let entropy = sup.entanglement_entropy(&[0, 1]);
assert!(approx_eq(entropy, 2.0_f64.ln(), 0.05));
}
#[test]
fn test_binom() {
assert_eq!(binom(5, 2), 10);
assert_eq!(binom(10, 3), 120);
assert_eq!(binom(0, 0), 1);
assert_eq!(binom(3, 5), 0);
}
#[test]
fn test_post_measurement_collapses_state() {
let inv_sqrt2 = 1.0 / 2.0_f64.sqrt();
let s1 = MultiModeFockState::new(vec![2, 0]);
let s2 = MultiModeFockState::new(vec![0, 1]);
let sup = FockSuperposition::new(vec![
(Complex64::new(inv_sqrt2, 0.0), s1),
(Complex64::new(inv_sqrt2, 0.0), s2),
]);
let det = PnrdMeasurement::new(1.0, 0.0); let post = det.post_measurement_state(&sup, 0, 0);
let s2b = MultiModeFockState::new(vec![0, 1]);
assert!(approx_eq(post.probability(&s2b), 1.0, 1e-10));
}
}