use super::{FockBasis, FockDensity, PhotonSector};
use crate::{ComplexTensor, Error, ResourceLimits, Result};
use burn::tensor::{DType, Device, Tensor, TensorData};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(try_from = "SourceData", into = "SourceData")]
pub struct CalibratedSource(SourceData);
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct SourceData {
probabilities: Vec<f64>,
calibration: String,
}
impl CalibratedSource {
pub fn new(probabilities: Vec<f64>, calibration: String) -> Result<Self> {
Self::try_from(SourceData {
probabilities,
calibration,
})
}
pub fn vacuum_single_pair(mean: f64, g2: f64, calibration: String) -> Result<Self> {
if !mean.is_finite() || !g2.is_finite() || mean < 0. || g2 < 0. {
return Err(Error::InvalidValue("invalid source moments".into()));
}
let p2 = 0.5 * g2 * mean * mean;
let p1 = mean - 2. * p2;
Self::new(vec![1. - p1 - p2, p1, p2], calibration)
}
pub fn probabilities(&self) -> &[f64] {
&self.0.probabilities
}
pub fn calibration(&self) -> &str {
&self.0.calibration
}
pub fn mean(&self) -> f64 {
self.0
.probabilities
.iter()
.enumerate()
.map(|(n, p)| n as f64 * p)
.sum()
}
pub fn emission_probability(&self) -> f64 {
1. - self.0.probabilities[0]
}
pub fn g2(&self) -> Option<f64> {
let mean = self.mean();
(mean > 0.).then(|| {
self.0
.probabilities
.iter()
.enumerate()
.map(|(n, p)| n.saturating_sub(1) as f64 * n as f64 * p)
.sum::<f64>()
/ (mean * mean)
})
}
}
impl TryFrom<SourceData> for CalibratedSource {
type Error = Error;
fn try_from(v: SourceData) -> Result<Self> {
if v.probabilities.is_empty()
|| v.probabilities.len() > 65
|| v.calibration.is_empty()
|| v.calibration.len() > 4096
|| v.probabilities
.iter()
.any(|p| !p.is_finite() || !(0.0..=1.).contains(p))
|| (v.probabilities.iter().sum::<f64>() - 1.).abs() > 1e-12
{
return Err(Error::InvalidValue(
"source needs a complete probability distribution and calibration identity".into(),
));
}
Ok(Self(v))
}
}
impl From<CalibratedSource> for SourceData {
fn from(v: CalibratedSource) -> Self {
v.0
}
}
impl FockDensity {
pub fn source_product(
basis: FockBasis,
sources: &[CalibratedSource],
batch: usize,
device: &Device,
dtype: DType,
limits: ResourceLimits,
) -> Result<Self> {
if sources.len() != basis.modes() {
return Err(Error::Shape(
"one photon-number source is required per fine mode".into(),
));
}
let minimum: usize = sources
.iter()
.map(|s| s.probabilities().iter().position(|p| *p > 0.).unwrap())
.sum();
let maximum: usize = sources
.iter()
.map(|s| s.probabilities().iter().rposition(|p| *p > 0.).unwrap())
.sum();
let contains_support = match basis.sector() {
PhotonSector::Fixed(n) => minimum == n && maximum == n,
PhotonSector::UpTo(n) => maximum <= n,
};
if !contains_support {
return Err(Error::Unsupported(
"source support exceeds the supplied Fock basis; preserve missing sectors".into(),
));
}
let d = basis.size();
let size = d.checked_mul(d).ok_or(Error::Overflow)?;
limits.check_state(batch, size)?;
let weights: Vec<f64> = basis
.occupations()
.iter()
.map(|o| {
o.iter()
.zip(sources)
.map(|(&n, s)| s.probabilities().get(n).copied().unwrap_or(0.))
.product()
})
.collect();
if (weights.iter().sum::<f64>() - 1.).abs() > 1e-12 {
return Err(Error::Unsupported(
"source support exceeds the supplied Fock basis; preserve missing sectors".into(),
));
}
let mut entries = vec![0.; batch * size];
for b in 0..batch {
for (i, &p) in weights.iter().enumerate() {
entries[b * size + i * d + i] = p;
}
}
let re = Tensor::from_data(TensorData::new(entries, [batch, d, d]), (device, dtype));
Self::from_entries(
basis,
ComplexTensor::new(re.clone(), re.zeros_like())?,
limits,
)
}
}