use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct PhotonicAdc {
pub sampling_rate_gsps: f64,
pub bits: u32,
pub optical_bandwidth_ghz: f64,
pub modulator_vpi: f64,
}
impl PhotonicAdc {
pub fn new(rate_gsps: f64, enob: u32, bandwidth_ghz: f64) -> Self {
PhotonicAdc {
sampling_rate_gsps: rate_gsps,
bits: enob,
optical_bandwidth_ghz: bandwidth_ghz,
modulator_vpi: 5.0,
}
}
pub fn new_with_vpi(rate_gsps: f64, enob: u32, bandwidth_ghz: f64, vpi: f64) -> Self {
PhotonicAdc {
sampling_rate_gsps: rate_gsps,
bits: enob,
optical_bandwidth_ghz: bandwidth_ghz,
modulator_vpi: vpi,
}
}
pub fn sfdr_db(&self) -> f64 {
6.02 * self.bits as f64 + 1.76
}
pub fn snr_theoretical_db(&self) -> f64 {
6.02 * self.bits as f64 + 1.76
}
pub fn timing_jitter_snr_db(&self, rf_freq_ghz: f64, jitter_fs: f64) -> f64 {
let f_hz = rf_freq_ghz * 1.0e9;
let sigma_t = jitter_fs * 1.0e-15;
let arg = 2.0 * PI * f_hz * sigma_t;
if arg <= 0.0 {
return f64::INFINITY;
}
-20.0 * arg.log10()
}
pub fn max_frequency_ghz(&self, jitter_fs: f64, snr_req_db: f64) -> f64 {
let sigma_t = jitter_fs * 1.0e-15;
let snr_linear = 10.0_f64.powf(snr_req_db / 20.0);
if sigma_t <= 0.0 || snr_linear <= 0.0 {
return f64::INFINITY;
}
let f_hz = 1.0 / (2.0 * PI * sigma_t * snr_linear);
f_hz * 1.0e-9 }
pub fn interleaved_rate(&self, n_channels: usize) -> f64 {
self.sampling_rate_gsps * n_channels as f64
}
pub fn nyquist_bandwidth_ghz(&self) -> f64 {
self.sampling_rate_gsps / 2.0
}
pub fn modulator_sfdr_db(&self, optical_power_dbm: f64) -> f64 {
let oip3_proxy = 10.0 * (self.modulator_vpi * 2.0 / PI).log10() + optical_power_dbm;
let noise_floor_dbm_hz = -174.0 + 5.0; (2.0 / 3.0) * (oip3_proxy - noise_floor_dbm_hz)
}
pub fn enob_at_frequency(&self, rf_freq_ghz: f64, jitter_fs: f64) -> f64 {
let snr = self.timing_jitter_snr_db(rf_freq_ghz, jitter_fs);
let enob_jitter = (snr - 1.76) / 6.02;
enob_jitter.min(self.bits as f64)
}
}
#[derive(Debug, Clone)]
pub struct PhotonicChannelizer {
pub n_channels: usize,
pub channel_bandwidth_ghz: f64,
pub total_bandwidth_ghz: f64,
pub channel_isolation_db: f64,
}
impl PhotonicChannelizer {
pub fn new(n_channels: usize, total_bw_ghz: f64) -> Self {
let channel_bw = if n_channels > 0 {
total_bw_ghz / n_channels as f64
} else {
0.0
};
PhotonicChannelizer {
n_channels,
channel_bandwidth_ghz: channel_bw,
total_bandwidth_ghz: total_bw_ghz,
channel_isolation_db: 20.0,
}
}
pub fn new_with_overlap(n_channels: usize, channel_bw_ghz: f64, overlap: f64) -> Self {
let effective_bw_per_ch = channel_bw_ghz / overlap.max(1.0);
let total_bw = n_channels as f64 * effective_bw_per_ch;
PhotonicChannelizer {
n_channels,
channel_bandwidth_ghz: channel_bw_ghz,
total_bandwidth_ghz: total_bw,
channel_isolation_db: 20.0,
}
}
pub fn channel_center_freq(&self, channel: usize) -> f64 {
let spacing = if self.n_channels > 0 {
self.total_bandwidth_ghz / self.n_channels as f64
} else {
0.0
};
(channel as f64 + 0.5) * spacing
}
pub fn channel_bandwidth_ghz(&self) -> f64 {
self.channel_bandwidth_ghz
}
pub fn instantaneous_bandwidth_ghz(&self) -> f64 {
self.total_bandwidth_ghz
}
pub fn frequency_resolution_ghz(&self) -> f64 {
if self.n_channels > 0 {
self.total_bandwidth_ghz / self.n_channels as f64
} else {
0.0
}
}
pub fn channel_for_frequency(&self, freq_ghz: f64) -> Option<usize> {
if freq_ghz < 0.0 || freq_ghz > self.total_bandwidth_ghz {
return None;
}
let spacing = self.total_bandwidth_ghz / self.n_channels as f64;
let ch = (freq_ghz / spacing).floor() as usize;
if ch < self.n_channels {
Some(ch)
} else {
Some(self.n_channels - 1)
}
}
pub fn dynamic_range_db(&self) -> f64 {
self.channel_isolation_db
}
pub fn min_pulse_width_ns(&self) -> f64 {
if self.channel_bandwidth_ghz > 0.0 {
1.0 / self.channel_bandwidth_ghz
} else {
f64::INFINITY
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
fn adc_8bit() -> PhotonicAdc {
PhotonicAdc::new(40.0, 8, 20.0)
}
#[test]
fn test_sfdr_8bit() {
let adc = adc_8bit();
assert_abs_diff_eq!(adc.sfdr_db(), 49.92, epsilon = 0.01);
}
#[test]
fn test_snr_theoretical_8bit() {
let adc = adc_8bit();
assert_abs_diff_eq!(adc.snr_theoretical_db(), 49.92, epsilon = 0.01);
}
#[test]
fn test_timing_jitter_snr_decreases_with_frequency() {
let adc = adc_8bit();
let snr_1ghz = adc.timing_jitter_snr_db(1.0, 100.0);
let snr_10ghz = adc.timing_jitter_snr_db(10.0, 100.0);
assert!(
snr_10ghz < snr_1ghz,
"SNR should decrease at higher frequency"
);
}
#[test]
fn test_timing_jitter_snr_formula() {
let adc = adc_8bit();
let expected = -20.0 * (2.0 * PI * 1.0e9 * 100.0e-15).log10();
let computed = adc.timing_jitter_snr_db(1.0, 100.0);
assert_abs_diff_eq!(computed, expected, epsilon = 1e-6);
}
#[test]
fn test_max_frequency_ghz() {
let adc = adc_8bit();
let f_low_snr = adc.max_frequency_ghz(100.0, 30.0);
let f_high_snr = adc.max_frequency_ghz(100.0, 50.0);
assert!(
f_high_snr < f_low_snr,
"Higher SNR req → lower max frequency"
);
}
#[test]
fn test_interleaved_rate() {
let adc = adc_8bit();
let rate = adc.interleaved_rate(4);
assert_abs_diff_eq!(rate, 160.0, epsilon = 1e-6);
}
#[test]
fn test_nyquist_bandwidth() {
let adc = PhotonicAdc::new(20.0, 10, 10.0);
assert_abs_diff_eq!(adc.nyquist_bandwidth_ghz(), 10.0, epsilon = 1e-9);
}
#[test]
fn test_enob_at_frequency_capped_by_bits() {
let adc = PhotonicAdc::new(40.0, 6, 20.0);
let enob = adc.enob_at_frequency(1.0, 0.1); assert!(
enob <= adc.bits as f64,
"ENOB must not exceed architectural bits"
);
}
#[test]
fn test_enob_decreases_with_jitter() {
let adc = adc_8bit();
let enob_low = adc.enob_at_frequency(5.0, 10.0);
let enob_high = adc.enob_at_frequency(5.0, 1000.0);
assert!(
enob_low > enob_high,
"ENOB should be higher with less jitter"
);
}
#[test]
fn test_channelizer_channel_bw() {
let ch = PhotonicChannelizer::new(16, 40.0);
assert_abs_diff_eq!(ch.channel_bandwidth_ghz(), 2.5, epsilon = 1e-9);
}
#[test]
fn test_channelizer_center_freqs() {
let ch = PhotonicChannelizer::new(4, 40.0);
assert_abs_diff_eq!(ch.channel_center_freq(0), 5.0, epsilon = 1e-9);
assert_abs_diff_eq!(ch.channel_center_freq(1), 15.0, epsilon = 1e-9);
assert_abs_diff_eq!(ch.channel_center_freq(3), 35.0, epsilon = 1e-9);
}
#[test]
fn test_channelizer_frequency_resolution() {
let ch = PhotonicChannelizer::new(8, 16.0);
assert_abs_diff_eq!(ch.frequency_resolution_ghz(), 2.0, epsilon = 1e-9);
}
#[test]
fn test_channelizer_channel_lookup() {
let ch = PhotonicChannelizer::new(4, 40.0);
assert_eq!(ch.channel_for_frequency(5.0), Some(0));
assert_eq!(ch.channel_for_frequency(25.0), Some(2));
assert_eq!(ch.channel_for_frequency(50.0), None);
}
#[test]
fn test_channelizer_min_pulse_width() {
let ch = PhotonicChannelizer::new(10, 10.0);
assert_abs_diff_eq!(ch.min_pulse_width_ns(), 1.0, epsilon = 1e-9);
}
#[test]
fn test_channelizer_instantaneous_bandwidth() {
let ch = PhotonicChannelizer::new(8, 40.0);
assert_abs_diff_eq!(ch.instantaneous_bandwidth_ghz(), 40.0, epsilon = 1e-9);
}
}