use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct SacOcdma {
pub n_wavelengths: usize,
pub code_weight: usize,
pub center_freq_hz: f64,
pub channel_spacing_hz: f64,
}
impl SacOcdma {
pub fn new(
n_wavelengths: usize,
code_weight: usize,
center_freq_hz: f64,
channel_spacing_hz: f64,
) -> Self {
Self {
n_wavelengths,
code_weight,
center_freq_hz,
channel_spacing_hz,
}
}
pub fn mdw_code(&self, user_idx: usize) -> Vec<u8> {
let n = self.n_wavelengths;
let w = self.code_weight;
let mut code = vec![0u8; n];
let start = (2 * user_idx * w) % (2 * n.max(1));
for k in 0..w {
let pos = (start + k) % n;
code[pos] = 1;
}
code
}
pub fn complementary_code(&self, code: &[u8]) -> Vec<u8> {
code.iter().map(|&b| 1 - b.min(1)).collect()
}
pub fn snr(
&self,
power_per_chip_w: f64,
shot_noise_variance: f64,
thermal_variance: f64,
) -> f64 {
let signal_power = (self.code_weight as f64 * power_per_chip_w).powi(2);
let noise_power = shot_noise_variance + thermal_variance;
if noise_power < 1e-300 {
return f64::INFINITY;
}
signal_power / noise_power
}
pub fn spectral_efficiency(&self, bit_rate_hz: f64) -> f64 {
let total_bw = self.n_wavelengths as f64 * self.channel_spacing_hz;
if total_bw < 1e-30 {
return 0.0;
}
bit_rate_hz / total_bw
}
pub fn max_users(&self) -> usize {
if self.code_weight == 0 {
return 0;
}
self.n_wavelengths / self.code_weight
}
pub fn chip_frequency_hz(&self, chip_idx: usize) -> f64 {
let offset = chip_idx as f64 - (self.n_wavelengths as f64 - 1.0) / 2.0;
self.center_freq_hz + offset * self.channel_spacing_hz
}
pub fn cross_correlation(&self, user_a: usize, user_b: usize) -> usize {
let ca = self.mdw_code(user_a);
let cb = self.mdw_code(user_b);
ca.iter()
.zip(cb.iter())
.map(|(&a, &b)| (a & b) as usize)
.sum()
}
}
#[derive(Debug, Clone)]
pub struct SpcOcdma {
pub n_chips: usize,
pub phases: Vec<f64>,
}
impl SpcOcdma {
pub fn new(phases: Vec<f64>) -> Self {
let n_chips = phases.len();
Self { n_chips, phases }
}
pub fn encode_spectrum(&self, spectrum: &[f64]) -> Vec<(f64, f64)> {
spectrum
.iter()
.zip(self.phases.iter())
.map(|(&s, &phi)| {
let (sin_p, cos_p) = phi.sin_cos();
(s * cos_p, s * sin_p)
})
.collect()
}
pub fn decode_spectrum(&self, encoded: &[(f64, f64)]) -> Vec<(f64, f64)> {
encoded
.iter()
.zip(self.phases.iter())
.map(|(&(re, im), &phi)| {
let (sin_p, cos_p) = phi.sin_cos();
(re * cos_p + im * sin_p, -re * sin_p + im * cos_p)
})
.collect()
}
pub fn autocorrelation_peak(&self) -> f64 {
self.n_chips as f64
}
pub fn walsh_code(n: usize, idx: usize) -> Vec<f64> {
let n_clamped = n.next_power_of_two().max(1);
let idx = idx % n_clamped;
let mut code = vec![1.0f64; n_clamped];
let mut half = 1usize;
let mut row = idx;
while half < n_clamped {
let bit = row & 1;
if bit == 1 {
for (k, v) in code.iter_mut().enumerate().take(n_clamped) {
if (k / half) % 2 == 1 {
*v *= -1.0;
}
}
}
row >>= 1;
half <<= 1;
}
code.iter()
.map(|&v| if v > 0.0 { 0.0 } else { PI })
.collect()
}
pub fn decoded_pulse_width_s(&self, chip_bandwidth_hz: f64) -> f64 {
if chip_bandwidth_hz < 1e-30 || self.n_chips == 0 {
return f64::INFINITY;
}
1.0 / (self.n_chips as f64 * chip_bandwidth_hz)
}
pub fn ifft(&self, spectrum: &[(f64, f64)]) -> Vec<(f64, f64)> {
let n = spectrum.len().next_power_of_two().max(1);
let mut buf: Vec<(f64, f64)> = spectrum
.iter()
.cloned()
.chain(std::iter::repeat((0.0, 0.0)))
.take(n)
.collect();
let mut j = 0usize;
for i in 1..n {
let mut bit = n >> 1;
while j & bit != 0 {
j ^= bit;
bit >>= 1;
}
j ^= bit;
if i < j {
buf.swap(i, j);
}
}
let mut len = 2usize;
while len <= n {
let half = len / 2;
let angle = 2.0 * PI / len as f64; let (wsin, wcos) = angle.sin_cos();
for start in (0..n).step_by(len) {
let (mut wr, mut wi) = (1.0f64, 0.0f64);
for k in 0..half {
let (ur, ui) = buf[start + k];
let (vr, vi) = buf[start + k + half];
let (tr, ti) = (wr * vr - wi * vi, wr * vi + wi * vr);
buf[start + k] = (ur + tr, ui + ti);
buf[start + k + half] = (ur - tr, ui - ti);
let new_wr = wr * wcos - wi * wsin;
wi = wr * wsin + wi * wcos;
wr = new_wr;
}
}
len <<= 1;
}
let scale = 1.0 / n as f64;
buf.iter()
.map(|&(re, im)| (re * scale, im * scale))
.collect()
}
pub fn n_orthogonal_codes(&self) -> usize {
self.n_chips.next_power_of_two().max(1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sac_mdw_code_weight() {
let sac = SacOcdma::new(16, 4, 193.1e12, 50e9);
let code = sac.mdw_code(0);
let weight: usize = code.iter().map(|&b| b as usize).sum();
assert_eq!(weight, 4, "MDW code weight should equal code_weight");
}
#[test]
fn sac_complementary_code_no_overlap() {
let sac = SacOcdma::new(16, 4, 193.1e12, 50e9);
let code = sac.mdw_code(0);
let comp = sac.complementary_code(&code);
let overlap: usize = code
.iter()
.zip(comp.iter())
.map(|(&a, &b)| (a & b) as usize)
.sum();
assert_eq!(overlap, 0);
let union: usize = code
.iter()
.zip(comp.iter())
.map(|(&a, &b)| (a | b) as usize)
.sum();
assert_eq!(union, 16);
}
#[test]
fn sac_max_users() {
let sac = SacOcdma::new(32, 4, 193.1e12, 50e9);
assert_eq!(sac.max_users(), 8); }
#[test]
fn spc_encode_decode_identity() {
let phases = vec![0.0f64; 8];
let spc = SpcOcdma::new(phases);
let spectrum = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let encoded = spc.encode_spectrum(&spectrum);
let decoded = spc.decode_spectrum(&encoded);
for (orig, (re, _im)) in spectrum.iter().zip(decoded.iter()) {
assert!((orig - re).abs() < 1e-10, "decode(encode(x)) ≠ x");
}
}
#[test]
fn spc_walsh_code_orthogonality() {
let w0 = SpcOcdma::walsh_code(8, 0);
let w1 = SpcOcdma::walsh_code(8, 1);
let dot: f64 = w0
.iter()
.zip(w1.iter())
.map(|(&a, &b)| {
let va = if a.abs() < 0.1 { 1.0 } else { -1.0 };
let vb = if b.abs() < 0.1 { 1.0 } else { -1.0 };
va * vb
})
.sum();
assert_eq!(dot, 0.0, "Walsh codes 0 and 1 must be orthogonal");
}
#[test]
fn spc_autocorrelation_peak() {
let phases = SpcOcdma::walsh_code(8, 3);
let spc = SpcOcdma::new(phases);
assert_eq!(spc.autocorrelation_peak(), 8.0);
}
#[test]
fn sac_snr_infinite_at_zero_noise() {
let sac = SacOcdma::new(8, 2, 193.1e12, 50e9);
let snr = sac.snr(1e-3, 0.0, 0.0);
assert!(snr.is_infinite() || snr > 1e20);
}
#[test]
fn spc_ifft_impulse() {
let phases = vec![0.0f64; 8];
let spc = SpcOcdma::new(phases);
let spectrum: Vec<(f64, f64)> = vec![(1.0, 0.0); 8];
let td = spc.ifft(&spectrum);
assert!(
(td[0].0 - 1.0).abs() < 1e-10,
"IFFT impulse[0] = {}",
td[0].0
);
for &(re, im) in &td[1..8] {
assert!(re.abs() < 1e-10 && im.abs() < 1e-10, "IFFT tail non-zero");
}
}
}