#![cfg(feature = "interconnect")]
use crate::interconnect::sparam_link::SiPhLink;
const C_LIGHT: f64 = 2.997_924_58e8;
#[derive(Debug, Clone)]
pub struct WdmCh {
pub wavelength_nm: f64,
pub power_dbm: f64,
}
impl WdmCh {
pub fn frequency_hz(&self) -> f64 {
C_LIGHT / (self.wavelength_nm * 1e-9)
}
}
#[derive(Debug, Clone)]
pub struct WdmCrosstalkMatrix {
pub channels: Vec<WdmCh>,
pub crosstalk_matrix_db: Vec<Vec<f64>>,
}
impl WdmCrosstalkMatrix {
pub fn from_link(
link: &SiPhLink,
channels: Vec<WdmCh>,
filter_rejection_db_per_nm: f64,
) -> Self {
let n = channels.len();
let freqs: Vec<f64> = channels.iter().map(|ch| ch.frequency_hz()).collect();
let sp = if freqs.is_empty() {
Vec::new()
} else {
link.cascade(&freqs)
};
let link_il_db: Vec<f64> = sp
.iter()
.map(|p| {
let mag = p[1].norm();
if mag < 1e-40 {
400.0
} else {
-20.0 * mag.log10()
}
})
.collect();
let mut matrix: Vec<Vec<f64>> = vec![vec![f64::NEG_INFINITY; n]; n];
for i in 0..n {
for j in 0..n {
if i == j {
matrix[i][j] = 0.0;
} else {
let delta_lambda_nm =
(channels[i].wavelength_nm - channels[j].wavelength_nm).abs();
let isolation_db = filter_rejection_db_per_nm * delta_lambda_nm;
let signal_power_dbm = channels[i].power_dbm - link_il_db[i];
let interferer_power_dbm = channels[j].power_dbm - link_il_db[j] - isolation_db;
matrix[i][j] = interferer_power_dbm - signal_power_dbm;
}
}
}
Self {
channels,
crosstalk_matrix_db: matrix,
}
}
pub fn crosstalk_penalty_db(&self, channel_idx: usize) -> f64 {
let n = self.channels.len();
if channel_idx >= n || n < 2 {
return 0.0;
}
let total_xt_linear: f64 = (0..n)
.filter(|&j| j != channel_idx)
.map(|j| {
let xt_db = self.crosstalk_matrix_db[channel_idx][j];
if xt_db.is_finite() {
10.0_f64.powf(xt_db / 10.0)
} else {
0.0 }
})
.sum();
if total_xt_linear <= 0.0 {
return 0.0;
}
if total_xt_linear >= 1.0 {
return 30.0; }
-10.0 * (1.0 - total_xt_linear).log10()
}
pub fn n_channels(&self) -> usize {
self.channels.len()
}
pub fn total_interference_dbm(&self, channel_idx: usize) -> f64 {
let n = self.channels.len();
if channel_idx >= n {
return f64::NEG_INFINITY;
}
let signal_power_dbm = self.channels[channel_idx].power_dbm;
let sum_linear: f64 = (0..n)
.filter(|&j| j != channel_idx)
.map(|j| {
let xt_db = self.crosstalk_matrix_db[channel_idx][j];
let xt_abs_dbm = signal_power_dbm + xt_db;
if xt_abs_dbm.is_finite() {
10.0_f64.powf(xt_abs_dbm / 10.0)
} else {
0.0
}
})
.sum();
if sum_linear <= 0.0 {
return f64::NEG_INFINITY;
}
10.0 * sum_linear.log10()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::interconnect::sparam_link::SiPhLink;
#[test]
fn crosstalk_matrix_is_n_by_n() {
let channels = vec![
WdmCh {
wavelength_nm: 1550.0,
power_dbm: 0.0,
},
WdmCh {
wavelength_nm: 1550.8,
power_dbm: 0.0,
},
WdmCh {
wavelength_nm: 1551.6,
power_dbm: 0.0,
},
];
let link = SiPhLink::new();
let mat = WdmCrosstalkMatrix::from_link(&link, channels, 20.0);
for row in &mat.crosstalk_matrix_db {
assert_eq!(row.len(), 3);
}
assert_eq!(mat.crosstalk_matrix_db.len(), 3);
}
#[test]
fn diagonal_is_zero() {
let channels = vec![
WdmCh {
wavelength_nm: 1549.2,
power_dbm: 0.0,
},
WdmCh {
wavelength_nm: 1550.0,
power_dbm: 0.0,
},
WdmCh {
wavelength_nm: 1550.8,
power_dbm: 0.0,
},
];
let link = SiPhLink::new();
let mat = WdmCrosstalkMatrix::from_link(&link, channels, 20.0);
for i in 0..3 {
assert!(
(mat.crosstalk_matrix_db[i][i] - 0.0).abs() < 1e-12,
"Diagonal[{i}] should be 0, got {}",
mat.crosstalk_matrix_db[i][i]
);
}
}
#[test]
fn crosstalk_decreases_with_channel_separation() {
let channels = vec![
WdmCh {
wavelength_nm: 1550.0,
power_dbm: 0.0,
},
WdmCh {
wavelength_nm: 1550.8,
power_dbm: 0.0,
},
WdmCh {
wavelength_nm: 1552.0,
power_dbm: 0.0,
},
];
let link = SiPhLink::new();
let mat = WdmCrosstalkMatrix::from_link(&link, channels, 20.0);
let xt_near = mat.crosstalk_matrix_db[0][1];
let xt_far = mat.crosstalk_matrix_db[0][2];
assert!(
xt_near > xt_far,
"Near channel crosstalk should be higher (less suppressed): xt_near={xt_near:.1}, xt_far={xt_far:.1}"
);
}
#[test]
fn penalty_zero_for_single_channel() {
let channels = vec![WdmCh {
wavelength_nm: 1550.0,
power_dbm: 0.0,
}];
let link = SiPhLink::new();
let mat = WdmCrosstalkMatrix::from_link(&link, channels, 20.0);
let penalty = mat.crosstalk_penalty_db(0);
assert!(
penalty.abs() < 1e-10,
"Single channel should have zero penalty, got {penalty}"
);
}
}