use std::f64::consts::PI;
fn q_function(x: f64) -> f64 {
crate::optical_cdma::performance::q_function(x)
}
#[derive(Debug, Clone)]
pub struct IncoherentOcdma {
pub code: Vec<u8>,
pub chip_rate_hz: f64,
pub power_per_chip_w: f64,
}
impl IncoherentOcdma {
pub fn new(code: Vec<u8>, chip_rate_hz: f64, power_per_chip_w: f64) -> Self {
Self {
code,
chip_rate_hz,
power_per_chip_w,
}
}
pub fn encode(&self, bit: u8) -> Vec<u8> {
if bit != 0 {
self.code.clone()
} else {
vec![0u8; self.code.len()]
}
}
pub fn decode(&self, received: &[f64]) -> u8 {
let weight: usize = self.code.iter().map(|&b| b as usize).sum();
let correlation: f64 = received
.iter()
.zip(self.code.iter())
.map(|(&r, &c)| r * c as f64)
.sum();
let threshold = weight as f64 - 0.5;
if correlation >= threshold {
1
} else {
0
}
}
pub fn bit_rate_hz(&self) -> f64 {
self.chip_rate_hz / self.code.len() as f64
}
pub fn peak_power_w(&self) -> f64 {
let weight: usize = self.code.iter().map(|&b| b as usize).sum();
weight as f64 * self.power_per_chip_w
}
pub fn average_power_w(&self) -> f64 {
let n = self.code.len();
let weight: usize = self.code.iter().map(|&b| b as usize).sum();
0.5 * weight as f64 * self.power_per_chip_w * weight as f64 / n as f64
}
pub fn weight(&self) -> usize {
self.code.iter().map(|&b| b as usize).sum()
}
pub fn code_length(&self) -> usize {
self.code.len()
}
}
#[derive(Debug, Clone)]
pub struct MaiAnalyzer {
pub codes: Vec<Vec<u8>>,
pub n_users: usize,
}
impl MaiAnalyzer {
pub fn new(codes: Vec<Vec<u8>>) -> Self {
let n_users = codes.len();
Self { codes, n_users }
}
pub fn mai_probability(&self, user_j: usize, threshold: usize) -> f64 {
if user_j >= self.codes.len() || self.codes.is_empty() {
return 0.0;
}
let c0 = &self.codes[0];
let cj = &self.codes[user_j];
let n = c0.len().min(cj.len());
let w0: usize = c0.iter().map(|&b| b as usize).sum();
if w0 == 0 {
return 0.0;
}
let max_xcorr = (0..n)
.map(|tau| {
(0..n)
.map(|i| (cj[i] as usize) * (c0[(i + tau) % n] as usize))
.sum::<usize>()
})
.max()
.unwrap_or(0);
let raw = max_xcorr as f64 / (w0 as f64 * threshold.max(1) as f64);
raw.min(1.0)
}
pub fn total_mai_variance(&self, k_users: usize) -> f64 {
if self.codes.is_empty() {
return 0.0;
}
let n = self.codes[0].len();
if n == 0 || k_users <= 1 {
return 0.0;
}
let c0 = &self.codes[0];
let n_pairs = (self.codes.len() - 1).max(1);
let mean_sq_xcorr: f64 = self.codes[1..]
.iter()
.map(|cj| {
let max_xc = (0..n)
.map(|tau| {
(0..n)
.map(|i| (cj[i] as usize) * (c0[(i + tau) % n] as usize))
.sum::<usize>()
})
.max()
.unwrap_or(0) as f64;
max_xc * max_xc
})
.sum::<f64>()
/ n_pairs as f64;
(k_users - 1) as f64 * mean_sq_xcorr / (n as f64 * n as f64)
}
pub fn ber_with_mai(&self, snr_per_chip: f64, k_users: usize) -> f64 {
if self.codes.is_empty() {
return 0.5;
}
let w: f64 = self.codes[0].iter().map(|&b| b as f64).sum();
let n = self.codes[0].len() as f64;
if w < 1.0 || n < 1.0 {
return 0.5;
}
let sigma_sq_shot = w / snr_per_chip.max(1e-12);
let sigma_sq_mai = self.total_mai_variance(k_users);
let sigma = (sigma_sq_shot + sigma_sq_mai).sqrt();
let snr_arg = (w - 0.5) / sigma.max(1e-30);
q_function(snr_arg)
}
pub fn max_users_for_ber(&self, snr_per_chip: f64, target_ber: f64) -> usize {
let mut k = 1usize;
loop {
let ber = self.ber_with_mai(snr_per_chip, k);
if ber > target_ber {
return k.saturating_sub(1).max(1);
}
k += 1;
if k > 1000 {
return 1000;
}
}
}
}
#[derive(Debug, Clone)]
pub struct CoherentOcdma {
pub code: Vec<i8>,
pub chip_duration_s: f64,
}
impl CoherentOcdma {
pub fn new(code: Vec<i8>, chip_duration_s: f64) -> Self {
Self {
code,
chip_duration_s,
}
}
pub fn encode(&self, bit: i8) -> Vec<i8> {
self.code.iter().map(|&c| bit * c).collect()
}
pub fn correlate(&self, received: &[f64]) -> f64 {
received
.iter()
.zip(self.code.iter())
.map(|(&r, &c)| r * c as f64)
.sum()
}
pub fn snr_gain(&self) -> f64 {
self.code.len() as f64
}
pub fn processing_gain_db(&self) -> f64 {
10.0 * (self.code.len() as f64).log10()
}
pub fn symbol_rate_sps(&self) -> f64 {
if self.chip_duration_s > 0.0 {
1.0 / (self.chip_duration_s * self.code.len() as f64)
} else {
0.0
}
}
pub fn ber_bpsk(&self, eb_n0_linear: f64) -> f64 {
let n = self.code.len() as f64;
q_function((2.0 * n * eb_n0_linear).sqrt())
}
}
#[allow(dead_code)]
const _PI: f64 = PI;
#[cfg(test)]
mod tests {
use super::*;
fn make_ooc_code() -> Vec<u8> {
vec![1, 1, 0, 1, 0, 0, 0]
}
#[test]
fn incoherent_encode_decode_roundtrip() {
let code = make_ooc_code();
let trx = IncoherentOcdma::new(code.clone(), 1e9, 1e-3);
let chips1 = trx.encode(1);
let chips0 = trx.encode(0);
let rx1: Vec<f64> = chips1.iter().map(|&b| b as f64).collect();
let rx0: Vec<f64> = chips0.iter().map(|&b| b as f64).collect();
assert_eq!(trx.decode(&rx1), 1);
assert_eq!(trx.decode(&rx0), 0);
}
#[test]
fn incoherent_bit_rate() {
let code = make_ooc_code(); let trx = IncoherentOcdma::new(code, 7e9, 1e-3);
assert!((trx.bit_rate_hz() - 1e9).abs() < 1.0);
}
#[test]
fn incoherent_peak_average_power() {
let code = make_ooc_code(); let p_chip = 1e-3;
let trx = IncoherentOcdma::new(code, 1e9, p_chip);
assert!((trx.peak_power_w() - 3e-3).abs() < 1e-12);
let expected_avg = 0.5 * 3.0 * p_chip * 3.0 / 7.0;
assert!((trx.average_power_w() - expected_avg).abs() < 1e-15);
}
#[test]
fn mai_analyzer_variance_zero_one_user() {
let c0 = make_ooc_code();
let analyzer = MaiAnalyzer::new(vec![c0]);
assert_eq!(analyzer.total_mai_variance(1), 0.0);
}
#[test]
fn mai_analyzer_ber_decreases_with_snr() {
let c0 = make_ooc_code();
let c1 = vec![0u8, 0, 1, 0, 1, 1, 0]; let analyzer = MaiAnalyzer::new(vec![c0, c1]);
let ber_low = analyzer.ber_with_mai(5.0, 2);
let ber_high = analyzer.ber_with_mai(20.0, 2);
assert!(ber_low > ber_high, "BER should decrease with SNR");
}
#[test]
fn coherent_encode_decode() {
let code = vec![1i8, -1, 1, 1, -1, 1, -1, -1];
let trx = CoherentOcdma::new(code.clone(), 1e-9);
let chips = trx.encode(1i8);
let rx: Vec<f64> = chips.iter().map(|&c| c as f64).collect();
let corr = trx.correlate(&rx);
assert!((corr - 8.0).abs() < 1e-10);
}
#[test]
fn coherent_processing_gain() {
let code: Vec<i8> = vec![1; 64];
let trx = CoherentOcdma::new(code, 1e-9);
let pg = trx.processing_gain_db();
assert!((pg - 18.06).abs() < 0.1, "PG_dB = {}", pg);
}
#[test]
fn mai_max_users_for_ber_monotone() {
let c0 = make_ooc_code();
let c1 = vec![0u8, 1, 0, 1, 1, 0, 0];
let analyzer = MaiAnalyzer::new(vec![c0, c1]);
let cap_tight = analyzer.max_users_for_ber(10.0, 1e-3);
let cap_loose = analyzer.max_users_for_ber(10.0, 0.1);
assert!(cap_loose >= cap_tight);
}
}