use crate::codebook::Codebook;
use crate::hyper::Hyper;
#[derive(Clone, Debug, PartialEq)]
pub struct Factorization {
pub factors: Vec<usize>,
pub iterations: usize,
pub converged: bool,
pub verified: bool,
pub restarts: usize,
}
#[derive(Clone)]
pub struct Resonator {
codebooks: Vec<Codebook>,
dim: usize,
}
impl Resonator {
pub fn new(codebooks: Vec<Codebook>) -> Self {
assert!(!codebooks.is_empty(), "need at least one factor");
let dim = codebooks[0].dim();
assert!(
codebooks.iter().all(|c| c.dim() == dim),
"all factor codebooks must share the same dimension"
);
Resonator { codebooks, dim }
}
pub fn uniform(dim: usize, factors: usize, count: usize, seed: u64) -> Self {
let codebooks = (0..factors)
.map(|f| {
Codebook::new(
dim,
count,
seed.wrapping_add(f as u64).wrapping_mul(0x9E37_79B9),
)
})
.collect();
Resonator::new(codebooks)
}
#[inline]
pub fn factors(&self) -> usize {
self.codebooks.len()
}
#[inline]
pub fn dim(&self) -> usize {
self.dim
}
#[inline]
pub fn codebook(&self, f: usize) -> &Codebook {
&self.codebooks[f]
}
pub fn compose(&self, ids: &[usize]) -> Hyper {
assert_eq!(ids.len(), self.factors(), "one id per factor");
let mut acc = self.codebooks[0].atom(ids[0]);
for (f, &id) in ids.iter().enumerate().skip(1) {
acc = acc.bind(&self.codebooks[f].atom(id));
}
acc
}
fn project(&self, f: usize, v: &Hyper) -> Hyper {
let scores = self.codebooks[f].scores_batch(core::slice::from_ref(v));
self.codebooks[f].superpose(&scores[0]).sign()
}
fn initial_estimate(&self, f: usize, restart: usize) -> Hyper {
if restart == 0 {
let ones = vec![1.0f32; self.codebooks[f].len()];
self.codebooks[f].superpose(&ones).sign()
} else {
Hyper::atom(self.dim, 0xC0DE_F00D ^ restart as u64, f as u64)
}
}
pub fn verify(&self, composite: &Hyper, ids: &[usize]) -> bool {
ids.len() == self.factors() && self.compose(ids) == *composite
}
fn run_once(&self, composite: &Hyper, max_iters: usize, restart: usize) -> (Vec<usize>, usize) {
let f = self.factors();
let mut est: Vec<Hyper> = (0..f).map(|i| self.initial_estimate(i, restart)).collect();
let mut prev_ids: Vec<usize> = vec![usize::MAX; f];
for iter in 1..=max_iters {
let mut next = Vec::with_capacity(f);
for target in 0..f {
let mut unbound = composite.clone();
for (other, e) in est.iter().enumerate() {
if other != target {
unbound = unbound.bind(e);
}
}
next.push(self.project(target, &unbound));
}
est = next;
let ids: Vec<usize> = (0..f)
.map(|i| self.codebooks[i].cleanup(&est[i]).0)
.collect();
if ids == prev_ids {
return (ids, iter);
}
prev_ids = ids;
}
(prev_ids, max_iters)
}
pub fn factorize(&self, composite: &Hyper, max_iters: usize) -> Factorization {
self.factorize_with(composite, max_iters, 25)
}
pub fn factorize_with(
&self,
composite: &Hyper,
max_iters: usize,
max_restarts: usize,
) -> Factorization {
let mut fallback: Option<(Vec<usize>, usize, usize)> = None;
for restart in 0..max_restarts.max(1) {
let (ids, iters) = self.run_once(composite, max_iters, restart);
let converged = iters < max_iters;
if self.verify(composite, &ids) {
return Factorization {
factors: ids,
iterations: iters,
converged,
verified: true,
restarts: restart,
};
}
if fallback.is_none() {
fallback = Some((ids, iters, restart));
}
}
let (ids, iters, restart) = fallback.expect("at least one attempt runs");
Factorization {
factors: ids,
iterations: iters,
converged: false,
verified: false,
restarts: restart,
}
}
}
impl core::fmt::Debug for Resonator {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Resonator {{ factors: {}, dim: {}, vocab: {} }}",
self.factors(),
self.dim,
self.codebooks.first().map(|c| c.len()).unwrap_or(0)
)
}
}