#[derive(Debug, Clone, PartialEq)]
pub enum MdmModFormat {
DpQpsk,
Dp16Qam,
Dp64Qam,
}
impl MdmModFormat {
pub fn bits_per_symbol(&self) -> u32 {
match self {
MdmModFormat::DpQpsk => 4,
MdmModFormat::Dp16Qam => 8,
MdmModFormat::Dp64Qam => 12,
}
}
pub fn spectral_efficiency(&self) -> f64 {
match self {
MdmModFormat::DpQpsk => 4.0,
MdmModFormat::Dp16Qam => 8.0,
MdmModFormat::Dp64Qam => 12.0,
}
}
pub fn required_osnr_db(&self) -> f64 {
match self {
MdmModFormat::DpQpsk => 12.0,
MdmModFormat::Dp16Qam => 18.5,
MdmModFormat::Dp64Qam => 25.0,
}
}
pub fn fec_overhead(&self) -> f64 {
match self {
MdmModFormat::DpQpsk => 0.07, MdmModFormat::Dp16Qam => 0.20, MdmModFormat::Dp64Qam => 0.25, }
}
pub fn net_spectral_efficiency(&self) -> f64 {
self.spectral_efficiency() / (1.0 + self.fec_overhead())
}
}
pub struct MdmSystem {
pub fiber: super::few_mode_fiber::FewModeFiber,
pub n_spatial_modes: usize,
pub n_wavelength_channels: usize,
pub baud_rate_gbaud: f64,
pub modulation_format: MdmModFormat,
}
impl MdmSystem {
pub fn new(
fiber: super::few_mode_fiber::FewModeFiber,
n_modes: usize,
n_wl: usize,
baud: f64,
format: MdmModFormat,
) -> Self {
Self {
fiber,
n_spatial_modes: n_modes,
n_wavelength_channels: n_wl,
baud_rate_gbaud: baud,
modulation_format: format,
}
}
pub fn total_capacity_tbps(&self) -> f64 {
let se = self.modulation_format.spectral_efficiency();
self.n_spatial_modes as f64
* self.n_wavelength_channels as f64
* self.baud_rate_gbaud
* se
* 1.0e-3 }
pub fn spectral_efficiency_per_mode(&self) -> f64 {
self.modulation_format.spectral_efficiency()
}
pub fn aggregate_se_bps_per_hz(&self) -> f64 {
self.n_spatial_modes as f64 * self.modulation_format.spectral_efficiency()
}
pub fn mimo_complexity_tops(&self, n_taps: usize) -> f64 {
let n = self.n_spatial_modes as f64;
let ops_per_sym = n * n * n_taps as f64;
ops_per_sym * self.baud_rate_gbaud * 1.0e9 / 1.0e12
}
pub fn shannon_capacity_tbps(&self, snr_db: f64, bandwidth_thz: f64) -> f64 {
let snr = 10.0_f64.powf(snr_db / 10.0);
let n = self.n_spatial_modes as f64;
let bw_hz = bandwidth_thz * 1.0e12;
let capacity_bps = n * bw_hz * (1.0 + snr / n).log2();
capacity_bps * 1.0e-12 }
pub fn mdl_penalty_db(&self) -> f64 {
let losses = &self.fiber.loss_db_per_km;
let n = losses.len() as f64;
if n < 2.0 {
return 0.0;
}
let mean = losses.iter().sum::<f64>() / n;
let variance = losses.iter().map(|l| (l - mean).powi(2)).sum::<f64>() / n;
let total_loss_spread = (variance.sqrt() * self.fiber.length_km).powi(2);
total_loss_spread / (2.0 * 2.0_f64.ln())
}
pub fn reach_km(&self, target_tbps: f64, snr_db: f64) -> f64 {
let span_length_km = 80.0_f64;
let max_spans = 100usize;
let bandwidth_thz = self.n_wavelength_channels as f64 * self.baud_rate_gbaud * 1.0e-3; let mut lo = 0.0_f64;
let mut hi = max_spans as f64 * span_length_km;
for _ in 0..50 {
let mid = (lo + hi) / 2.0;
let snr_degraded = snr_db - 0.3 * mid / 100.0;
let cap = if snr_degraded > 0.0 {
self.shannon_capacity_tbps(snr_degraded, bandwidth_thz)
} else {
0.0
};
if cap >= target_tbps {
lo = mid;
} else {
hi = mid;
}
}
(lo + hi) / 2.0
}
}
pub struct ModegroupDemux {
pub mode_groups: Vec<Vec<usize>>,
pub crosstalk_within_group_db: f64,
pub crosstalk_between_groups_db: f64,
}
impl ModegroupDemux {
pub fn new_3group(fiber: &super::few_mode_fiber::FewModeFiber) -> Self {
let n = fiber.n_modes;
let group0: Vec<usize> = (0..1).filter(|&i| i < n).collect();
let group1: Vec<usize> = (1..3).filter(|&i| i < n).collect();
let group2: Vec<usize> = (3..6).filter(|&i| i < n).collect();
let mut groups = vec![group0, group1];
if !group2.is_empty() {
groups.push(group2);
}
Self {
mode_groups: groups,
crosstalk_within_group_db: -15.0, crosstalk_between_groups_db: -30.0, }
}
pub fn n_groups(&self) -> usize {
self.mode_groups.len()
}
pub fn modes_per_group(&self) -> Vec<usize> {
self.mode_groups.iter().map(|g| g.len()).collect()
}
pub fn effective_capacity_factor(&self) -> f64 {
let xt_linear = 10.0_f64.powf(self.crosstalk_within_group_db / 10.0);
let penalty_per_group: Vec<f64> = self
.mode_groups
.iter()
.map(|g| {
let n_in_group = g.len() as f64;
(1.0 - xt_linear).max(0.0).powf(1.0 / n_in_group.max(1.0))
})
.collect();
penalty_per_group.iter().product::<f64>() / self.mode_groups.len() as f64
* self.mode_groups.len() as f64 }
pub fn required_mimo_size(&self) -> usize {
self.mode_groups.iter().map(|g| g.len()).max().unwrap_or(0)
}
}
pub struct SdmCapacityComparison {
pub smf_capacity_tbps: f64,
pub fmf_capacity_tbps: f64,
pub mcf_capacity_tbps: f64,
pub fmf_mcf_capacity_tbps: f64,
}
impl SdmCapacityComparison {
pub fn compute(bandwidth_thz: f64, snr_db: f64) -> Self {
let snr = 10.0_f64.powf(snr_db / 10.0);
let bw_hz = bandwidth_thz * 1.0e12;
let capacity_tbps =
|n_sdm: f64| -> f64 { n_sdm * bw_hz * (1.0 + snr / n_sdm).log2() * 1.0e-12 };
let smf_cap = capacity_tbps(1.0);
let fmf_cap = capacity_tbps(6.0); let mcf_cap = capacity_tbps(7.0); let fm_mcf_cap = capacity_tbps(42.0);
Self {
smf_capacity_tbps: smf_cap,
fmf_capacity_tbps: fmf_cap,
mcf_capacity_tbps: mcf_cap,
fmf_mcf_capacity_tbps: fm_mcf_cap,
}
}
pub fn sdm_gain_over_smf(&self) -> f64 {
if self.smf_capacity_tbps <= 0.0 {
return 0.0;
}
self.fmf_mcf_capacity_tbps / self.smf_capacity_tbps
}
pub fn print_summary(&self) -> String {
let gain = self.sdm_gain_over_smf();
format!(
"SDM Capacity Comparison\n\
========================\n\
SMF (reference): {:>8.1} Tb/s\n\
FMF (6 modes): {:>8.1} Tb/s ({:.1}× SMF)\n\
MCF (7 cores): {:>8.1} Tb/s ({:.1}× SMF)\n\
FM-MCF (7c×6m): {:>8.1} Tb/s ({:.1}× SMF)\n\
========================\n\
Total SDM gain: {:.0}×",
self.smf_capacity_tbps,
self.fmf_capacity_tbps,
self.fmf_capacity_tbps / self.smf_capacity_tbps.max(1.0e-12),
self.mcf_capacity_tbps,
self.mcf_capacity_tbps / self.smf_capacity_tbps.max(1.0e-12),
self.fmf_mcf_capacity_tbps,
self.fmf_mcf_capacity_tbps / self.smf_capacity_tbps.max(1.0e-12),
gain
)
}
}
#[cfg(test)]
mod tests {
use super::super::few_mode_fiber::FewModeFiber;
use super::*;
use approx::assert_abs_diff_eq;
fn make_system(n_modes: usize, n_wl: usize) -> MdmSystem {
let fiber = FewModeFiber::new_6mode(100.0, 1.55e-6);
MdmSystem::new(fiber, n_modes, n_wl, 32.0, MdmModFormat::DpQpsk)
}
#[test]
fn test_modformat_bits_per_symbol() {
assert_eq!(MdmModFormat::DpQpsk.bits_per_symbol(), 4);
assert_eq!(MdmModFormat::Dp16Qam.bits_per_symbol(), 8);
assert_eq!(MdmModFormat::Dp64Qam.bits_per_symbol(), 12);
}
#[test]
fn test_total_capacity_scales_with_modes() {
let sys1 = make_system(2, 80);
let sys2 = make_system(4, 80);
assert!(sys2.total_capacity_tbps() > sys1.total_capacity_tbps());
}
#[test]
fn test_shannon_capacity_positive() {
let sys = make_system(6, 80);
let cap = sys.shannon_capacity_tbps(20.0, 4.0);
assert!(cap > 0.0, "Shannon capacity should be positive");
}
#[test]
fn test_aggregate_se_multiple_of_per_mode() {
let sys = make_system(6, 80);
assert_abs_diff_eq!(
sys.aggregate_se_bps_per_hz(),
6.0 * sys.spectral_efficiency_per_mode(),
epsilon = 1.0e-9
);
}
#[test]
fn test_mimo_complexity_grows_with_taps() {
let sys = make_system(6, 80);
let c1 = sys.mimo_complexity_tops(32);
let c2 = sys.mimo_complexity_tops(64);
assert!(c2 > c1, "Complexity should grow with tap count");
}
#[test]
fn test_sdm_gain_greater_than_1() {
let comp = SdmCapacityComparison::compute(4.0, 20.0);
assert!(comp.sdm_gain_over_smf() > 1.0, "SDM should outperform SMF");
}
#[test]
fn test_fmf_mcf_capacity_greater_than_fmf() {
let comp = SdmCapacityComparison::compute(4.0, 20.0);
assert!(
comp.fmf_mcf_capacity_tbps > comp.fmf_capacity_tbps,
"FM-MCF should exceed FMF capacity"
);
}
#[test]
fn test_modegroup_demux_3groups() {
let fiber = FewModeFiber::new_6mode(100.0, 1.55e-6);
let demux = ModegroupDemux::new_3group(&fiber);
assert_eq!(demux.n_groups(), 3, "Should have 3 mode groups");
let total_modes: usize = demux.modes_per_group().iter().sum();
assert!(total_modes > 0, "Should have modes assigned to groups");
}
#[test]
fn test_print_summary_non_empty() {
let comp = SdmCapacityComparison::compute(4.0, 20.0);
let summary = comp.print_summary();
assert!(!summary.is_empty());
assert!(summary.contains("SMF"), "Summary should mention SMF");
}
#[test]
fn test_net_se_less_than_gross() {
for fmt in &[
MdmModFormat::DpQpsk,
MdmModFormat::Dp16Qam,
MdmModFormat::Dp64Qam,
] {
assert!(
fmt.net_spectral_efficiency() < fmt.spectral_efficiency(),
"Net SE must be less than gross SE for {:?}",
fmt
);
}
}
}