Skip to content

Commit e658a67

Browse files
committed
feat(fingerprint): full BindSpace API — get/set/toggle_bit, bind, and, not, permute, random, from_content, density
Adds all methods needed by ladybug-rs cognitive modules: - Bit manipulation: get_bit, set_bit, toggle_bit - VSA ops: bind (XOR), and, not, permute (circular shift) - Construction: random (xorshift128+), from_content (hash expansion) - Query: density (set bits / total), hamming alias, as_raw These enable lance-graph-cognitive grammar+world modules to compile clean against ndarray Fingerprint<256> without ladybug-rs core types. https://claude.ai/code/session_01SbYsmmbPf9YQuYbHZN52Zh
1 parent 9b00646 commit e658a67

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

src/hpc/fingerprint.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,129 @@ impl<const N: usize> Fingerprint<N> {
8484
out
8585
}
8686

87+
/// Get a specific bit (0-indexed).
88+
#[inline]
89+
pub fn get_bit(&self, index: usize) -> bool {
90+
debug_assert!(index < Self::BITS);
91+
let word_idx = index / 64;
92+
let bit_idx = index % 64;
93+
(self.words[word_idx] >> bit_idx) & 1 == 1
94+
}
95+
96+
/// Set a specific bit.
97+
#[inline]
98+
pub fn set_bit(&mut self, index: usize, value: bool) {
99+
debug_assert!(index < Self::BITS);
100+
let word_idx = index / 64;
101+
let bit_idx = index % 64;
102+
if value {
103+
self.words[word_idx] |= 1u64 << bit_idx;
104+
} else {
105+
self.words[word_idx] &= !(1u64 << bit_idx);
106+
}
107+
}
108+
109+
/// Toggle a specific bit.
110+
#[inline]
111+
pub fn toggle_bit(&mut self, index: usize) {
112+
debug_assert!(index < Self::BITS);
113+
let word_idx = index / 64;
114+
let bit_idx = index % 64;
115+
self.words[word_idx] ^= 1u64 << bit_idx;
116+
}
117+
118+
/// Create a random fingerprint from a seed (xorshift128+).
119+
pub fn random(seed: u64) -> Self {
120+
let mut s0 = seed;
121+
let mut s1 = seed.wrapping_mul(0x9E3779B97F4A7C15);
122+
let mut words = [0u64; N];
123+
for word in &mut words {
124+
let mut s = s0;
125+
s0 = s1;
126+
s ^= s << 23;
127+
s ^= s >> 18;
128+
s ^= s1;
129+
s ^= s1 >> 5;
130+
s1 = s;
131+
*word = s0.wrapping_add(s1);
132+
}
133+
Self { words }
134+
}
135+
87136
/// Hamming distance (number of differing bits).
88137
/// Delegates to ndarray's SIMD dispatch (AVX-512 → AVX2 → scalar).
89138
#[inline]
90139
pub fn hamming_distance(&self, other: &Self) -> u32 {
91140
super::bitwise::hamming_distance_raw(self.as_bytes(), other.as_bytes()) as u32
92141
}
93142

143+
/// Alias for `hamming_distance` (ladybug-rs compat).
144+
#[inline]
145+
pub fn hamming(&self, other: &Self) -> u32 {
146+
self.hamming_distance(other)
147+
}
148+
149+
/// XOR bind (ladybug-rs compat). Returns a new fingerprint.
150+
#[inline]
151+
pub fn bind(&self, other: &Self) -> Self {
152+
let mut words = [0u64; N];
153+
for i in 0..N { words[i] = self.words[i] ^ other.words[i]; }
154+
Self { words }
155+
}
156+
157+
/// AND (bitwise intersection).
158+
#[inline]
159+
pub fn and(&self, other: &Self) -> Self {
160+
let mut words = [0u64; N];
161+
for i in 0..N { words[i] = self.words[i] & other.words[i]; }
162+
Self { words }
163+
}
164+
165+
/// Bitwise NOT.
166+
#[inline]
167+
pub fn not(&self) -> Self {
168+
let mut words = [0u64; N];
169+
for i in 0..N { words[i] = !self.words[i]; }
170+
Self { words }
171+
}
172+
173+
/// Density: fraction of set bits (popcount / total bits).
174+
#[inline]
175+
pub fn density(&self) -> f32 {
176+
self.popcount() as f32 / Self::BITS as f32
177+
}
178+
179+
/// Access raw words as slice.
180+
#[inline]
181+
pub fn as_raw(&self) -> &[u64; N] {
182+
&self.words
183+
}
184+
185+
/// Create from content string (SHA-256-like hash expansion).
186+
pub fn from_content(data: &str) -> Self {
187+
let mut h = 0x736f6d6570736575u64;
188+
for (i, b) in data.bytes().enumerate() {
189+
h ^= (b as u64) << ((i % 8) * 8);
190+
h = h.rotate_left(13).wrapping_mul(5).wrapping_add(0xe6546b64);
191+
}
192+
Self::random(h)
193+
}
194+
195+
/// Permute: circular bit shift by `positions` (positive = left).
196+
pub fn permute(&self, positions: i32) -> Self {
197+
let total = Self::BITS as i32;
198+
let shift = ((positions % total) + total) % total;
199+
if shift == 0 { return self.clone(); }
200+
let mut result = Self::zero();
201+
for i in 0..Self::BITS {
202+
if self.get_bit(i) {
203+
let new_pos = ((i as i32 + shift) % total) as usize;
204+
result.set_bit(new_pos, true);
205+
}
206+
}
207+
result
208+
}
209+
94210
/// Hamming weight (number of set bits).
95211
#[inline]
96212
pub fn popcount(&self) -> u32 {

0 commit comments

Comments
 (0)