1use std::borrow::Borrow;
22use std::f64::consts::TAU;
23
24const SCALE: i64 = 1 << 14;
26
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct PhaseHVec {
29 dim: usize,
30 n: u32,
31 phases: Vec<u16>,
32}
33
34fn mix(x: u64, k: u64) -> u64 {
35 let mut h = x.wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ k.wrapping_mul(0xC2B2_AE3D_27D4_EB4F);
36 h ^= h >> 29;
37 h = h.wrapping_mul(0xBF58_476D_1CE4_E5B9);
38 h ^= h >> 32;
39 h
40}
41
42fn luts(n: u32) -> (Vec<i64>, Vec<i64>) {
44 let cos = (0..n)
45 .map(|k| (SCALE as f64 * (TAU * k as f64 / n as f64).cos()).round() as i64)
46 .collect();
47 let sin = (0..n)
48 .map(|k| (SCALE as f64 * (TAU * k as f64 / n as f64).sin()).round() as i64)
49 .collect();
50 (cos, sin)
51}
52
53impl PhaseHVec {
54 pub fn new_random(dim: usize, n: u32, seed: u64) -> Self {
57 assert!(
58 (2..=65536).contains(&n),
59 "phase resolution n must be in 2..=65536"
60 );
61 let phases = (0..dim)
62 .map(|d| (mix(d as u64, seed) % n as u64) as u16)
63 .collect();
64 Self { dim, n, phases }
65 }
66
67 pub fn from_phases(phases: Vec<u16>, n: u32) -> Self {
69 assert!(
70 (2..=65536).contains(&n),
71 "phase resolution n must be in 2..=65536"
72 );
73 assert!(
74 phases.iter().all(|&p| (p as u32) < n),
75 "phase index out of range"
76 );
77 Self {
78 dim: phases.len(),
79 n,
80 phases,
81 }
82 }
83
84 pub fn dim(&self) -> usize {
86 self.dim
87 }
88 pub fn n(&self) -> u32 {
91 self.n
92 }
93 pub fn phases(&self) -> &[u16] {
95 &self.phases
96 }
97
98 fn check(&self, other: &Self) {
99 assert_eq!(self.dim, other.dim, "phase-vector dimension mismatch");
100 assert_eq!(self.n, other.n, "phase-vector resolution mismatch");
101 }
102
103 pub fn bind(&self, other: &Self) -> Self {
105 self.check(other);
106 let n = self.n;
107 let phases = self
108 .phases
109 .iter()
110 .zip(&other.phases)
111 .map(|(&a, &b)| ((a as u32 + b as u32) % n) as u16)
112 .collect();
113 Self {
114 dim: self.dim,
115 n: self.n,
116 phases,
117 }
118 }
119
120 pub fn unbind(&self, other: &Self) -> Self {
122 self.check(other);
123 let n = self.n;
124 let phases = self
125 .phases
126 .iter()
127 .zip(&other.phases)
128 .map(|(&a, &b)| ((a as u32 + n - b as u32) % n) as u16)
129 .collect();
130 Self {
131 dim: self.dim,
132 n: self.n,
133 phases,
134 }
135 }
136
137 pub fn similarity(&self, other: &Self) -> f64 {
140 self.check(other);
141 if self.dim == 0 {
142 return 1.0;
143 }
144 let n = self.n as f64;
145 let s: f64 = self
146 .phases
147 .iter()
148 .zip(&other.phases)
149 .map(|(&a, &b)| (TAU * (a as f64 - b as f64) / n).cos())
150 .sum();
151 s / self.dim as f64
152 }
153
154 pub fn bundle<V: Borrow<Self>>(vectors: &[V]) -> Self {
159 if vectors.is_empty() {
160 return Self {
161 dim: 0,
162 n: 2,
163 phases: Vec::new(),
164 };
165 }
166 let first = vectors[0].borrow();
167 let (dim, n) = (first.dim, first.n);
168 let (cos, sin) = luts(n);
169 let mut re = vec![0i64; dim];
170 let mut im = vec![0i64; dim];
171 for v in vectors {
172 let v = v.borrow();
173 assert_eq!(v.dim, dim, "phase-vector dimension mismatch in bundle");
174 assert_eq!(v.n, n, "phase-vector resolution mismatch in bundle");
175 for d in 0..dim {
176 re[d] += cos[v.phases[d] as usize];
177 im[d] += sin[v.phases[d] as usize];
178 }
179 }
180 let phases = (0..dim)
181 .map(|d| {
182 let ang = (im[d] as f64).atan2(re[d] as f64);
183 let idx = (ang / TAU * n as f64).round() as i64;
184 idx.rem_euclid(n as i64) as u16
185 })
186 .collect();
187 Self { dim, n, phases }
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn bind_unbind_is_exact_roundtrip() {
197 let a = PhaseHVec::new_random(512, 256, 1);
198 let b = PhaseHVec::new_random(512, 256, 2);
199 let recovered = a.bind(&b).unbind(&b);
200 assert_eq!(
201 recovered.phases(),
202 a.phases(),
203 "unbind(bind(a,b),b) must equal a"
204 );
205 }
206
207 #[test]
208 fn similarity_self_is_one_random_near_zero() {
209 let a = PhaseHVec::new_random(4096, 256, 7);
210 assert!((a.similarity(&a) - 1.0).abs() < 1e-9);
211 let b = PhaseHVec::new_random(4096, 256, 8);
212 assert!(
213 a.similarity(&b).abs() < 0.05,
214 "independent vectors ~orthogonal"
215 );
216 }
217
218 #[test]
219 fn binding_is_not_self_inverse() {
220 let a = PhaseHVec::new_random(2048, 256, 3);
222 let self_bound = a.bind(&a);
223 assert!(
224 self_bound.similarity(&a).abs() < 0.2,
225 "phase self-bind must not collapse back to a (non-self-inverse)"
226 );
227 }
228
229 #[test]
230 fn bundle_preserves_membership() {
231 let members: Vec<PhaseHVec> = (0..5)
232 .map(|i| PhaseHVec::new_random(4096, 256, 100 + i))
233 .collect();
234 let bundle = PhaseHVec::bundle(&members);
235 let member_sim = bundle.similarity(&members[0]);
236 let nonmember = PhaseHVec::new_random(4096, 256, 999);
237 assert!(
238 member_sim > bundle.similarity(&nonmember) + 0.05,
239 "a bundled member ({member_sim:.3}) must score above a non-member"
240 );
241 }
242
243 #[test]
244 fn bundle_is_deterministic_and_order_free() {
245 let v: Vec<PhaseHVec> = (0..4)
246 .map(|i| PhaseHVec::new_random(256, 64, 10 + i))
247 .collect();
248 let ab = PhaseHVec::bundle(&v);
249 let mut rev = v.clone();
250 rev.reverse();
251 let ba = PhaseHVec::bundle(&rev);
252 assert_eq!(
253 ab.phases(),
254 ba.phases(),
255 "bundle must be order-independent and deterministic"
256 );
257 }
258
259 #[test]
260 fn bundle_empty_is_zero_dim() {
261 let empty: Vec<PhaseHVec> = Vec::new();
262 let b = PhaseHVec::bundle(&empty);
263 assert_eq!(b.dim(), 0);
264 assert!(b.phases().is_empty());
265 }
266
267 #[test]
268 fn bundle_single_element_is_identity() {
269 let a = PhaseHVec::new_random(512, 256, 42);
270 let b = PhaseHVec::bundle(std::slice::from_ref(&a));
271 assert_eq!(
272 b.phases(),
273 a.phases(),
274 "bundling one vector must return its own phases"
275 );
276 }
277
278 #[test]
279 fn zero_dim_similarity_is_one() {
280 let a = PhaseHVec::from_phases(Vec::new(), 2);
282 let b = PhaseHVec::from_phases(Vec::new(), 2);
283 assert_eq!(a.dim(), 0);
284 assert!((a.similarity(&b) - 1.0).abs() < 1e-12);
285 }
286
287 #[test]
288 fn minimum_resolution_roundtrip() {
289 let a = PhaseHVec::new_random(256, 2, 3);
291 let b = PhaseHVec::new_random(256, 2, 4);
292 assert_eq!(a.bind(&b).unbind(&b).phases(), a.phases());
293 }
294
295 #[test]
296 fn maximum_resolution_constructs() {
297 let a = PhaseHVec::new_random(128, 65536, 7);
299 assert_eq!(a.n(), 65536);
300 assert!((a.similarity(&a) - 1.0).abs() < 1e-9);
301 }
302}