use crate::constellation::Constellation;
use comnum_core::Result;
use comnum_core::rng::Rng;
use comnum_math::C64;
pub fn bits(n: usize, rng: &mut Rng) -> Vec<u8> {
(0..n).map(|_| rng.bit()).collect()
}
#[inline]
pub fn qpsk(rng: &mut Rng) -> C64 {
C64::from_polar(1.0, std::f64::consts::FRAC_PI_4 * (2 * rng.below(4) + 1) as f64)
}
const PRBS_TAPS: [(u32, u32); 8] = [(7, 6), (9, 5), (11, 9), (15, 14), (20, 3), (23, 18), (29, 27), (31, 28)];
pub fn prbs(order: u32, length: usize, seed: u64) -> Result<Vec<u8>> {
let (n, k) =
PRBS_TAPS.iter().copied().find(|&(n, _)| n == order).ok_or_else(|| {
comnum_core::Error::Config(format!("PRBS order {order} not in 7, 9, 11, 15, 20, 23, 29, 31"))
})?;
let (n, k) = (n as usize, k as usize);
let reg = match seed & ((1u64 << n) - 1) {
0 => 1,
r => r,
};
let init: Vec<u8> = (0..n).map(|i| ((reg >> (n - 1 - i)) & 1) as u8).collect();
Ok(comnum_math::sequences::lfsr(&[0, n - k], n, &init, n + length).split_off(n))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum O150Sequence {
Prbs9,
Prbs11,
Prbs15,
Prbs20,
Prbs20Zs,
Prbs23,
Prbs29,
Prbs31,
}
impl O150Sequence {
pub const ALL: [O150Sequence; 8] = [
O150Sequence::Prbs9,
O150Sequence::Prbs11,
O150Sequence::Prbs15,
O150Sequence::Prbs20,
O150Sequence::Prbs20Zs,
O150Sequence::Prbs23,
O150Sequence::Prbs29,
O150Sequence::Prbs31,
];
pub fn from_name(name: &str) -> Result<O150Sequence> {
O150Sequence::ALL.into_iter().find(|s| s.name() == name.to_ascii_lowercase()).ok_or_else(|| {
let known: Vec<&str> = O150Sequence::ALL.iter().map(|s| s.name()).collect();
comnum_core::Error::Config(format!("O.150 sequence {name:?} not in {}", known.join(", ")))
})
}
pub fn name(self) -> &'static str {
match self {
O150Sequence::Prbs9 => "prbs9",
O150Sequence::Prbs11 => "prbs11",
O150Sequence::Prbs15 => "prbs15",
O150Sequence::Prbs20 => "prbs20",
O150Sequence::Prbs20Zs => "prbs20z",
O150Sequence::Prbs23 => "prbs23",
O150Sequence::Prbs29 => "prbs29",
O150Sequence::Prbs31 => "prbs31",
}
}
pub fn stages(self) -> u32 {
match self {
O150Sequence::Prbs9 => 9,
O150Sequence::Prbs11 => 11,
O150Sequence::Prbs15 => 15,
O150Sequence::Prbs20 | O150Sequence::Prbs20Zs => 20,
O150Sequence::Prbs23 => 23,
O150Sequence::Prbs29 => 29,
O150Sequence::Prbs31 => 31,
}
}
pub fn feedback(self) -> u32 {
match self {
O150Sequence::Prbs9 => 5,
O150Sequence::Prbs11 => 9,
O150Sequence::Prbs15 => 14,
O150Sequence::Prbs20 => 3,
O150Sequence::Prbs20Zs => 17,
O150Sequence::Prbs23 => 18,
O150Sequence::Prbs29 => 27,
O150Sequence::Prbs31 => 28,
}
}
pub fn inverted(self) -> bool {
matches!(self, O150Sequence::Prbs15 | O150Sequence::Prbs23 | O150Sequence::Prbs29 | O150Sequence::Prbs31)
}
pub fn period(self) -> u64 {
(1u64 << self.stages()) - 1
}
pub fn longest_zeros(self) -> u32 {
match self {
O150Sequence::Prbs20Zs => 14,
s if s.inverted() => s.stages(),
s => s.stages() - 1,
}
}
pub fn bits(self, length: usize) -> Vec<u8> {
self.iter().take(length).collect()
}
pub fn iter(self) -> O150Bits {
O150Bits { sequence: self, register: (1u32 << self.stages()) - 1 }
}
}
#[derive(Clone, Debug)]
pub struct O150Bits {
sequence: O150Sequence,
register: u32,
}
impl Iterator for O150Bits {
type Item = u8;
fn next(&mut self) -> Option<u8> {
let (n, k) = (self.sequence.stages(), self.sequence.feedback());
let q = self.register;
let last = (q >> (n - 1)) & 1;
let out = match self.sequence {
O150Sequence::Prbs20Zs => last | u32::from(q & (((1u32 << 14) - 1) << 5) == 0),
_ => last,
};
let fb = last ^ ((q >> (k - 1)) & 1);
self.register = ((q << 1) | fb) & ((1u32 << n) - 1);
Some(out as u8 ^ u8::from(self.sequence.inverted()))
}
}
pub fn maxwell_boltzmann(c: &Constellation, lambda: f64) -> Vec<f64> {
let w: Vec<f64> = c.points().iter().map(|p| (-lambda * p.norm_sqr()).exp()).collect();
let s: f64 = w.iter().sum();
w.iter().map(|v| v / s).collect()
}
pub fn symbols(c: &Constellation, n: usize, px: Option<&[f64]>, rng: &mut Rng) -> Result<(Vec<usize>, Vec<C64>)> {
let m = c.order();
let idx: Vec<usize> = if px.is_none() {
(0..n).map(|_| rng.below(m)).collect()
} else {
let mut cdf = Vec::with_capacity(m);
let mut acc = 0.0;
for v in c.priors(px)? {
acc += v;
cdf.push(acc);
}
(0..n)
.map(|_| {
let u = rng.uniform() * acc;
cdf.partition_point(|&c| c <= u).min(m - 1)
})
.collect()
};
let x = idx.iter().map(|&i| c.points()[i]).collect();
Ok((idx, x))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prbs_periods_and_balance() {
for &(n, _) in &PRBS_TAPS[..4] {
let period = (1usize << n) - 1;
let p = prbs(n, 2 * period, 0xACE1).unwrap();
assert_eq!(&p[..period], &p[period..], "order {n}");
let ones = p[..period].iter().filter(|&&v| v == 1).count();
assert_eq!(ones, period.div_ceil(2), "order {n}");
}
assert!(prbs(8, 10, 1).is_err());
assert_eq!(prbs(29, 10, 1).unwrap().len(), 10);
}
#[test]
fn the_511_bit_sequence_begins_with_its_nine_ones() {
let b = O150Sequence::Prbs9.bits(2 * 511);
assert_eq!(&b[..9], &[1; 9]);
assert_eq!(b[510], 0, "the bit before the run (one period earlier) is a zero");
assert_eq!(&b[..511], &b[511..]);
}
#[test]
fn an_inverted_sequence_is_the_negated_register_output() {
let t = O150Sequence::Prbs15.bits(32_767);
assert_eq!(&t[..15], &[0; 15]);
assert_eq!(t.iter().filter(|&&v| v == 0).count(), 16_384, "the 2^14 ones of the m-sequence are zeros");
let p = prbs(15, 2 * 32_767, 1).unwrap();
let negated: Vec<u8> = t.iter().map(|v| v ^ 1).collect();
assert!((0..32_767).any(|d| p[d..d + 32_767] == negated[..]), "a phase of the same m-sequence");
}
#[test]
fn the_zero_suppression_differs_from_the_plain_sequence_of_the_same_length() {
let (a, b) = (O150Sequence::Prbs20.bits(4096), O150Sequence::Prbs20Zs.bits(4096));
assert_ne!(a, b);
let longest = b.split(|&v| v == 1).map(<[u8]>::len).max().unwrap();
assert!(longest <= 14);
}
#[test]
fn the_names_round_trip() {
for s in O150Sequence::ALL {
assert_eq!(O150Sequence::from_name(s.name()).unwrap(), s);
assert_eq!(s.period(), (1u64 << s.stages()) - 1);
}
assert_eq!(O150Sequence::from_name("PRBS23").unwrap(), O150Sequence::Prbs23);
assert!(O150Sequence::from_name("prbs7").is_err());
}
#[test]
fn shaped_symbols_follow_the_distribution() {
let c = Constellation::qam(16).unwrap().normalized();
let px = maxwell_boltzmann(&c, 1.0);
let (idx, _) = symbols(&c, 200_000, Some(&px), &mut Rng::new(5)).unwrap();
for i in [0usize, 5, 15] {
let f = idx.iter().filter(|&&k| k == i).count() as f64 / idx.len() as f64;
assert!((f - px[i]).abs() < 0.005, "point {i}: {f} vs {}", px[i]);
}
}
}