Skip to main content

qualia_core_db/specialized_libs/computational_geometry/
features.rs

1//! Geometry/topology feature bridge for ML, retrieval, and P64 inference paths.
2
3use crate::tensor::Tensor10D;
4
5use super::connectivity::ConnectivitySummary;
6use super::csr_adjacency::{CsrHeader, CsrSummary};
7use super::{HalfEdge, INVALID_INDEX};
8
9// ---------------------------------------------------------------------------
10// Compile-time alignment / Pod assertions for GPU staging
11// ---------------------------------------------------------------------------
12
13const _: () = assert!(std::mem::size_of::<CsrHeader>() == 16);
14const _: () = assert!(std::mem::align_of::<CsrHeader>() == 4);
15const _: () = assert!(std::mem::size_of::<CsrSummary>() == 12);
16const _: () = assert!(std::mem::align_of::<CsrSummary>() == 4);
17const _: () = assert!(std::mem::size_of::<HalfEdge>() == 16);
18const _: () = assert!(std::mem::align_of::<HalfEdge>() == 4);
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum FeatureError {
22    OutputTooSmall { required: usize },
23    VertexOutOfRange { half_edge: usize, vertex: u32 },
24}
25
26/// Encode mesh-graph vertex features as 10D manifold records.
27///
28/// Convenience wrapper that calls [`encode_topology_features_10d_with_connectivity`]
29/// with `connectivity = None` (no component/genus enrichment).
30///
31/// Mapping:
32/// - `x/y/z`: geometric position
33/// - `q/w/t`: caller-provided epistemic world, domain head, and time
34/// - `v`: `3` for a boundary-clique vertex, `0` for an interior Euclidean vertex
35/// - `alpha`: degree normalized by the maximum degree in this mesh
36/// - `mu`: boundary flag (`1` or `0`)
37/// - `sigma`: raw directed half-edge degree
38///
39/// The operation is allocation-free and leaves all semantic dimensions
40/// attached to the original vertex, making the result directly usable by
41/// graph-ML, retrieval, P64 projection, and the renderer's tensor projector.
42pub fn encode_topology_features_10d(
43    positions: &[[f32; 3]],
44    half_edges: &[HalfEdge],
45    q: f32,
46    w: f32,
47    t: f32,
48    out: &mut [Tensor10D],
49) -> Result<usize, FeatureError> {
50    encode_topology_features_10d_with_connectivity(positions, half_edges, q, w, t, None, out)
51}
52
53/// Encode mesh-graph vertex features as 10D manifold records with connectivity enrichment.
54///
55/// When `connectivity` is `Some`, the component count and genus are folded into
56/// the feature vectors:
57/// - `sigma` gets `component_count * 0.001` added (sub-integer encoding)
58/// - `v` for interior vertices is set to `genus` (boundary vertices keep v=3)
59///
60/// See [`encode_topology_features_10d`] for the base mapping.
61pub fn encode_topology_features_10d_with_connectivity(
62    positions: &[[f32; 3]],
63    half_edges: &[HalfEdge],
64    q: f32,
65    w: f32,
66    t: f32,
67    connectivity: Option<&ConnectivitySummary>,
68    out: &mut [Tensor10D],
69) -> Result<usize, FeatureError> {
70    if out.len() < positions.len() {
71        return Err(FeatureError::OutputTooSmall {
72            required: positions.len(),
73        });
74    }
75    for (index, position) in positions.iter().copied().enumerate() {
76        out[index] = Tensor10D {
77            q,
78            v: 0.0,
79            w,
80            x: position[0],
81            y: position[1],
82            z: position[2],
83            t,
84            alpha: 0.0,
85            mu: 0.0,
86            sigma: 0.0,
87        };
88    }
89
90    let mut max_degree = 0.0f32;
91    for (edge_index, edge) in half_edges.iter().enumerate() {
92        let vertex = edge.origin as usize;
93        if vertex >= positions.len() {
94            return Err(FeatureError::VertexOutOfRange {
95                half_edge: edge_index,
96                vertex: edge.origin,
97            });
98        }
99        out[vertex].sigma += 1.0;
100        max_degree = max_degree.max(out[vertex].sigma);
101        if edge.twin == INVALID_INDEX {
102            out[vertex].mu = 1.0;
103            out[vertex].v = 3.0;
104            let destination = half_edges
105                .get(edge.next as usize)
106                .map(|next| next.origin as usize);
107            if let Some(destination) = destination.filter(|&v| v < positions.len()) {
108                out[destination].mu = 1.0;
109                out[destination].v = 3.0;
110            }
111        }
112    }
113    if max_degree > 0.0 {
114        for feature in &mut out[..positions.len()] {
115            feature.alpha = feature.sigma / max_degree;
116        }
117    }
118
119    if let Some(summary) = connectivity {
120        let component_class = summary.component_count as f32;
121        let genus_class = summary.genus.unwrap_or(0) as f32;
122        for feature in &mut out[..positions.len()] {
123            feature.sigma += component_class * 0.001;
124            if feature.mu == 0.0 {
125                feature.v = genus_class;
126            }
127        }
128    }
129
130    Ok(positions.len())
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use crate::specialized_libs::computational_geometry::{
137        build_triangle_half_edges, compute_connectivity, EdgeSlot,
138    };
139
140    #[test]
141    fn mesh_graph_becomes_tensor_features() {
142        let positions = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
143        let triangles = [[0, 1, 2]];
144        let mut edges = [HalfEdge::default(); 3];
145        let mut slots = [EdgeSlot::default(); 8];
146        build_triangle_half_edges(3, &triangles, &mut edges, &mut slots).unwrap();
147
148        let mut features = [Tensor10D::default(); 3];
149        let n = encode_topology_features_10d(&positions, &edges, 2.0, 7.0, 11.0, &mut features)
150            .unwrap();
151        assert_eq!(n, 3);
152        assert!(features.iter().all(|feature| feature.v == 3.0));
153        assert!(features.iter().all(|feature| feature.mu == 1.0));
154        assert!(features.iter().all(|feature| feature.alpha == 1.0));
155        assert_eq!(features[1].x, 1.0);
156        assert_eq!(features[2].w, 7.0);
157    }
158
159    #[test]
160    fn connectivity_enrichment_tetrahedron() {
161        // Tetrahedron: 4 vertices, 4 faces, genus 0, 1 component, no boundary.
162        let positions: [[f32; 3]; 4] = [
163            [0.0, 0.0, 0.0],
164            [1.0, 0.0, 0.0],
165            [0.0, 1.0, 0.0],
166            [0.0, 0.0, 1.0],
167        ];
168        let triangles = [[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]];
169        let mut edges = [HalfEdge::default(); 12];
170        let mut slots = [EdgeSlot::default(); 32];
171        build_triangle_half_edges(4, &triangles, &mut edges, &mut slots).unwrap();
172
173        let mut labels = [0u32; 4];
174        let mut queue = [0u32; 4];
175        let mut visited = [false; 12];
176        let summary =
177            compute_connectivity(4, 4, &edges, &mut labels, &mut queue, &mut visited).unwrap();
178
179        let mut features = [Tensor10D::default(); 4];
180        encode_topology_features_10d_with_connectivity(
181            &positions,
182            &edges,
183            1.0,
184            2.0,
185            3.0,
186            Some(&summary),
187            &mut features,
188        )
189        .unwrap();
190
191        // All vertices are interior (no boundary) → v = genus = 0.
192        assert!(features.iter().all(|f| f.v == 0.0));
193        // sigma = degree + component_count * 0.001 = 3 + 0.001
194        assert!(features.iter().all(|f| f.sigma > 3.0 && f.sigma < 3.002));
195    }
196
197    #[test]
198    fn connectivity_enrichment_single_triangle() {
199        let positions = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
200        let triangles = [[0, 1, 2]];
201        let mut edges = [HalfEdge::default(); 3];
202        let mut slots = [EdgeSlot::default(); 8];
203        build_triangle_half_edges(3, &triangles, &mut edges, &mut slots).unwrap();
204
205        let mut labels = [0u32; 1];
206        let mut queue = [0u32; 1];
207        let mut visited = [false; 3];
208        let summary =
209            compute_connectivity(3, 1, &edges, &mut labels, &mut queue, &mut visited).unwrap();
210
211        let mut features = [Tensor10D::default(); 3];
212        encode_topology_features_10d_with_connectivity(
213            &positions,
214            &edges,
215            1.0,
216            2.0,
217            3.0,
218            Some(&summary),
219            &mut features,
220        )
221        .unwrap();
222
223        // All vertices are boundary → v = 3 (not overwritten by genus).
224        assert!(features.iter().all(|f| f.v == 3.0));
225        // sigma = degree + component_count * 0.001 = 1 + 0.001
226        assert!(features.iter().all(|f| f.sigma > 1.0 && f.sigma < 1.002));
227    }
228
229    #[test]
230    fn differential_vs_naive_oracle_multi_component() {
231        // Two disjoint triangles → 2 components, genus None.
232        let positions: [[f32; 3]; 6] = [
233            [0.0, 0.0, 0.0],
234            [1.0, 0.0, 0.0],
235            [0.0, 1.0, 0.0],
236            [2.0, 0.0, 0.0],
237            [3.0, 0.0, 0.0],
238            [2.0, 1.0, 0.0],
239        ];
240        let triangles = [[0, 1, 2], [3, 4, 5]];
241        let mut edges = [HalfEdge::default(); 6];
242        let mut slots = [EdgeSlot::default(); 16];
243        build_triangle_half_edges(6, &triangles, &mut edges, &mut slots).unwrap();
244
245        let mut labels = [0u32; 2];
246        let mut queue = [0u32; 2];
247        let mut visited = [false; 6];
248        let summary =
249            compute_connectivity(6, 2, &edges, &mut labels, &mut queue, &mut visited).unwrap();
250        assert_eq!(summary.component_count, 2);
251
252        let mut features = [Tensor10D::default(); 6];
253        encode_topology_features_10d_with_connectivity(
254            &positions,
255            &edges,
256            1.0,
257            2.0,
258            3.0,
259            Some(&summary),
260            &mut features,
261        )
262        .unwrap();
263
264        // Naive oracle: degree = 1 for all, component_count = 2.
265        // sigma = 1 + 2 * 0.001 = 1.002
266        assert!(features.iter().all(|f| (f.sigma - 1.002).abs() < 1e-5));
267    }
268}