use crate::bsc::{bundle, Hypervector};
use crate::rng::Rng;
pub struct LevelEncoder {
d: usize,
n_features: usize,
min: f64,
max: f64,
positions: Vec<Hypervector>,
levels: Vec<Hypervector>, }
impl LevelEncoder {
pub fn new(
d: usize,
n_features: usize,
min: f64,
max: f64,
n_levels: usize,
rng: &mut Rng,
) -> Self {
assert!(max > min, "max must be greater than min");
assert!(n_levels >= 1, "need at least one level");
let positions = (0..n_features)
.map(|_| Hypervector::random(d, rng))
.collect();
let levels = make_levels(d, n_levels, rng);
LevelEncoder {
d,
n_features,
min,
max,
positions,
levels,
}
}
#[inline]
fn level_index(&self, value: f64) -> usize {
let n = self.levels.len() - 1;
let t = ((value - self.min) / (self.max - self.min)).clamp(0.0, 1.0);
(t * n as f64).round() as usize
}
pub fn encode(&self, features: &[f64]) -> Hypervector {
assert_eq!(features.len(), self.n_features, "feature count mismatch");
let terms: Vec<Hypervector> = features
.iter()
.enumerate()
.map(|(i, &v)| self.positions[i].bind(&self.levels[self.level_index(v)]))
.collect();
bundle(&terms)
}
pub fn dim(&self) -> usize {
self.d
}
}
fn make_levels(d: usize, n_levels: usize, rng: &mut Rng) -> Vec<Hypervector> {
let mut order: Vec<usize> = (0..d).collect();
for k in 0..d {
let j = k + (rng.next_u64() as usize) % (d - k);
order.swap(k, j);
}
let mut current = Hypervector::random(d, rng);
let mut out = Vec::with_capacity(n_levels + 1);
out.push(current.clone());
let step = (d / n_levels).max(1);
for l in 0..n_levels {
let start = (l * step).min(d);
let end = ((l + 1) * step).min(d);
for &pos in &order[start..end] {
current.flip(pos);
}
out.push(current.clone());
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Rng;
#[test]
fn similar_inputs_encode_similarly() {
let mut rng = Rng::new(20);
let enc = LevelEncoder::new(10_000, 4, 0.0, 1.0, 20, &mut rng);
let a = enc.encode(&[0.10, 0.50, 0.90, 0.30]);
let near = enc.encode(&[0.12, 0.52, 0.88, 0.31]); let far = enc.encode(&[0.90, 0.10, 0.20, 0.80]); assert!(a.similarity(&near) > a.similarity(&far));
assert!(a.similarity(&near) > 0.5);
}
}