@@ -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