1use std::sync::Arc;
36
37use vyre_foundation::ir::model::expr::Ident;
38use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
39
40pub const BIND_OP_ID: &str = "vyre-primitives::hash::hypervector_xor_bind";
42pub const BUNDLE_OP_ID: &str = "vyre-primitives::hash::hypervector_majority_bundle";
44
45pub const STANDARD_DIM_BITS: u32 = 10240;
50pub const STANDARD_DIM_WORDS: u32 = STANDARD_DIM_BITS / 32;
52
53#[must_use]
55pub fn hypervector_xor_bind(a: &str, b: &str, out: &str, dim_words: u32) -> Program {
56 if dim_words == 0 {
57 return crate::invalid_output_program(
58 BIND_OP_ID,
59 out,
60 DataType::U32,
61 "Fix: hypervector_xor_bind requires dim_words > 0, got 0.".to_string(),
62 );
63 }
64
65 let t = Expr::InvocationId { axis: 0 };
66 let body = vec![Node::if_then(
67 Expr::lt(t.clone(), Expr::u32(dim_words)),
68 vec![Node::store(
69 out,
70 t.clone(),
71 Expr::bitxor(Expr::load(a, t.clone()), Expr::load(b, t)),
72 )],
73 )];
74
75 Program::wrapped(
76 vec![
77 BufferDecl::storage(a, 0, BufferAccess::ReadOnly, DataType::U32).with_count(dim_words),
78 BufferDecl::storage(b, 1, BufferAccess::ReadOnly, DataType::U32).with_count(dim_words),
79 BufferDecl::storage(out, 2, BufferAccess::ReadWrite, DataType::U32)
80 .with_count(dim_words),
81 ],
82 [256, 1, 1],
83 vec![Node::Region {
84 generator: Ident::from(BIND_OP_ID),
85 source_region: None,
86 body: Arc::new(body),
87 }],
88 )
89}
90
91#[must_use]
98pub fn hypervector_majority_bundle(stacked: &str, out: &str, dim_words: u32, k: u32) -> Program {
99 if dim_words == 0 {
100 return crate::invalid_output_program(
101 BUNDLE_OP_ID,
102 out,
103 DataType::U32,
104 "Fix: hypervector_majority_bundle requires dim_words > 0, got 0.".to_string(),
105 );
106 }
107 if k == 0 {
108 return crate::invalid_output_program(
109 BUNDLE_OP_ID,
110 out,
111 DataType::U32,
112 "Fix: hypervector_majority_bundle requires k > 0, got 0.".to_string(),
113 );
114 }
115 let Some(stacked_words) = k.checked_mul(dim_words) else {
116 return crate::invalid_output_program(BUNDLE_OP_ID,
117 out,
118 DataType::U32,
119 format!(
120 "Fix: hypervector_majority_bundle k*dim_words overflows stacked input count for k={k}, dim_words={dim_words}; shard the bundle before GPU dispatch."
121 ),);
122 };
123
124 let t = Expr::InvocationId { axis: 0 };
125 let threshold = k / 2; let body = vec![Node::if_then(
128 Expr::lt(t.clone(), Expr::u32(dim_words)),
129 vec![
130 Node::let_bind("acc", Expr::u32(0)),
131 Node::loop_for(
132 "bit",
133 Expr::u32(0),
134 Expr::u32(32),
135 vec![
136 Node::let_bind("count", Expr::u32(0)),
137 Node::loop_for(
138 "ii",
139 Expr::u32(0),
140 Expr::u32(k),
141 vec![
142 Node::let_bind("_unused_assign", Expr::u32(0)),
143 Node::assign(
144 "count",
145 Expr::add(
146 Expr::var("count"),
147 Expr::bitand(
148 Expr::shr(
149 Expr::load(
150 stacked,
151 Expr::add(
152 Expr::mul(
153 Expr::var("ii"),
154 Expr::u32(dim_words),
155 ),
156 t.clone(),
157 ),
158 ),
159 Expr::var("bit"),
160 ),
161 Expr::u32(1),
162 ),
163 ),
164 ),
165 ],
166 ),
167 Node::if_then(
168 Expr::gt(Expr::var("count"), Expr::u32(threshold)),
169 vec![Node::assign(
170 "acc",
171 Expr::bitor(
172 Expr::var("acc"),
173 Expr::shl(Expr::u32(1), Expr::var("bit")),
174 ),
175 )],
176 ),
177 ],
178 ),
179 Node::store(out, t, Expr::var("acc")),
180 ],
181 )];
182
183 Program::wrapped(
184 vec![
185 BufferDecl::storage(stacked, 0, BufferAccess::ReadOnly, DataType::U32)
186 .with_count(stacked_words),
187 BufferDecl::storage(out, 1, BufferAccess::ReadWrite, DataType::U32)
188 .with_count(dim_words),
189 ],
190 [256, 1, 1],
191 vec![Node::Region {
192 generator: Ident::from(BUNDLE_OP_ID),
193 source_region: None,
194 body: Arc::new(body),
195 }],
196 )
197}
198
199#[must_use]
203#[cfg(any(test, feature = "cpu-parity"))]
204pub fn xor_bind_cpu(a: &[u32], b: &[u32]) -> Vec<u32> {
205 let mut out = Vec::new();
206 match try_xor_bind_cpu_into(a, b, &mut out) {
207 Ok(()) => out,
208 Err(error) => panic!("vyre-primitives hypervector XOR bind CPU reference failed: {error}"),
212 }
213}
214
215#[cfg(any(test, feature = "cpu-parity"))]
217pub fn xor_bind_cpu_into(a: &[u32], b: &[u32], out: &mut Vec<u32>) {
218 if let Err(error) = try_xor_bind_cpu_into(a, b, out) {
219 panic!("vyre-primitives hypervector XOR bind CPU reference failed: {error}");
220 }
221}
222
223#[cfg(any(test, feature = "cpu-parity"))]
225pub fn try_xor_bind_cpu_into(a: &[u32], b: &[u32], out: &mut Vec<u32>) -> Result<(), String> {
226 let dim_words = a.len().min(b.len());
227 vyre_foundation::allocation::reserve_exact_cleared(out, dim_words).map_err(|err| {
228 format!("hypervector XOR bind could not reserve {dim_words} output words: {err}")
229 })?;
230 out.extend(a.iter().zip(b.iter()).take(dim_words).map(|(&x, &y)| x ^ y));
231 Ok(())
232}
233
234#[must_use]
236#[cfg(any(test, feature = "cpu-parity"))]
237pub fn majority_bundle_cpu(hvs: &[Vec<u32>]) -> Vec<u32> {
238 let mut out = Vec::new();
239 match try_majority_bundle_cpu_into(hvs, &mut out) {
240 Ok(()) => out,
241 Err(error) => {
245 panic!("vyre-primitives hypervector majority bundle CPU reference failed: {error}")
246 }
247 }
248}
249
250#[cfg(any(test, feature = "cpu-parity"))]
252pub fn majority_bundle_cpu_into(hvs: &[Vec<u32>], out: &mut Vec<u32>) {
253 if let Err(error) = try_majority_bundle_cpu_into(hvs, out) {
254 panic!("vyre-primitives hypervector majority bundle CPU reference failed: {error}");
255 }
256}
257
258#[cfg(any(test, feature = "cpu-parity"))]
260pub fn try_majority_bundle_cpu_into(hvs: &[Vec<u32>], out: &mut Vec<u32>) -> Result<(), String> {
261 let Some(dim_words) = hvs.iter().map(Vec::len).min() else {
262 out.clear();
263 return Ok(());
264 };
265 if dim_words == 0 {
266 out.clear();
267 return Ok(());
268 }
269 let k = hvs.len();
270 let threshold = k / 2;
271
272 vyre_foundation::allocation::reserve_exact_cleared(out, dim_words).map_err(|err| {
273 format!("hypervector majority bundle could not reserve {dim_words} output words: {err}")
274 })?;
275 out.resize(dim_words, 0);
276 for w in 0..dim_words {
277 for bit in 0..32 {
278 let mut count = 0;
279 for hv in hvs {
280 count += (hv[w] >> bit) & 1;
281 }
282 if count as usize > threshold {
283 out[w] |= 1 << bit;
284 }
285 }
286 }
287 Ok(())
288}
289
290#[must_use]
294pub fn hamming_similarity(a: &[u32], b: &[u32]) -> f32 {
295 let dim_words = a.len().min(b.len());
296 if dim_words == 0 {
297 return 1.0;
298 }
299 let dim_bits = (dim_words * 32) as f32;
300 let hamming: u32 = a
301 .iter()
302 .zip(b.iter())
303 .take(dim_words)
304 .map(|(&x, &y)| (x ^ y).count_ones())
305 .sum();
306 1.0 - 2.0 * (hamming as f32) / dim_bits
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn xor_bind_self_cancels() {
315 let a = vec![0xDEAD_BEEFu32, 0x0BAD_F00D];
317 let b = vec![0x1234_5678, 0x90AB_CDEF];
318 let bound = xor_bind_cpu(&a, &b);
319 let unbound = xor_bind_cpu(&bound, &b);
320 assert_eq!(unbound, a);
321 }
322
323 #[test]
324 fn xor_bind_zero_is_identity() {
325 let a = vec![0x1234, 0x5678, 0xABCD];
326 let zero = vec![0u32; a.len()];
327 assert_eq!(xor_bind_cpu(&a, &zero), vec![0x1234, 0x5678, 0xABCD]);
328 }
329
330 #[test]
331 fn xor_bind_cpu_into_reuses_output() {
332 let a = vec![0x1234, 0x5678, 0xABCD];
333 let b = vec![0xFFFF, 0x0000, 0x1111];
334 let mut out = Vec::with_capacity(8);
335 let ptr = out.as_ptr();
336 xor_bind_cpu_into(&a, &b, &mut out);
337 assert_eq!(out, vec![0xEDCB, 0x5678, 0xBADC]);
338 assert_eq!(out.as_ptr(), ptr);
339 }
340
341 #[test]
342 fn try_xor_bind_cpu_into_clears_stale_tail_without_reallocating() {
343 let a = vec![0x1234, 0x5678, 0xABCD];
344 let b = vec![0xFFFF];
345 let mut out = Vec::with_capacity(8);
346 out.extend_from_slice(&[u32::MAX; 8]);
347 let ptr = out.as_ptr();
348
349 try_xor_bind_cpu_into(&a, &b, &mut out).unwrap();
350
351 assert_eq!(out, vec![0xEDCB]);
352 assert_eq!(out.as_ptr(), ptr);
353 }
354
355 #[test]
356 fn xor_bind_wrappers_match_fallible_reference() {
357 let a = vec![0x1234, 0x5678, 0xABCD];
358 let b = vec![0xFFFF, 0, 0x1111];
359 let mut compat = Vec::with_capacity(8);
360 let mut fallible = Vec::with_capacity(8);
361
362 xor_bind_cpu_into(&a, &b, &mut compat);
363 try_xor_bind_cpu_into(&a, &b, &mut fallible)
364 .expect("Fix: small hypervector XOR bind CPU reference must reserve");
365
366 assert_eq!(xor_bind_cpu(&a, &b), fallible);
367 assert_eq!(compat, fallible);
368 }
369
370 #[test]
371 fn xor_bind_cpu_truncates_mismatched_inputs() {
372 let a = vec![0x1234, 0x5678, 0xABCD];
373 let b = vec![0xFFFF];
374 assert_eq!(xor_bind_cpu(&a, &b), vec![0xEDCB]);
375 }
376
377 #[test]
378 fn majority_bundle_three_vectors() {
379 let hvs = vec![vec![0b001], vec![0b001], vec![0b010]];
383 let out = majority_bundle_cpu(&hvs);
384 assert_eq!(out, vec![0b001]);
385 }
386
387 #[test]
388 fn majority_bundle_unanimous() {
389 let hvs = vec![vec![0xFF], vec![0xFF], vec![0xFF]];
390 let out = majority_bundle_cpu(&hvs);
391 assert_eq!(out, vec![0xFF]);
392 }
393
394 #[test]
395 fn majority_bundle_cpu_into_reuses_output() {
396 let hvs = vec![vec![0b001], vec![0b001], vec![0b010]];
397 let mut out = Vec::with_capacity(8);
398 let ptr = out.as_ptr();
399 majority_bundle_cpu_into(&hvs, &mut out);
400 assert_eq!(out, vec![0b001]);
401 assert_eq!(out.as_ptr(), ptr);
402 }
403
404 #[test]
405 fn try_majority_bundle_cpu_into_clears_stale_tail_without_reallocating() {
406 let hvs = vec![vec![0b001], vec![0b001], vec![0b010]];
407 let mut out = Vec::with_capacity(8);
408 out.extend_from_slice(&[u32::MAX; 8]);
409 let ptr = out.as_ptr();
410
411 try_majority_bundle_cpu_into(&hvs, &mut out).unwrap();
412
413 assert_eq!(out, vec![0b001]);
414 assert_eq!(out.as_ptr(), ptr);
415 }
416
417 #[test]
418 fn majority_bundle_wrappers_match_fallible_reference() {
419 let hvs = vec![vec![0b001], vec![0b001], vec![0b010]];
420 let mut compat = Vec::with_capacity(8);
421 let mut fallible = Vec::with_capacity(8);
422
423 majority_bundle_cpu_into(&hvs, &mut compat);
424 try_majority_bundle_cpu_into(&hvs, &mut fallible)
425 .expect("Fix: small hypervector majority bundle CPU reference must reserve");
426
427 assert_eq!(majority_bundle_cpu(&hvs), fallible);
428 assert_eq!(compat, fallible);
429 }
430
431 #[test]
432 fn majority_bundle_tie_rounds_to_zero() {
433 let hvs = vec![vec![0b1], vec![0b0]];
435 let out = majority_bundle_cpu(&hvs);
436 assert_eq!(out, vec![0b0]);
437 }
438
439 #[test]
440 fn majority_bundle_cpu_handles_empty_and_mismatched_inputs() {
441 let empty: Vec<Vec<u32>> = Vec::new();
442 assert!(majority_bundle_cpu(&empty).is_empty());
443
444 let hvs = vec![vec![0b001, 0b111], vec![0b001]];
445 assert_eq!(majority_bundle_cpu(&hvs), vec![0b001]);
446 }
447
448 #[test]
449 fn hamming_similarity_self_is_one() {
450 let a = vec![0xDEAD_BEEFu32; 8];
451 assert!((hamming_similarity(&a, &a) - 1.0).abs() < 1e-6);
452 }
453
454 #[test]
455 fn hamming_similarity_complement_is_minus_one() {
456 let a = vec![0xFFFF_FFFFu32; 4];
457 let b = vec![0x0000_0000u32; 4];
458 assert!((hamming_similarity(&a, &b) - (-1.0)).abs() < 1e-6);
459 }
460
461 #[test]
462 fn hamming_similarity_handles_empty_and_mismatched_inputs() {
463 assert_eq!(hamming_similarity(&[], &[]), 1.0);
464 let a = vec![0xFFFF_FFFFu32, 0];
465 let b = vec![0];
466 assert!((hamming_similarity(&a, &b) - (-1.0)).abs() < 1e-6);
467 }
468
469 #[test]
470 fn ir_program_xor_bind_buffer_layout() {
471 let p = hypervector_xor_bind("a", "b", "out", 64);
472 assert_eq!(p.workgroup_size, [256, 1, 1]);
473 let names: Vec<&str> = p.buffers.iter().map(|b| b.name()).collect();
474 assert_eq!(names, vec!["a", "b", "out"]);
475 for buf in p.buffers.iter() {
476 assert_eq!(buf.count(), 64);
477 }
478 }
479
480 #[test]
481 fn ir_program_xor_bind_zero_dim_is_trap() {
482 let p = hypervector_xor_bind("a", "b", "out", 0);
483 assert_eq!(p.buffers.len(), 1);
484 assert_eq!(p.buffers[0].name(), "out");
485 }
486
487 #[test]
488 fn ir_program_bundle_buffer_layout() {
489 let p = hypervector_majority_bundle("stack", "out", 8, 5);
490 assert_eq!(p.buffers[0].count(), 5 * 8);
491 assert_eq!(p.buffers[1].count(), 8);
492 }
493
494 #[test]
495 fn bundle_overflow_lowers_to_trap_not_host_panic() {
496 let p = hypervector_majority_bundle("stack", "out", u32::MAX, 2);
497 assert!(p.stats().trap());
498 assert_eq!(p.buffers[0].name(), "out");
499 }
500
501 #[test]
502 fn standard_dim_constants() {
503 assert_eq!(STANDARD_DIM_BITS, STANDARD_DIM_WORDS * 32);
504 const _: () = assert!(STANDARD_DIM_BITS >= 8192);
505 }
506}