77//!
88//! Address width for named fingerprints is fixed at 256 × u64 = 16,384 bits
99//! = `ndarray::hpc::fingerprint::Fingerprint<256>`.
10+ //! The `cycle` column uses `Vsa16kF32` carrier (16,384 × f32 = 64 KB per row)
11+ //! for algebraic operations; other planes remain `u64 × 256`.
1012
1113use lance_graph_contract:: cognitive_shader:: { ColumnWindow , MetaFilter , MetaWord } ;
1214
1315pub const WORDS_PER_FP : usize = 256 ;
1416pub const WIDTH_BITS : usize = WORDS_PER_FP * 64 ;
1517pub const QUALIA_DIMS : usize = 18 ;
18+ pub const FLOATS_PER_VSA : usize = 16_384 ; // Vsa16kF32 carrier width
1619
1720/// Named fingerprint planes (content / cycle / topic / angle).
18- /// Flat `Box<[u64]>` of length `len * 256`. Each row starts at
21+ /// Flat `Box<[u64]>` of length `len * 256` for content/topic/angle . Each row starts at
1922/// `row * 256` words and spans 256 consecutive u64.
23+ /// The `cycle` plane uses `Vsa16kF32` carrier: `Box<[f32]>` of length `len * 16_384`.
2024///
2125/// Why not `[[u64; 256]]`? Because row-major Box<[u64]> gives us O(1)
2226/// `chunks_exact(256)` iteration which LLVM autovectorises cleanly.
2327#[ derive( Debug ) ]
2428pub struct FingerprintColumns {
2529 pub content : Box < [ u64 ] > ,
26- pub cycle : Box < [ u64 ] > ,
30+ pub cycle : Box < [ f32 ] > , // was Box<[ u64]>, now Vsa16kF32 carrier (16_384 f32 per row)
2731 pub topic : Box < [ u64 ] > ,
2832 pub angle : Box < [ u64 ] > ,
2933}
3034
3135impl FingerprintColumns {
3236 pub fn zeros ( len : usize ) -> Self {
3337 let mk = || vec ! [ 0u64 ; len * WORDS_PER_FP ] . into_boxed_slice ( ) ;
34- Self { content : mk ( ) , cycle : mk ( ) , topic : mk ( ) , angle : mk ( ) }
38+ Self {
39+ content : mk ( ) ,
40+ cycle : vec ! [ 0.0f32 ; len * FLOATS_PER_VSA ] . into_boxed_slice ( ) ,
41+ topic : mk ( ) ,
42+ angle : mk ( ) ,
43+ }
3544 }
3645
3746 /// Zero-copy view of a row's content fingerprint words (len = 256).
@@ -41,8 +50,8 @@ impl FingerprintColumns {
4150 }
4251
4352 #[ inline]
44- pub fn cycle_row ( & self , row : usize ) -> & [ u64 ] {
45- & self . cycle [ row * WORDS_PER_FP ..( row + 1 ) * WORDS_PER_FP ]
53+ pub fn cycle_row ( & self , row : usize ) -> & [ f32 ] {
54+ & self . cycle [ row * FLOATS_PER_VSA ..( row + 1 ) * FLOATS_PER_VSA ]
4655 }
4756
4857 #[ inline]
@@ -62,10 +71,19 @@ impl FingerprintColumns {
6271 . copy_from_slice ( words) ;
6372 }
6473
65- pub fn set_cycle ( & mut self , row : usize , words : & [ u64 ] ) {
66- assert_eq ! ( words. len( ) , WORDS_PER_FP ) ;
67- self . cycle [ row * WORDS_PER_FP ..( row + 1 ) * WORDS_PER_FP ]
68- . copy_from_slice ( words) ;
74+ pub fn set_cycle ( & mut self , row : usize , vsa : & [ f32 ] ) {
75+ assert_eq ! ( vsa. len( ) , FLOATS_PER_VSA ) ;
76+ self . cycle [ row * FLOATS_PER_VSA ..( row + 1 ) * FLOATS_PER_VSA ]
77+ . copy_from_slice ( vsa) ;
78+ }
79+
80+ /// Write a cycle fingerprint from Binary16K (u64×256) by projecting to Vsa16kF32 bipolar.
81+ /// This is the adapter for upstream producers (ShaderBus) that still emit u64.
82+ pub fn set_cycle_from_bits ( & mut self , row : usize , bits : & [ u64 ; WORDS_PER_FP ] ) {
83+ use lance_graph_contract:: crystal:: binary16k_to_vsa16k_bipolar;
84+ let vsa = binary16k_to_vsa16k_bipolar ( bits) ;
85+ self . cycle [ row * FLOATS_PER_VSA ..( row + 1 ) * FLOATS_PER_VSA ]
86+ . copy_from_slice ( & * vsa) ;
6987 }
7088}
7189
@@ -144,13 +162,14 @@ impl BindSpace {
144162
145163 /// Total byte footprint (sum across all columns).
146164 pub fn byte_footprint ( & self ) -> usize {
147- let fp_bytes = 4 * self . len * WORDS_PER_FP * 8 ; // 4 planes × len × 256 × 8
165+ let content_topic_angle = 3 * self . len * WORDS_PER_FP * 8 ;
166+ let cycle_bytes = self . len * FLOATS_PER_VSA * 4 ; // f32 carrier
148167 let edge_bytes = self . len * 8 ;
149168 let qualia_bytes = self . len * QUALIA_DIMS * 4 ;
150169 let meta_bytes = self . len * 4 ;
151170 let temporal_bytes = self . len * 8 ;
152171 let expert_bytes = self . len * 2 ;
153- fp_bytes + edge_bytes + qualia_bytes + meta_bytes + temporal_bytes + expert_bytes
172+ content_topic_angle + cycle_bytes + edge_bytes + qualia_bytes + meta_bytes + temporal_bytes + expert_bytes
154173 }
155174
156175 /// Apply MetaFilter across a row window. Returns a dense Vec of row
@@ -173,7 +192,7 @@ impl BindSpace {
173192 /// Layer 4 (cognitive_stack) persists its per-cycle signature into
174193 /// BindSpace so future cycles can retrieve/replay it.
175194 pub fn write_cycle_fingerprint ( & mut self , row : usize , cycle_fp : & [ u64 ; WORDS_PER_FP ] ) {
176- self . fingerprints . set_cycle ( row, cycle_fp) ;
195+ self . fingerprints . set_cycle_from_bits ( row, cycle_fp) ;
177196 }
178197}
179198
@@ -227,16 +246,17 @@ mod tests {
227246 let bs = BindSpace :: zeros ( 10 ) ;
228247 assert_eq ! ( bs. len, 10 ) ;
229248 assert_eq ! ( bs. fingerprints. content. len( ) , 10 * WORDS_PER_FP ) ;
249+ assert_eq ! ( bs. fingerprints. cycle. len( ) , 10 * FLOATS_PER_VSA ) ;
230250 assert_eq ! ( bs. qualia. 0 . len( ) , 10 * QUALIA_DIMS ) ;
231251 assert_eq ! ( bs. meta. 0 . len( ) , 10 ) ;
232252 }
233253
234254 #[ test]
235255 fn bindspace_footprint_adds_columns ( ) {
236256 let bs = BindSpace :: zeros ( 1 ) ;
237- // 4 × 2048 (fp ) + 8 (edge) + 72 (qualia 18×4) + 4 (meta) + 8 (temporal) + 2 (expert)
238- // = 8192 + 8 + 72 + 4 + 8 + 2 = 8286
239- assert_eq ! ( bs. byte_footprint( ) , 8286 ) ;
257+ // 3 × 2048 (content/topic/angle) + 65536 (cycle f32 ) + 8 (edge) + 72 (qualia 18×4) + 4 (meta) + 8 (temporal) + 2 (expert)
258+ // = 6144 + 65536 + 8 + 72 + 4 + 8 + 2 = 71774
259+ assert_eq ! ( bs. byte_footprint( ) , 71774 ) ;
240260 }
241261
242262 #[ test]
@@ -268,9 +288,25 @@ mod tests {
268288 fn write_cycle_fingerprint_persists ( ) {
269289 let mut bs = BindSpace :: zeros ( 2 ) ;
270290 let mut fp = [ 0u64 ; WORDS_PER_FP ] ;
271- fp[ 42 ] = 0xDEADBEEF ;
291+ fp[ 0 ] = 1 ; // bit 0 set
272292 bs. write_cycle_fingerprint ( 1 , & fp) ;
273- assert_eq ! ( bs. fingerprints. cycle_row( 1 ) [ 42 ] , 0xDEADBEEF ) ;
274- assert_eq ! ( bs. fingerprints. cycle_row( 0 ) [ 42 ] , 0 ) ;
293+ // After bipolar projection: bit 0 set → dim 0 = +1.0, bit 1 unset → dim 1 = -1.0
294+ let row = bs. fingerprints . cycle_row ( 1 ) ;
295+ assert_eq ! ( row[ 0 ] , 1.0 ) ; // bit 0 was set
296+ assert_eq ! ( row[ 1 ] , -1.0 ) ; // bit 1 was not set
297+ // Row 0 should still be all zeros (not projected)
298+ assert ! ( bs. fingerprints. cycle_row( 0 ) . iter( ) . all( |& v| v == 0.0 ) ) ;
299+ }
300+
301+ #[ test]
302+ fn set_cycle_direct_f32 ( ) {
303+ let mut bs = BindSpace :: zeros ( 2 ) ;
304+ let mut vsa = vec ! [ 0.0f32 ; FLOATS_PER_VSA ] ;
305+ vsa[ 42 ] = 0.75 ;
306+ vsa[ 16383 ] = -0.5 ;
307+ bs. fingerprints . set_cycle ( 0 , & vsa) ;
308+ assert_eq ! ( bs. fingerprints. cycle_row( 0 ) [ 42 ] , 0.75 ) ;
309+ assert_eq ! ( bs. fingerprints. cycle_row( 0 ) [ 16383 ] , -0.5 ) ;
310+ assert ! ( bs. fingerprints. cycle_row( 1 ) . iter( ) . all( |& v| v == 0.0 ) ) ;
275311 }
276312}
0 commit comments