Skip to main content

qualia_core_db/tensor/
resident_substrate.rs

1//! Fixed-capacity resident graph–tensor SOA for U1 background search (Track B3.3).
2//!
3//! Cold path may parse heap buffers; hot path (`tensor_search_into`) uses caller stack only.
4
5use core::cell::UnsafeCell;
6use std::sync::atomic::{AtomicU32, Ordering};
7use std::sync::OnceLock;
8
9use super::buffer_export::{parse_header, read_tensor_at};
10use super::q42_integration::{
11    Q42TensorView, TensorMetadata, TensorVolumeConfig, TensorVolumeError,
12};
13use super::Tensor10D;
14use crate::NQuin;
15
16/// Maximum nodes pinned in U1 ledger (structural cap, not heap growth).
17pub const MAX_RESIDENT_NODES: usize = 4096;
18pub const MAX_KNN_HITS: usize = 32;
19
20/// Process-wide resident substrate (U1 pin). Writer: cold load; reader: U1 producer.
21pub struct ResidentTensorSubstrate {
22    nquins: UnsafeCell<[NQuin; MAX_RESIDENT_NODES]>,
23    metadata: UnsafeCell<[TensorMetadata; MAX_RESIDENT_NODES]>,
24    subject_hashes: UnsafeCell<[u64; MAX_RESIDENT_NODES]>,
25    node_count: AtomicU32,
26    load_generation: AtomicU32,
27}
28
29// SAFETY: Writers synchronize via `load_generation` bump + Release; readers snapshot count.
30unsafe impl Sync for ResidentTensorSubstrate {}
31
32fn empty_nquin() -> NQuin {
33    NQuin {
34        subject: 0,
35        predicate: 0,
36        object: 0,
37        context: 0,
38        metadata: 0,
39        parity: 0,
40    }
41}
42
43impl ResidentTensorSubstrate {
44    pub fn new() -> Self {
45        Self {
46            nquins: UnsafeCell::new([empty_nquin(); MAX_RESIDENT_NODES]),
47            metadata: UnsafeCell::new([TensorMetadata::default(); MAX_RESIDENT_NODES]),
48            subject_hashes: UnsafeCell::new([0u64; MAX_RESIDENT_NODES]),
49            node_count: AtomicU32::new(0),
50            load_generation: AtomicU32::new(0),
51        }
52    }
53
54    #[inline]
55    pub fn node_count(&self) -> u32 {
56        self.node_count.load(Ordering::Acquire)
57    }
58
59    #[inline]
60    pub fn load_generation(&self) -> u32 {
61        self.load_generation.load(Ordering::Acquire)
62    }
63
64    #[inline]
65    pub fn subject_hash_at(&self, index: u32) -> u64 {
66        let count = self.node_count();
67        if index >= count {
68            return 0;
69        }
70        unsafe { (*self.subject_hashes.get())[index as usize] }
71    }
72
73    #[inline]
74    pub fn tensor_at(&self, index: u32) -> Option<Tensor10D> {
75        let count = self.node_count();
76        if index >= count {
77            return None;
78        }
79        let meta = unsafe { (*self.metadata.get())[index as usize] };
80        if meta.has_tensor {
81            Some(meta.tensor)
82        } else {
83            None
84        }
85    }
86
87    /// Cold-path load from exported tensor buffer (spatial encode, daemon slice).
88    pub fn load_from_tensor_buffer(
89        &self,
90        bytes: &[u8],
91        default_subject_hash: u64,
92    ) -> Result<u32, &'static str> {
93        let (header, _header_len) = parse_header(bytes)?;
94        let count = header.node_count as usize;
95        if count > MAX_RESIDENT_NODES {
96            return Err("resident substrate capacity exceeded");
97        }
98
99        let nquins = unsafe { &mut *self.nquins.get() };
100        let metadata = unsafe { &mut *self.metadata.get() };
101        let subject_hashes = unsafe { &mut *self.subject_hashes.get() };
102
103        for i in 0..count {
104            let tensor = read_tensor_at(bytes, i)?;
105            let subject = if default_subject_hash != 0 {
106                default_subject_hash ^ (i as u64)
107            } else {
108                crate::q_hash(&format!("tensor:node:{i}"))
109            };
110            nquins[i] = stub_nquin_for_tensor(i, subject, &tensor);
111            metadata[i] = TensorMetadata::from_nquin_and_tensor(&nquins[i], tensor);
112            subject_hashes[i] = subject;
113        }
114
115        self.node_count.store(count as u32, Ordering::Release);
116        self.load_generation.fetch_add(1, Ordering::AcqRel);
117        Ok(count as u32)
118    }
119
120    /// Zero-heap kNN into caller buffer (U1 producer / SIMD path).
121    pub fn tensor_search_into(
122        &self,
123        query: &Tensor10D,
124        max_distance: f32,
125        out: &mut [usize],
126    ) -> Result<usize, TensorVolumeError> {
127        let count = self.node_count() as usize;
128        if count == 0 {
129            return Ok(0);
130        }
131        let nquins =
132            unsafe { std::slice::from_raw_parts(self.nquins.get() as *const NQuin, count) };
133        let meta = unsafe {
134            std::slice::from_raw_parts(self.metadata.get() as *const TensorMetadata, count)
135        };
136        let config = TensorVolumeConfig::default();
137        let view = Q42TensorView::new(nquins, meta, &config)?;
138        view.tensor_search_into(query, max_distance, out)
139    }
140
141    /// Load tensors directly (native encode path without full buffer round-trip).
142    pub fn load_from_tensors(
143        &self,
144        tensors: &[Tensor10D],
145        subject_hash: u64,
146    ) -> Result<u32, &'static str> {
147        if tensors.len() > MAX_RESIDENT_NODES {
148            return Err("resident substrate capacity exceeded");
149        }
150        let nquins = unsafe { &mut *self.nquins.get() };
151        let metadata = unsafe { &mut *self.metadata.get() };
152        let subject_hashes = unsafe { &mut *self.subject_hashes.get() };
153
154        for (i, tensor) in tensors.iter().enumerate() {
155            let subject = if subject_hash != 0 {
156                subject_hash ^ (i as u64)
157            } else {
158                crate::q_hash(&format!("tensor:node:{i}"))
159            };
160            nquins[i] = stub_nquin_for_tensor(i, subject, tensor);
161            metadata[i] = TensorMetadata::from_nquin_and_tensor(&nquins[i], *tensor);
162            subject_hashes[i] = subject;
163        }
164
165        self.node_count
166            .store(tensors.len() as u32, Ordering::Release);
167        self.load_generation.fetch_add(1, Ordering::AcqRel);
168        Ok(tensors.len() as u32)
169    }
170}
171
172#[inline]
173fn stub_nquin_for_tensor(index: usize, subject: u64, tensor: &Tensor10D) -> NQuin {
174    let object = ((tensor.x.to_bits() as u64) << 20)
175        ^ (tensor.y.to_bits() as u64)
176        ^ (tensor.z.to_bits() as u64);
177    NQuin {
178        subject,
179        predicate: 0,
180        object,
181        context: 0,
182        metadata: index as u64,
183        parity: subject ^ object,
184    }
185}
186
187static RESIDENT_SUBSTRATE: OnceLock<Box<ResidentTensorSubstrate>> = OnceLock::new();
188
189#[inline]
190pub fn global_resident_substrate() -> &'static ResidentTensorSubstrate {
191    RESIDENT_SUBSTRATE.get_or_init(|| Box::new(ResidentTensorSubstrate::new()))
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use crate::tensor::buffer_export::{write_tensor_buffer, TensorBufferHeader};
198
199    #[test]
200    fn load_and_search_resident_substrate() {
201        let sub = ResidentTensorSubstrate::new();
202        let tensors = [
203            Tensor10D::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
204            Tensor10D::new(0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
205        ];
206        let need = TensorBufferHeader::total_bytes(2);
207        let mut buf = vec![0u8; need];
208        write_tensor_buffer(&tensors, &mut buf).unwrap();
209        assert_eq!(sub.load_from_tensor_buffer(&buf, 42).unwrap(), 2);
210
211        let query = Tensor10D::new(0.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
212        let mut hits = [0usize; MAX_KNN_HITS];
213        let n = sub.tensor_search_into(&query, 0.5, &mut hits).unwrap();
214        assert!(n >= 1);
215        assert_eq!(hits[0], 0);
216    }
217}