use num_complex::Complex64;
use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct OpticalFirFilter {
pub tap_weights: Vec<Complex64>,
pub delay_per_tap: f64,
pub center_frequency: f64,
}
impl OpticalFirFilter {
pub fn new(weights: Vec<Complex64>, delay: f64, fc: f64) -> Self {
Self {
tap_weights: weights,
delay_per_tap: delay,
center_frequency: fc,
}
}
pub fn transfer_function(&self, omega: f64) -> Complex64 {
self.tap_weights
.iter()
.enumerate()
.fold(Complex64::new(0.0, 0.0), |acc, (k, &w)| {
let phase = -(k as f64) * omega * self.delay_per_tap;
acc + w * Complex64::new(phase.cos(), phase.sin())
})
}
pub fn response_db(&self, freq_hz: f64) -> f64 {
let omega = 2.0 * PI * freq_hz;
let h = self.transfer_function(omega);
let power = h.norm_sqr();
if power <= 0.0 {
-300.0
} else {
10.0 * power.log10()
}
}
pub fn group_delay_s(&self, omega: f64) -> f64 {
let delta = 1e3; let h_plus = self.transfer_function(omega + delta);
let h_minus = self.transfer_function(omega - delta);
let phase_plus = h_plus.im.atan2(h_plus.re);
let phase_minus = h_minus.im.atan2(h_minus.re);
let mut dphi = phase_plus - phase_minus;
while dphi > PI {
dphi -= 2.0 * PI;
}
while dphi < -PI {
dphi += 2.0 * PI;
}
-dphi / (2.0 * delta)
}
pub fn lowpass(n_taps: usize, cutoff_hz: f64, delay: f64, fc: f64) -> Self {
let mut weights: Vec<Complex64> = Vec::with_capacity(n_taps);
let m = n_taps as f64 - 1.0;
let wc = 2.0 * PI * cutoff_hz * delay; for n in 0..n_taps {
let nf = n as f64;
let sinc = if (nf - m / 2.0).abs() < 1e-12 {
wc / PI
} else {
(wc * (nf - m / 2.0)).sin() / (PI * (nf - m / 2.0))
};
let window = 0.54 - 0.46 * (2.0 * PI * nf / m).cos();
weights.push(Complex64::new(sinc * window, 0.0));
}
Self::new(weights, delay, fc)
}
pub fn bandpass(n_taps: usize, f_pass: f64, bw_hz: f64, delay: f64, fc: f64) -> Self {
let mut weights: Vec<Complex64> = Vec::with_capacity(n_taps);
let m = n_taps as f64 - 1.0;
let wc = 2.0 * PI * (bw_hz / 2.0) * delay;
let w0 = 2.0 * PI * f_pass * delay;
for n in 0..n_taps {
let nf = n as f64;
let x = nf - m / 2.0;
let sinc = if x.abs() < 1e-12 {
wc / PI
} else {
(wc * x).sin() / (PI * x)
};
let window = 0.54 - 0.46 * (2.0 * PI * nf / m).cos();
let modulated = sinc * window * (w0 * x).cos() * 2.0;
weights.push(Complex64::new(modulated, 0.0));
}
Self::new(weights, delay, fc)
}
pub fn filter(&self, signal: &[Complex64]) -> Vec<Complex64> {
let n = signal.len();
let m = self.tap_weights.len();
let mut output = vec![Complex64::new(0.0, 0.0); n];
for i in 0..n {
for (k, &w) in self.tap_weights.iter().enumerate() {
if i >= k {
output[i] += w * signal[i - k];
}
}
}
if n > m {
output[m - 1..].to_vec()
} else {
output
}
}
pub fn free_spectral_range(&self) -> f64 {
if self.delay_per_tap > 0.0 {
1.0 / self.delay_per_tap
} else {
f64::INFINITY
}
}
}
#[derive(Debug, Clone)]
pub struct RingResonatorFilter {
pub coupling_coefficients: Vec<f64>,
pub round_trip_loss: Vec<f64>,
pub round_trip_phases: Vec<f64>,
pub fsr: f64,
}
impl RingResonatorFilter {
pub fn new_single(kappa: f64, alpha: f64, phi: f64, fsr: f64) -> Self {
Self {
coupling_coefficients: vec![kappa],
round_trip_loss: vec![alpha],
round_trip_phases: vec![phi],
fsr,
}
}
pub fn new_coupled_resonators(kappas: Vec<f64>, alphas: Vec<f64>, fsr: f64) -> Self {
let n = kappas.len();
let phases = vec![0.0_f64; n];
Self {
coupling_coefficients: kappas,
round_trip_loss: alphas,
round_trip_phases: phases,
fsr,
}
}
pub fn transfer_function(&self, freq_offset: f64) -> Complex64 {
let n = self.coupling_coefficients.len();
let mut h = Complex64::new(1.0, 0.0);
for i in 0..n {
let kappa = self.coupling_coefficients[i].clamp(0.0, 1.0);
let alpha = self.round_trip_loss[i].clamp(0.0, 1.0);
let phi_rt = self.round_trip_phases[i] + 2.0 * PI * freq_offset / self.fsr;
let t = (1.0 - kappa).sqrt();
let exp_jrt = Complex64::new(phi_rt.cos(), phi_rt.sin());
let numerator = Complex64::new(t, 0.0) - alpha * exp_jrt;
let denominator = Complex64::new(1.0, 0.0) - t * alpha * exp_jrt;
if denominator.norm() > 1e-30 {
h *= numerator / denominator;
}
}
h
}
pub fn extinction_ratio_db(&self) -> f64 {
let h_on = self.transfer_function(0.0);
let h_off = self.transfer_function(self.fsr / 2.0);
let er = h_off.norm_sqr() / h_on.norm_sqr().max(1e-30);
10.0 * er.max(1e-30).log10()
}
pub fn bandwidth_hz(&self) -> f64 {
let peak = self.transfer_function(0.0).norm_sqr();
let target = peak / 2.0;
let half_fsr = self.fsr / 2.0;
if self.transfer_function(half_fsr).norm_sqr() > target {
return self.fsr;
}
let mut lo = 0.0_f64;
let mut hi = half_fsr;
for _ in 0..64 {
let mid = (lo + hi) / 2.0;
if self.transfer_function(mid).norm_sqr() > target {
lo = mid;
} else {
hi = mid;
}
}
(2.0 * hi).min(self.fsr) }
pub fn q_factor(&self) -> f64 {
let bw = self.bandwidth_hz();
if bw > 0.0 {
self.fsr / bw
} else {
f64::INFINITY
}
}
pub fn group_delay_peak_s(&self) -> f64 {
let bw = self.bandwidth_hz();
if bw > 0.0 {
1.0 / (PI * bw)
} else {
f64::INFINITY
}
}
}
#[derive(Debug, Clone)]
pub enum EqAlgorithm {
Lms { mu: f64 },
Rls { lambda: f64 },
Cma { mu: f64 },
Dd { mu: f64 },
}
#[derive(Debug, Clone)]
pub struct OpticalEqualizer {
pub n_taps: usize,
pub tap_spacing_ps: f64,
pub taps: Vec<Complex64>,
pub adaptation_algorithm: EqAlgorithm,
}
impl OpticalEqualizer {
pub fn new(n_taps: usize, tap_spacing_ps: f64, algo: EqAlgorithm) -> Self {
let mut taps = vec![Complex64::new(0.0, 0.0); n_taps];
if n_taps > 0 {
taps[n_taps / 2] = Complex64::new(1.0, 0.0);
}
Self {
n_taps,
tap_spacing_ps,
taps,
adaptation_algorithm: algo,
}
}
pub fn apply(&self, signal: &[Complex64]) -> Vec<Complex64> {
let n = signal.len();
let m = self.taps.len();
let mut output = vec![Complex64::new(0.0, 0.0); n];
for i in 0..n {
for (k, &tap) in self.taps.iter().enumerate() {
if i >= k {
output[i] += tap * signal[i - k];
}
}
}
if n > m {
output[m - 1..].to_vec()
} else {
output
}
}
pub fn update_lms(&mut self, input: &[Complex64], error: Complex64) -> Complex64 {
let mu = match &self.adaptation_algorithm {
EqAlgorithm::Lms { mu } => *mu,
EqAlgorithm::Dd { mu } => *mu,
_ => 1e-3,
};
let start = if input.len() >= self.n_taps {
input.len() - self.n_taps
} else {
0
};
let x_slice = &input[start..];
for (k, tap) in self.taps.iter_mut().enumerate() {
if k < x_slice.len() {
*tap += mu * error * x_slice[x_slice.len() - 1 - k].conj();
}
}
x_slice
.iter()
.rev()
.zip(self.taps.iter())
.fold(Complex64::new(0.0, 0.0), |acc, (&xi, &wi)| acc + wi * xi)
}
pub fn update_cma(&mut self, input: &[Complex64]) -> Complex64 {
let mu = match &self.adaptation_algorithm {
EqAlgorithm::Cma { mu } => *mu,
_ => 1e-4,
};
let start = if input.len() >= self.n_taps {
input.len() - self.n_taps
} else {
0
};
let x_slice = &input[start..];
let y = x_slice
.iter()
.rev()
.zip(self.taps.iter())
.fold(Complex64::new(0.0, 0.0), |acc, (&xi, &wi)| acc + wi * xi);
let error = y * (1.0 - y.norm_sqr());
for (k, tap) in self.taps.iter_mut().enumerate() {
if k < x_slice.len() {
*tap += mu * error * x_slice[x_slice.len() - 1 - k].conj();
}
}
y
}
pub fn max_cd_compensation_ps_per_nm(&self, symbol_rate_gbaud: f64) -> f64 {
let tap_span = self.tap_span_ps();
let lambda_nm = 1550.0;
let c_nm_ps = 3e8 * 1e9 / 1e12; let bandwidth_nm = symbol_rate_gbaud * 1e9 * (lambda_nm * lambda_nm) / (c_nm_ps * 1e12);
if bandwidth_nm > 0.0 {
tap_span / bandwidth_nm
} else {
0.0
}
}
pub fn tap_span_ps(&self) -> f64 {
self.n_taps as f64 * self.tap_spacing_ps
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn fir_transfer_dc() {
let n = 5_usize;
let taps = vec![Complex64::new(1.0, 0.0); n];
let fir = OpticalFirFilter::new(taps, 1e-12, 193.4e12);
let h0 = fir.transfer_function(0.0);
assert_abs_diff_eq!(h0.re, n as f64, epsilon = 1e-10);
assert_abs_diff_eq!(h0.im, 0.0, epsilon = 1e-10);
}
#[test]
fn fir_fsr() {
let delay_ps = 10e-12;
let fir = OpticalFirFilter::new(vec![Complex64::new(1.0, 0.0)], delay_ps, 193.4e12);
let expected_fsr = 1.0 / delay_ps;
assert_abs_diff_eq!(fir.free_spectral_range(), expected_fsr, epsilon = 1.0);
}
#[test]
fn fir_lowpass_dc_passband() {
let fir = OpticalFirFilter::lowpass(31, 10e9, 10e-12, 193.4e12);
let h0 = fir.transfer_function(0.0);
assert!(h0.norm() > 0.5, "DC gain too low: {}", h0.norm());
}
#[test]
fn ring_resonator_single_resonance() {
let kappa = 0.5_f64;
let alpha = (1.0 - kappa).sqrt(); let ring = RingResonatorFilter::new_single(kappa, alpha, 0.0, 100e9);
let h_on = ring.transfer_function(0.0);
assert!(
h_on.norm() < 0.05,
"On-resonance transmission should be near 0; got {}",
h_on.norm()
);
}
#[test]
fn ring_bandwidth_positive() {
let ring = RingResonatorFilter::new_single(0.5, 0.9, 0.0, 100e9);
let bw = ring.bandwidth_hz();
assert!(bw > 0.0, "Bandwidth must be positive, got {}", bw);
assert!(bw <= ring.fsr, "Bandwidth must not exceed FSR");
}
#[test]
fn ring_bandwidth_narrowband() {
let ring = RingResonatorFilter::new_single(0.9, 0.3, 0.0, 100e9);
let bw = ring.bandwidth_hz();
assert!(bw > 0.0, "Bandwidth must be positive, got {}", bw);
assert!(
bw <= ring.fsr,
"High-coupling ring bandwidth must not exceed FSR: {}",
bw
);
}
#[test]
fn equalizer_identity_tap() {
let eq = OpticalEqualizer::new(7, 50.0, EqAlgorithm::Lms { mu: 1e-3 });
let signal: Vec<Complex64> = (0..20).map(|i| Complex64::new(i as f64, 0.0)).collect();
let out = eq.apply(&signal);
assert!(!out.is_empty());
}
#[test]
fn equalizer_tap_span() {
let eq = OpticalEqualizer::new(11, 50.0, EqAlgorithm::Cma { mu: 1e-4 });
assert_abs_diff_eq!(eq.tap_span_ps(), 550.0, epsilon = 1e-6);
}
}