use super::codebook::{
CODEBOOK, CODEBOOK_BITS, CODEBOOK_EXTENDED, CODEBOOK_EXTENDED_MIN_CYCLIC_DIST,
CODEBOOK_EXTENDED_SEED, CODEBOOK_MIN_CYCLIC_DIST, CODEBOOK_SEED,
};
#[inline]
fn popcount(x: u16) -> u8 {
x.count_ones() as u8
}
#[inline]
pub fn rotate_left_16(word: u16, k: u32) -> u16 {
word.rotate_left(k % 16)
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn canonical_16(word: u16) -> u16 {
(0..16).map(|k| rotate_left_16(word, k)).min().unwrap()
}
#[cfg(test)]
#[inline]
fn cyclic_distance(a: u16, b: u16) -> u8 {
(0u32..CODEBOOK_BITS as u32)
.map(|k| popcount(a ^ rotate_left_16(b, k)))
.min()
.unwrap()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CodebookProfile {
#[default]
Base,
Extended,
}
impl CodebookProfile {
pub const fn as_str(self) -> &'static str {
match self {
Self::Base => "base",
Self::Extended => "extended",
}
}
}
pub struct Codebook {
profile: CodebookProfile,
words: &'static [u16],
min_cyclic_dist: usize,
seed: u64,
}
impl Default for Codebook {
fn default() -> Self {
Self::from_profile(CodebookProfile::Base)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Match {
pub id: usize,
pub rotation: u8,
pub dist: u8,
pub margin: u8,
pub confidence: f32,
}
impl Codebook {
#[cfg_attr(not(test), allow(dead_code))]
pub fn new(words: &'static [u16]) -> Self {
Self {
profile: CodebookProfile::Base,
words,
min_cyclic_dist: 1,
seed: 0,
}
}
pub fn from_profile(profile: CodebookProfile) -> Self {
match profile {
CodebookProfile::Base => Self {
profile,
words: &CODEBOOK,
min_cyclic_dist: CODEBOOK_MIN_CYCLIC_DIST,
seed: CODEBOOK_SEED,
},
CodebookProfile::Extended => Self {
profile,
words: &CODEBOOK_EXTENDED,
min_cyclic_dist: CODEBOOK_EXTENDED_MIN_CYCLIC_DIST,
seed: CODEBOOK_EXTENDED_SEED,
},
}
}
pub fn profile(&self) -> CodebookProfile {
self.profile
}
pub fn bits(&self) -> usize {
CODEBOOK_BITS
}
pub fn min_cyclic_dist(&self) -> usize {
self.min_cyclic_dist
}
pub fn seed(&self) -> u64 {
self.seed
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn len(&self) -> usize {
self.words.len()
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn is_empty(&self) -> bool {
self.words.is_empty()
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn word(&self, id: usize) -> Option<u16> {
self.words.get(id).copied()
}
pub fn match_word(&self, obs: u16) -> Match {
let mut best_id = 0usize;
let mut best_rot = 0u8;
let mut best_dist = u8::MAX;
let mut second_dist = u8::MAX;
for (id, &cw) in self.words.iter().enumerate() {
for k in 0u32..self.bits() as u32 {
let rotated = rotate_left_16(cw, k);
let d = popcount(obs ^ rotated);
if d < best_dist {
second_dist = best_dist;
best_dist = d;
best_id = id;
best_rot = k as u8;
} else if d < second_dist {
second_dist = d;
}
}
}
let margin = second_dist.saturating_sub(best_dist);
let conf_dist = (1.0 - best_dist as f32 / 6.0).clamp(0.0, 1.0);
let conf_margin = (margin as f32 / self.min_cyclic_dist as f32).clamp(0.0, 1.0);
let confidence = conf_dist * conf_margin;
Match {
id: best_id,
rotation: best_rot,
dist: best_dist,
margin,
confidence,
}
}
}
#[cfg(test)]
mod tests {
use super::super::codebook::{
CODEBOOK_EXTENDED, CODEBOOK_EXTENDED_EXTENSION_N, CODEBOOK_EXTENDED_MIN_CYCLIC_DIST,
CODEBOOK_EXTENDED_N, CODEBOOK_MIN_CYCLIC_DIST, CODEBOOK_N,
};
use super::*;
use rand::RngExt;
use rand::prelude::*;
use std::collections::HashSet;
fn assert_no_codeword_is_rotationally_symmetric(words: &[u16]) {
for (i, &w) in words.iter().enumerate() {
for k in 1u32..16 {
assert_ne!(
rotate_left_16(w, k),
w,
"codeword {} (0x{:04X}) is rotationally symmetric at k={}",
i,
w,
k
);
}
}
}
fn assert_pairwise_uniqueness_under_rotation(words: &[u16]) {
let mut canonical_words = HashSet::with_capacity(words.len());
for (i, &w) in words.iter().enumerate() {
assert!(
canonical_words.insert(canonical_16(w)),
"codeword {} (0x{:04X}) collides under rotation with another entry",
i,
w
);
}
}
#[test]
fn test_no_codeword_is_rotationally_symmetric() {
assert_no_codeword_is_rotationally_symmetric(&CODEBOOK);
assert_no_codeword_is_rotationally_symmetric(&CODEBOOK_EXTENDED);
}
#[test]
fn test_pairwise_uniqueness_under_rotation() {
assert_pairwise_uniqueness_under_rotation(&CODEBOOK);
assert_pairwise_uniqueness_under_rotation(&CODEBOOK_EXTENDED);
}
#[test]
fn test_pairwise_min_cyclic_distance() {
let n = CODEBOOK.len();
let mut rng = StdRng::seed_from_u64(123);
let pairs = 20_000.min(n * (n - 1) / 2);
let mut observed_min = 16u8;
for _ in 0..pairs {
let i = rng.random_range(0..n);
let j = rng.random_range(0..n);
if i == j {
continue;
}
let d = cyclic_distance(CODEBOOK[i], CODEBOOK[j]);
if d < observed_min {
observed_min = d;
}
}
assert!(
observed_min >= CODEBOOK_MIN_CYCLIC_DIST as u8,
"observed min cyclic dist {} < claimed {}",
observed_min,
CODEBOOK_MIN_CYCLIC_DIST
);
}
#[test]
fn test_default_profile_matches_shipped_baseline() {
let cb = Codebook::default();
assert_eq!(cb.profile(), CodebookProfile::Base);
assert_eq!(cb.len(), CODEBOOK_N);
assert_eq!(cb.min_cyclic_dist(), CODEBOOK_MIN_CYCLIC_DIST);
assert_eq!(cb.seed(), CODEBOOK_SEED);
}
#[test]
fn test_extended_profile_appends_base_prefix() {
let cb = Codebook::from_profile(CodebookProfile::Extended);
assert_eq!(cb.profile(), CodebookProfile::Extended);
assert_eq!(cb.len(), CODEBOOK_EXTENDED_N);
assert_eq!(cb.min_cyclic_dist(), CODEBOOK_EXTENDED_MIN_CYCLIC_DIST);
assert_eq!(&CODEBOOK_EXTENDED[..CODEBOOK_N], &CODEBOOK);
assert_eq!(
CODEBOOK_EXTENDED_EXTENSION_N,
CODEBOOK_EXTENDED_N - CODEBOOK_N
);
}
#[test]
fn test_extended_profile_has_distance_one_witness() {
let appended = &CODEBOOK_EXTENDED[CODEBOOK_N..];
assert!(
appended
.iter()
.any(|&ext| CODEBOOK.iter().any(|&base| cyclic_distance(ext, base) == 1)),
"extended profile should witness the weaker min distance of 1"
);
}
#[test]
fn test_extended_profile_appendix_excludes_new_complement_collisions() {
let canonical_words: HashSet<u16> =
CODEBOOK_EXTENDED.iter().map(|&w| canonical_16(w)).collect();
for (offset, &w) in CODEBOOK_EXTENDED[CODEBOOK_N..].iter().enumerate() {
let canonical = canonical_16(w);
let complement = canonical_16(!w);
if complement == canonical {
continue;
}
assert!(
!canonical_words.contains(&complement),
"appended codeword {} (0x{:04X}) collides with complement class 0x{:04X}",
CODEBOOK_N + offset,
w,
complement
);
}
}
#[test]
fn test_extended_profile_exact_match_for_first_appended_id() {
let cb = Codebook::from_profile(CodebookProfile::Extended);
let w = cb
.word(CODEBOOK_N)
.expect("extended profile should expose appended IDs");
let m = cb.match_word(w);
assert_eq!(m.id, CODEBOOK_N);
assert_eq!(m.dist, 0);
assert_eq!(m.rotation, 0);
}
#[test]
fn test_exact_match() {
let cb = Codebook::default();
for id in 0..cb.len().min(100) {
let w = cb.word(id).unwrap();
let m = cb.match_word(w);
assert_eq!(m.id, id, "exact match failed for id {}", id);
assert_eq!(m.dist, 0);
assert_eq!(m.rotation, 0);
}
}
#[test]
fn test_match_with_rotation() {
let cb = Codebook::default();
let mut rng = StdRng::seed_from_u64(77);
for _ in 0..100 {
let id = rng.random_range(0..cb.len());
let w = cb.word(id).unwrap();
let rot = rng.random_range(0u32..16);
let rotated = rotate_left_16(w, rot);
let m = cb.match_word(rotated);
assert_eq!(m.id, id, "rotation match failed for id {} rot {}", id, rot);
assert_eq!(m.dist, 0);
}
}
#[test]
fn test_match_with_1_bit_flip() {
let cb = Codebook::default();
let mut rng = StdRng::seed_from_u64(99);
let mut correct = 0;
let total = 200;
for _ in 0..total {
let id = rng.random_range(0..cb.len());
let w = cb.word(id).unwrap();
let rot = rng.random_range(0u32..16);
let mut obs = rotate_left_16(w, rot);
let bit = rng.random_range(0..16);
obs ^= 1u16 << bit;
let m = cb.match_word(obs);
assert!(
m.dist <= 1,
"1-bit flip should give dist <= 1, got {}",
m.dist
);
if m.id == id {
correct += 1;
}
}
let rate = correct as f64 / total as f64;
eprintln!("1-bit flip correct rate: {:.1}%", rate * 100.0);
}
#[test]
fn test_match_with_2_bit_flips() {
let cb = Codebook::default();
let mut rng = StdRng::seed_from_u64(2024);
let mut correct = 0;
let total = 200;
for _ in 0..total {
let id = rng.random_range(0..cb.len());
let w = cb.word(id).unwrap();
let rot = rng.random_range(0u32..16);
let mut obs = rotate_left_16(w, rot);
let bit1 = rng.random_range(0u16..16);
let mut bit2 = rng.random_range(0u16..15);
if bit2 >= bit1 {
bit2 += 1;
}
obs ^= (1u16 << bit1) | (1u16 << bit2);
let m = cb.match_word(obs);
assert!(
m.dist <= 2,
"2-bit flip should give dist <= 2, got {}",
m.dist
);
if m.id == id {
correct += 1;
}
}
let rate = correct as f64 / total as f64;
eprintln!("2-bit flip correct rate: {:.1}%", rate * 100.0);
}
#[test]
fn test_confidence_heuristic() {
let cb = Codebook::default();
let w = cb.word(0).unwrap();
let m = cb.match_word(w);
assert!(
m.confidence > 0.0,
"exact match should have positive confidence"
);
let flipped = !w;
let m2 = cb.match_word(flipped);
let _ = m2; }
#[test]
fn test_canonical() {
assert_eq!(canonical_16(0x0001), 0x0001);
assert_eq!(canonical_16(0x8000), 0x0001); assert_eq!(canonical_16(0x0003), 0x0003);
assert_eq!(canonical_16(0xC000), 0x0003); }
}