#![allow(dead_code)]
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use viterbi::{
decoder::{CcsdsViterbiDecoder, ViterbiDecoder},
encoder::ViterbiEncoder,
metric::{HardHamming, SoftLlr},
params::CodeParams,
puncture::{DePuncturer, Puncturer},
PunctureMatrix,
};
pub fn codec() -> (ViterbiEncoder, CcsdsViterbiDecoder) {
(
ViterbiEncoder::new(CodeParams::ccsds_r1_2()).unwrap(),
CcsdsViterbiDecoder::new(CodeParams::ccsds_r1_2(), 65_536).unwrap(),
)
}
const LLR_SCALE: f32 = 24.0;
pub fn run_ber(eb_n0_db: f64, n_bits: usize, seed: u64, soft: bool) -> f64 {
run_ber_params(eb_n0_db, n_bits, seed, soft, CodeParams::ccsds_r1_2())
}
pub fn run_ber_params(
eb_n0_db: f64,
n_bits: usize,
seed: u64,
soft: bool,
params: CodeParams,
) -> f64 {
assert!(
n_bits.is_multiple_of(8),
"run_ber expects a byte-aligned bit budget"
);
let code_rate = 1.0 / params.n() as f64; let mut rng = StdRng::seed_from_u64(seed);
let mut data = vec![0u8; n_bits / 8];
rng.fill(data.as_mut_slice());
let enc = ViterbiEncoder::new(params.clone()).unwrap();
let coded = enc.encode_bits(&data, n_bits).unwrap();
let ncoded = coded.nbits;
let eb_n0_lin = 10f64.powf(eb_n0_db / 10.0);
let sigma = (1.0 / (2.0 * code_rate * eb_n0_lin)).sqrt();
let noise = gaussian(&mut rng, ncoded);
let mut noisy = Vec::with_capacity(ncoded);
for (i, &nz) in noise.iter().enumerate() {
let symbol = if read_bit(&coded.bytes, i) == 0 {
1.0
} else {
-1.0
};
noisy.push(symbol + sigma * nz);
}
let decoded = if soft {
let llrs: Vec<i8> = noisy
.iter()
.map(|&y| SoftLlr::llr_from_bpsk(y as f32, LLR_SCALE))
.collect();
let mut dec = ViterbiDecoder::<64, SoftLlr>::new(params, n_bits).unwrap();
dec.decode(&llrs, n_bits).unwrap()
} else {
let hard: Vec<u8> = noisy.iter().map(|&y| u8::from(y < 0.0)).collect();
let mut dec = ViterbiDecoder::<64, HardHamming>::new(params, n_bits).unwrap();
dec.decode(&hard, n_bits).unwrap()
};
let errors: u32 = data
.iter()
.zip(decoded.bytes.iter())
.map(|(a, b)| (a ^ b).count_ones())
.sum();
f64::from(errors) / n_bits as f64
}
pub fn run_ber_punctured(
eb_n0_db: f64,
n_bits: usize,
seed: u64,
soft: bool,
params: CodeParams,
matrix: PunctureMatrix,
) -> f64 {
assert!(
n_bits.is_multiple_of(8),
"run_ber_punctured expects a byte-aligned bit budget"
);
let code_rate = matrix.period() as f64 / matrix.kept_per_period() as f64;
let mut rng = StdRng::seed_from_u64(seed);
let mut data = vec![0u8; n_bits / 8];
rng.fill(data.as_mut_slice());
let enc = ViterbiEncoder::new(params.clone()).unwrap();
let coded = enc.encode_bits(&data, n_bits).unwrap();
let punc = Puncturer::new(matrix.clone(), ¶ms).unwrap();
let punctured = punc.puncture(&coded, n_bits).unwrap();
let ncoded = punctured.nbits;
let eb_n0_lin = 10f64.powf(eb_n0_db / 10.0);
let sigma = (1.0 / (2.0 * code_rate * eb_n0_lin)).sqrt();
let noise = gaussian(&mut rng, ncoded);
let mut noisy = Vec::with_capacity(ncoded);
for (i, &nz) in noise.iter().enumerate() {
let symbol = if read_bit(&punctured.bytes, i) == 0 {
1.0
} else {
-1.0
};
noisy.push(symbol + sigma * nz);
}
let decoded = if soft {
let llrs: Vec<i8> = noisy
.iter()
.map(|&y| SoftLlr::llr_from_bpsk(y as f32, LLR_SCALE))
.collect();
let dp = DePuncturer::<SoftLlr>::new(matrix, ¶ms).unwrap();
let full = dp.depuncture(&llrs, n_bits).unwrap();
let mut dec = ViterbiDecoder::<64, SoftLlr>::new(params, n_bits).unwrap();
dec.decode(&full, n_bits).unwrap()
} else {
let hard: Vec<u8> = noisy.iter().map(|&y| u8::from(y < 0.0)).collect();
let dp = DePuncturer::<HardHamming>::new(matrix, ¶ms).unwrap();
let full = dp.depuncture(&hard, n_bits).unwrap();
let mut dec = ViterbiDecoder::<64, HardHamming>::new(params, n_bits).unwrap();
dec.decode(&full, n_bits).unwrap()
};
let errors: u32 = data
.iter()
.zip(decoded.bytes.iter())
.map(|(a, b)| (a ^ b).count_ones())
.sum();
f64::from(errors) / n_bits as f64
}
fn read_bit(bytes: &[u8], i: usize) -> u8 {
(bytes[i / 8] >> (7 - (i % 8))) & 1
}
fn gaussian(rng: &mut StdRng, n: usize) -> Vec<f64> {
let mut out = Vec::with_capacity(n);
while out.len() < n {
let u1 = 1.0 - rng.gen::<f64>();
let u2 = rng.gen::<f64>();
let r = (-2.0 * u1.ln()).sqrt();
let theta = 2.0 * std::f64::consts::PI * u2;
out.push(r * theta.cos());
if out.len() < n {
out.push(r * theta.sin());
}
}
out
}