Skip to content

Commit 117a76a

Browse files
committed
feat: Column H — EntityTypeId on BindSpace (Phase 1, D-H1..H4)
Foundry Vertex "Object Type" equivalent. Per-row entity type binding in the BindSpace SoA, enabling type-filtered queries without schema re-parsing. D-H1: `EntityTypeId = u16` + `entity_type_id(ontology, name) -> u16` in contract::ontology. 1-based index into Ontology.schemas. 0 = untyped. D-H2: `entity_type: Box<[u16]>` field on BindSpace SoA. +2 bytes/row (71774 → 71776 footprint for 1 row). D-H3: `BindSpaceBuilder::push_typed()` writes entity_type per row. `push()` defaults to 0 (untyped) for backward compat. D-H4: 4 tests (entity_type defaults to 0, set/get, builder push_typed, contract entity_type_id 1-based lookup). All pass. Phase 1 complete per bindspace-columns-v1.md §5. Unblocks: LF-22 ObjectView usage, LF-40 type-filtered search. https://claude.ai/code/session_01SbYsmmbPf9YQuYbHZN52Zh
1 parent b3df078 commit 117a76a

2 files changed

Lines changed: 95 additions & 4 deletions

File tree

crates/cognitive-shader-driver/src/bindspace.rs

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ pub struct BindSpace {
144144
pub meta: MetaColumn,
145145
pub temporal: Box<[u64]>,
146146
pub expert: Box<[u16]>,
147+
/// Column H: per-row entity type binding (Foundry Object Type equivalent).
148+
/// 0 = untyped. Non-zero = 1-based index into `Ontology.schemas`.
149+
pub entity_type: Box<[u16]>,
147150
}
148151

149152
impl BindSpace {
@@ -157,6 +160,7 @@ impl BindSpace {
157160
meta: MetaColumn::zeros(len),
158161
temporal: vec![0u64; len].into_boxed_slice(),
159162
expert: vec![0u16; len].into_boxed_slice(),
163+
entity_type: vec![0u16; len].into_boxed_slice(),
160164
}
161165
}
162166

@@ -169,7 +173,8 @@ impl BindSpace {
169173
let meta_bytes = self.len * 4;
170174
let temporal_bytes = self.len * 8;
171175
let expert_bytes = self.len * 2;
172-
content_topic_angle + cycle_bytes + edge_bytes + qualia_bytes + meta_bytes + temporal_bytes + expert_bytes
176+
let entity_type_bytes = self.len * 2;
177+
content_topic_angle + cycle_bytes + edge_bytes + qualia_bytes + meta_bytes + temporal_bytes + expert_bytes + entity_type_bytes
173178
}
174179

175180
/// Apply MetaFilter across a row window. Returns a dense Vec of row
@@ -220,6 +225,20 @@ impl BindSpaceBuilder {
220225
qualia: &[f32; QUALIA_DIMS],
221226
temporal: u64,
222227
expert: u16,
228+
) -> Self {
229+
self.push_typed(content, meta, edge, qualia, temporal, expert, 0)
230+
}
231+
232+
/// Push a row with explicit entity type (Column H).
233+
pub fn push_typed(
234+
mut self,
235+
content: &[u64],
236+
meta: MetaWord,
237+
edge: u64,
238+
qualia: &[f32; QUALIA_DIMS],
239+
temporal: u64,
240+
expert: u16,
241+
entity_type: u16,
223242
) -> Self {
224243
let row = self.cursor;
225244
self.bs.fingerprints.set_content(row, content);
@@ -228,6 +247,7 @@ impl BindSpaceBuilder {
228247
self.bs.qualia.set(row, qualia);
229248
self.bs.temporal[row] = temporal;
230249
self.bs.expert[row] = expert;
250+
self.bs.entity_type[row] = entity_type;
231251
self.cursor += 1;
232252
self
233253
}
@@ -254,9 +274,9 @@ mod tests {
254274
#[test]
255275
fn bindspace_footprint_adds_columns() {
256276
let bs = BindSpace::zeros(1);
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);
277+
// 3 × 2048 (content/topic/angle) + 65536 (cycle f32) + 8 (edge) + 72 (qualia 18×4) + 4 (meta) + 8 (temporal) + 2 (expert) + 2 (entity_type)
278+
// = 6144 + 65536 + 8 + 72 + 4 + 8 + 2 + 2 = 71776
279+
assert_eq!(bs.byte_footprint(), 71776);
260280
}
261281

262282
#[test]
@@ -298,6 +318,37 @@ mod tests {
298318
assert!(bs.fingerprints.cycle_row(0).iter().all(|&v| v == 0.0));
299319
}
300320

321+
#[test]
322+
fn entity_type_defaults_to_untyped() {
323+
let bs = BindSpace::zeros(4);
324+
for row in 0..4 {
325+
assert_eq!(bs.entity_type[row], 0, "default should be untyped (0)");
326+
}
327+
}
328+
329+
#[test]
330+
fn entity_type_set_and_get() {
331+
let mut bs = BindSpace::zeros(4);
332+
bs.entity_type[1] = 42;
333+
bs.entity_type[3] = 7;
334+
assert_eq!(bs.entity_type[0], 0);
335+
assert_eq!(bs.entity_type[1], 42);
336+
assert_eq!(bs.entity_type[2], 0);
337+
assert_eq!(bs.entity_type[3], 7);
338+
}
339+
340+
#[test]
341+
fn builder_push_typed_sets_entity_type() {
342+
let qualia = [0.0f32; QUALIA_DIMS];
343+
let content = [0u64; WORDS_PER_FP];
344+
let bs = BindSpaceBuilder::new(2)
345+
.push_typed(&content, MetaWord::new(1, 0, 100, 100, 0), 0, &qualia, 0, 0, 5)
346+
.push(&content, MetaWord::new(2, 0, 200, 200, 0), 0, &qualia, 0, 0)
347+
.build();
348+
assert_eq!(bs.entity_type[0], 5, "push_typed should set entity_type");
349+
assert_eq!(bs.entity_type[1], 0, "push should default to 0");
350+
}
351+
301352
#[test]
302353
fn set_cycle_direct_f32() {
303354
let mut bs = BindSpace::zeros(2);

crates/lance-graph-contract/src/ontology.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,27 @@ use crate::property::{
2121
};
2222
use crate::cam::CodecRoute;
2323

24+
// ═══════════════════════════════════════════════════════════════════════════
25+
// EntityTypeId — Foundry Object Type equivalent (Column H in BindSpace SoA)
26+
// ═══════════════════════════════════════════════════════════════════════════
27+
28+
/// Numeric entity type identifier for per-row BindSpace typing.
29+
/// 0 = untyped. Non-zero = index into `Ontology.schemas` (1-based).
30+
///
31+
/// This is the Palantir Vertex "Object Type" equivalent. Every row in
32+
/// BindSpace can be typed, enabling Object Explorer scrolling, property
33+
/// view selection (LF-22 ObjectView), and type-filtered search (LF-40).
34+
pub type EntityTypeId = u16;
35+
36+
/// Look up the EntityTypeId for a named entity type within an Ontology.
37+
/// Returns 0 if the name doesn't match any schema.
38+
pub fn entity_type_id(ontology: &Ontology, name: &str) -> EntityTypeId {
39+
ontology.schemas.iter()
40+
.position(|s| s.name == name)
41+
.map(|idx| (idx + 1) as EntityTypeId)
42+
.unwrap_or(0)
43+
}
44+
2445
// ═══════════════════════════════════════════════════════════════════════════
2546
// Ontology — the composed object model
2647
// ═══════════════════════════════════════════════════════════════════════════
@@ -371,4 +392,23 @@ mod tests {
371392
assert!(PrefetchDepth::Detail < PrefetchDepth::Similar);
372393
assert!(PrefetchDepth::Similar < PrefetchDepth::Full);
373394
}
395+
396+
#[test]
397+
fn entity_type_id_returns_1_based_index() {
398+
use crate::property::Schema;
399+
let ont = Ontology {
400+
name: "test",
401+
schemas: vec![
402+
Schema::builder("Customer").build(),
403+
Schema::builder("Invoice").build(),
404+
Schema::builder("Product").build(),
405+
],
406+
links: Vec::new(),
407+
actions: Vec::new(),
408+
};
409+
assert_eq!(entity_type_id(&ont, "Customer"), 1);
410+
assert_eq!(entity_type_id(&ont, "Invoice"), 2);
411+
assert_eq!(entity_type_id(&ont, "Product"), 3);
412+
assert_eq!(entity_type_id(&ont, "Unknown"), 0);
413+
}
374414
}

0 commit comments

Comments
 (0)