use super::phase_hvec::PhaseHVec;
use std::f64::consts::TAU;
pub struct ResonatorConfig {
pub max_iter: usize,
pub convergence_threshold: f64,
}
impl Default for ResonatorConfig {
fn default() -> Self {
Self {
max_iter: 50,
convergence_threshold: 0.999,
}
}
}
pub struct FactorResult {
pub factor_idx: usize,
pub codebook_entry: usize,
pub similarity: f64,
pub converged: bool,
pub iterations: usize,
}
struct Cx {
re: Vec<f64>,
im: Vec<f64>,
}
impl Cx {
fn zeros(dim: usize) -> Self {
Self {
re: vec![0.0; dim],
im: vec![0.0; dim],
}
}
fn from_phases(v: &PhaseHVec) -> Self {
let n = v.n() as f64;
let re = v
.phases()
.iter()
.map(|&p| (TAU * p as f64 / n).cos())
.collect();
let im = v
.phases()
.iter()
.map(|&p| (TAU * p as f64 / n).sin())
.collect();
Self { re, im }
}
fn snap(&mut self, n: u32) {
for d in 0..self.re.len() {
let ang = self.im[d].atan2(self.re[d]);
let idx = (ang / TAU * n as f64).round() as i64;
let p = TAU * idx.rem_euclid(n as i64) as f64 / n as f64;
self.re[d] = p.cos();
self.im[d] = p.sin();
}
}
}
fn dot(a: &Cx, b: &Cx) -> f64 {
let mut s = 0.0;
for d in 0..a.re.len() {
s += a.re[d] * b.re[d] + a.im[d] * b.im[d];
}
s
}
fn cleanup(codebook: &[Cx], v: &Cx, n: u32) -> Cx {
let dim = v.re.len();
let mut out = Cx::zeros(dim);
for c in codebook {
let s = dot(c, v);
for d in 0..dim {
out.re[d] += s * c.re[d];
out.im[d] += s * c.im[d];
}
}
out.snap(n);
out
}
fn argmax(codebook: &[Cx], v: &Cx) -> (usize, f64) {
let dim = v.re.len().max(1) as f64;
(0..codebook.len())
.map(|k| (k, dot(&codebook[k], v) / dim))
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or((0, 0.0))
}
fn resonate(books: &[Vec<Cx>], n: u32, comp: &Cx, config: &ResonatorConfig) -> Vec<FactorResult> {
let k = books.len();
if k == 0 {
return Vec::new();
}
let mut est: Vec<Cx> = books
.iter()
.map(|cb| {
let mut acc = Cx::zeros(comp.re.len());
for c in cb {
for d in 0..acc.re.len() {
acc.re[d] += c.re[d];
acc.im[d] += c.im[d];
}
}
acc.snap(n);
acc
})
.collect();
let mut prev = vec![usize::MAX; k];
let mut stable = 0;
let mut iters = 0;
let mut converged = false;
while iters < config.max_iter {
iters += 1;
for f in 0..k {
let mut ub = Cx {
re: comp.re.clone(),
im: comp.im.clone(),
};
for (j, e) in est.iter().enumerate() {
if j != f {
for d in 0..ub.re.len() {
let (r, i) = (ub.re[d], ub.im[d]);
ub.re[d] = r * e.re[d] + i * e.im[d];
ub.im[d] = i * e.re[d] - r * e.im[d];
}
}
}
est[f] = cleanup(&books[f], &ub, n);
}
let cur: Vec<usize> = (0..k).map(|f| argmax(&books[f], &est[f]).0).collect();
if cur == prev {
stable += 1;
if stable >= 3 {
converged = true;
break;
}
} else {
stable = 0;
}
prev = cur;
}
(0..k)
.map(|f| {
let (idx, sim) = argmax(&books[f], &est[f]);
FactorResult {
factor_idx: f,
codebook_entry: idx,
similarity: sim,
converged,
iterations: iters,
}
})
.collect()
}
pub fn phase_resonator_factorize(
composite: &PhaseHVec,
codebooks: &[Vec<PhaseHVec>],
config: &ResonatorConfig,
) -> Vec<FactorResult> {
let books: Vec<Vec<Cx>> = codebooks
.iter()
.map(|cb| cb.iter().map(Cx::from_phases).collect())
.collect();
let comp = Cx::from_phases(composite);
resonate(&books, composite.n(), &comp, config)
}
pub struct PhaseResonator {
books: Vec<Vec<Cx>>,
n: u32,
config: ResonatorConfig,
}
impl PhaseResonator {
pub fn new(codebooks: &[Vec<PhaseHVec>]) -> Self {
Self::with_config(codebooks, ResonatorConfig::default())
}
pub fn with_config(codebooks: &[Vec<PhaseHVec>], config: ResonatorConfig) -> Self {
let n = codebooks
.iter()
.flatten()
.next()
.map(PhaseHVec::n)
.unwrap_or(2);
let books = codebooks
.iter()
.map(|cb| cb.iter().map(Cx::from_phases).collect())
.collect();
Self { books, n, config }
}
pub fn factor_count(&self) -> usize {
self.books.len()
}
pub fn factorize(&self, composite: &PhaseHVec) -> Vec<FactorResult> {
if self.books.is_empty() {
return Vec::new();
}
assert_eq!(
composite.n(),
self.n,
"composite phase resolution must match the codebooks'"
);
let comp = Cx::from_phases(composite);
resonate(&self.books, self.n, &comp, &self.config)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn codebooks(k: usize, f: usize, n: u32, dim: usize, seed: u64) -> Vec<Vec<PhaseHVec>> {
(0..k)
.map(|axis| {
(0..f)
.map(|i| PhaseHVec::new_random(dim, n, seed ^ ((axis as u64) << 40) ^ i as u64))
.collect()
})
.collect()
}
fn compose(cbs: &[Vec<PhaseHVec>], idx: &[usize]) -> PhaseHVec {
let mut c = cbs[0][idx[0]].clone();
for axis in 1..cbs.len() {
c = c.bind(&cbs[axis][idx[axis]]);
}
c
}
#[test]
fn recovers_two_factors() {
let cbs = codebooks(2, 12, 256, 1024, 0xAB);
let cfg = ResonatorConfig::default();
let target = [5usize, 9];
let res = phase_resonator_factorize(&compose(&cbs, &target), &cbs, &cfg);
assert_eq!(res[0].codebook_entry, target[0]);
assert_eq!(res[1].codebook_entry, target[1]);
}
#[test]
fn reusable_index_matches_free_function() {
let cbs = codebooks(3, 8, 256, 1024, 0xC0DE);
let cfg = ResonatorConfig::default();
let target = [4usize, 1, 5];
let composite = compose(&cbs, &target);
let idx = PhaseResonator::new(&cbs);
assert_eq!(idx.factor_count(), 3);
let via_struct = idx.factorize(&composite);
let via_free = phase_resonator_factorize(&composite, &cbs, &cfg);
for a in 0..3 {
assert_eq!(via_struct[a].codebook_entry, target[a]);
assert_eq!(via_struct[a].codebook_entry, via_free[a].codebook_entry);
}
}
#[test]
fn empty_codebooks_yield_no_factors() {
let cfg = ResonatorConfig::default();
let composite = PhaseHVec::new_random(64, 256, 1);
assert!(phase_resonator_factorize(&composite, &[], &cfg).is_empty());
let idx = PhaseResonator::new(&[]);
assert_eq!(idx.factor_count(), 0);
assert!(idx.factorize(&composite).is_empty());
}
#[test]
fn single_factor_and_single_entry_boundaries() {
let cbs = codebooks(1, 1, 256, 512, 0x11);
let composite = compose(&cbs, &[0]);
let res = PhaseResonator::new(&cbs).factorize(&composite);
assert_eq!(res.len(), 1);
assert_eq!(res[0].codebook_entry, 0);
let cbs = codebooks(1, 8, 256, 512, 0x22);
let composite = compose(&cbs, &[5]);
let res = phase_resonator_factorize(&composite, &cbs, &ResonatorConfig::default());
assert_eq!(res[0].codebook_entry, 5);
}
#[test]
fn factors_at_minimum_phase_resolution() {
let cbs = codebooks(2, 4, 2, 1024, 0x33);
let target = [1usize, 3];
let res =
phase_resonator_factorize(&compose(&cbs, &target), &cbs, &ResonatorConfig::default());
assert_eq!(res.len(), 2);
}
#[test]
fn reusable_index_derives_resolution_from_codebooks() {
let cbs = codebooks(2, 6, 64, 256, 0x44);
let idx = PhaseResonator::new(&cbs);
let composite = compose(&cbs, &[2, 4]);
assert_eq!(idx.factorize(&composite).len(), 2);
}
#[test]
#[should_panic(expected = "phase resolution")]
fn mismatched_resolution_panics() {
let cbs = codebooks(2, 6, 64, 256, 0x55);
let idx = PhaseResonator::new(&cbs);
let wrong = PhaseHVec::new_random(256, 256, 9);
let _ = idx.factorize(&wrong);
}
#[test]
fn factors_above_greedy_capacity() {
let dim = 1024;
let n = 256;
let f = 16;
let cbs = codebooks(3, f, n, dim, 0x5EED);
let cfg = ResonatorConfig {
max_iter: 40,
convergence_threshold: 0.999,
};
let mut ok = 0;
let trials = 20;
for t in 0..trials {
let idx: Vec<usize> = (0..3).map(|axis| (t * 7 + axis * 3) % f).collect();
let res = phase_resonator_factorize(&compose(&cbs, &idx), &cbs, &cfg);
if (0..3).all(|a| res[a].codebook_entry == idx[a]) {
ok += 1;
}
}
assert!(
ok as f64 / trials as f64 >= 0.8,
"expected >=80% recovery, got {ok}/{trials}"
);
}
#[test]
fn quantized_4bit_still_factors() {
let cbs = codebooks(3, 12, 16, 1024, 0x4B17);
let cfg = ResonatorConfig {
max_iter: 40,
convergence_threshold: 0.999,
};
let target = [3usize, 7, 10];
let res = phase_resonator_factorize(&compose(&cbs, &target), &cbs, &cfg);
assert!(
(0..3).all(|a| res[a].codebook_entry == target[a]),
"4-bit phase resonator must still factor"
);
}
}