use super::{Detector, FockBasis, OpticalBellInstrument, PhotonSector};
use crate::{Error, Result};
use num_complex::Complex64;
use std::collections::BTreeMap;
#[derive(Clone, Debug)]
pub struct GriceNoiseConfig {
pub rail_transmissions: [[f64; 2]; 4],
pub rail_phases: [[f64; 2]; 4],
pub wavepackets: [Vec<Complex64>; 4],
pub detectors: [Detector; 8],
pub calibration: String,
pub max_terms: usize,
pub max_work: usize,
}
impl Default for GriceNoiseConfig {
fn default() -> Self {
Self {
rail_transmissions: [[1.; 2]; 4],
rail_phases: [[0.; 2]; 4],
wavepackets: std::array::from_fn(|_| vec![1.0.into()]),
detectors: [Detector::ideal(4).expect("valid resolution"); 8],
calibration: "ideal-grice-explicit-environment-v1".into(),
max_terms: 262_144,
max_work: 1 << 28,
}
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
struct Row {
lost_rails: u8,
occupation: Vec<u8>,
}
struct Budget {
used: usize,
maximum: usize,
}
impl Budget {
fn use_work(&mut self, count: usize) -> Result<()> {
self.used = self.used.checked_add(count).ok_or(Error::Overflow)?;
if self.used > self.maximum {
return Err(Error::ResourceLimit {
resource: "calibrated Grice scalar work",
required: self.used,
limit: self.maximum,
});
}
Ok(())
}
}
impl OpticalBellInstrument {
pub fn grice_calibrated(config: GriceNoiseConfig) -> Result<Self> {
validate(&config)?;
let mut budget = Budget {
used: 0,
maximum: config.max_work,
};
let labels = ideal_labels(&mut budget, config.max_terms)?;
let rows = amplitudes(&config, &mut budget)?;
let internal = config.wavepackets[0].len();
let mut responses = BTreeMap::<Vec<usize>, [f64; 6]>::new();
let mut effects = vec![vec![Complex64::new(0., 0.); 81]; 6];
for (row, amplitude) in rows {
let occupation: Vec<_> = row
.occupation
.chunks_exact(internal)
.map(|c| c.iter().map(|&n| n as usize).sum())
.collect();
if !responses.contains_key(&occupation) {
responses.insert(
occupation.clone(),
record_weights(&occupation, &config.detectors, &labels, &mut budget)?,
);
}
let weights = &responses[&occupation];
budget.use_work(6 * 81)?;
for (effect, &weight) in effects.iter_mut().zip(weights) {
if weight == 0. {
continue;
}
for a in 0..9 {
for b in 0..9 {
effect[a * 9 + b] += amplitude[a].conj() * amplitude[b] * weight;
}
}
}
}
Self::from_effects(super::bell::grice_records(), effects, config.calibration, 2)
}
}
fn validate(c: &GriceNoiseConfig) -> Result<()> {
let internal = c.wavepackets[0].len();
if !(1..=4).contains(&internal)
|| c.max_terms == 0
|| c.max_work == 0
|| c.calibration.is_empty()
|| c.calibration.len() > 4096
|| c.rail_transmissions
.iter()
.flatten()
.any(|p| !p.is_finite() || !(0.0..=1.).contains(p))
|| c.rail_phases.iter().flatten().any(|p| !p.is_finite())
|| c.wavepackets.iter().any(|p| {
p.len() != internal
|| p.iter().any(|z| !z.re.is_finite() || !z.im.is_finite())
|| (p.iter().map(|z| z.norm_sqr()).sum::<f64>() - 1.).abs() > 1e-10
})
{
return Err(Error::InvalidValue(
"invalid calibrated Grice optical model".into(),
));
}
Ok(())
}
fn amplitudes(c: &GriceNoiseConfig, budget: &mut Budget) -> Result<BTreeMap<Row, [Complex64; 9]>> {
let unitary = OpticalBellInstrument::grice_interferometer()?;
let internal = c.wavepackets[0].len();
let mut rows = BTreeMap::<Row, [Complex64; 9]>::new();
for a in 0..3 {
for b in 0..3 {
for ancillary in 0..2 {
let ports = [a, b, ancillary, ancillary];
let present: Vec<_> = (0..4).filter(|&p| ports[p] < 2).collect();
for mask in 0..1usize << present.len() {
let mut coefficient = Complex64::new(std::f64::consts::FRAC_1_SQRT_2, 0.);
let mut lost_rails = 0u8;
let mut surviving = vec![];
for (bit, &port) in present.iter().enumerate() {
let rail = ports[port];
let eta = c.rail_transmissions[port][rail];
coefficient *= Complex64::from_polar(1., c.rail_phases[port][rail]);
if mask & (1 << bit) == 0 {
coefficient *= eta.sqrt();
surviving.push((port, rail));
} else {
coefficient *= (1. - eta).sqrt();
lost_rails |= 1 << (2 * port + rail);
}
}
if coefficient.norm_sqr() == 0. {
continue;
}
let mut polynomial = BTreeMap::from([(vec![0u8; 8 * internal], coefficient)]);
for (port, rail) in surviving {
let mut next = BTreeMap::<Vec<u8>, Complex64>::new();
for (occupation, value) in polynomial {
for output in 0..8 {
for label in 0..internal {
let amplitude = unitary.matrix().entries()
[output * 8 + 2 * port + rail]
* c.wavepackets[port][label];
if amplitude.norm_sqr() == 0. {
continue;
}
budget.use_work(1)?;
let mode = output * internal + label;
let mut updated = occupation.clone();
updated[mode] += 1;
let factor = (updated[mode] as f64).sqrt();
*next.entry(updated).or_default() += value * amplitude * factor;
if next.len() > c.max_terms {
return Err(Error::ResourceLimit {
resource: "Grice polynomial terms",
required: next.len(),
limit: c.max_terms,
});
}
}
}
}
polynomial = next;
}
for (occupation, value) in polynomial {
if value.norm_sqr() == 0. {
continue;
}
let row = Row {
lost_rails,
occupation,
};
rows.entry(row).or_insert([Complex64::new(0., 0.); 9])[a * 3 + b] += value;
if rows.len() > c.max_terms {
return Err(Error::ResourceLimit {
resource: "Grice environment rows",
required: rows.len(),
limit: c.max_terms,
});
}
}
}
}
}
}
Ok(rows)
}
fn ideal_labels(budget: &mut Budget, max_terms: usize) -> Result<Vec<(Vec<usize>, usize)>> {
let rows = amplitudes(
&GriceNoiseConfig {
max_terms,
..Default::default()
},
budget,
)?;
let mut labels: Vec<(Vec<usize>, usize)> = vec![];
for (row, a) in rows {
if row.occupation.iter().map(|&n| n as usize).sum::<usize>() != 4 {
continue;
}
let bell = [a[1] + a[3], a[1] - a[3], a[0] + a[4], a[0] - a[4]];
let support: Vec<_> = bell
.iter()
.enumerate()
.filter(|(_, v)| v.norm_sqr() > 1e-12)
.map(|(i, _)| i)
.collect();
let label = match support.as_slice() {
[] => continue,
[only] => *only,
[2, 3] => 4,
_ => {
return Err(Error::InvalidValue(
"invalid ideal Grice classifier support".into(),
));
}
};
labels.push((row.occupation.iter().map(|&n| n as usize).collect(), label));
}
let basis = FockBasis::new(8, PhotonSector::Fixed(4), 330)?;
for (occupation, _) in &labels {
basis.index(occupation)?;
}
Ok(labels)
}
fn record_weights(
occupation: &[usize],
detectors: &[Detector; 8],
labels: &[(Vec<usize>, usize)],
budget: &mut Budget,
) -> Result<[f64; 6]> {
let responses = detectors
.iter()
.zip(occupation)
.map(|(d, &n)| d.response(n))
.collect::<Result<Vec<_>>>()?;
let mut weights = [0.; 6];
budget.use_work(labels.len() * 8)?;
for (record, label) in labels {
weights[*label] += record
.iter()
.zip(&responses)
.map(|(&n, p)| p.get(n).copied().unwrap_or(0.))
.product::<f64>();
}
let accepted = weights[..5].iter().sum::<f64>();
if accepted > 1. + 1e-10 {
return Err(Error::InvalidValue(
"Grice detector response exceeds one".into(),
));
}
weights[5] = (1. - accepted).max(0.);
Ok(weights)
}