Skip to main content

qualia_core_db/modalities/
manifold.rs

1use crate::solvers::linear_algebra::{FixedLanczosEigensolver, Matrix4x4, Vector4};
2use crate::solvers::{SolverConfig, SolverResult, SolverState, SolversError};
3use crate::{q_hash, NQuin};
4
5pub const MANIFOLD_HEAD_PREDICATE: u64 = q_hash("q42:manifold10d:head");
6pub const MANIFOLD_TAIL_PREDICATE: u64 = q_hash("q42:manifold10d:tail");
7pub const MANIFOLD_THRESHOLD_HOLDS: u64 = q_hash("q42:manifold10d:threshold-holds");
8pub const MANIFOLD_THRESHOLD_MISS: u64 = q_hash("q42:manifold10d:threshold-miss");
9
10pub const MANIFOLD_ATOM_COHERENT: u64 = q_hash("q42:manifold10d:coherent");
11pub const MANIFOLD_ATOM_RECURRENT: u64 = q_hash("q42:manifold10d:recurrent");
12pub const MANIFOLD_ATOM_DENSE: u64 = q_hash("q42:manifold10d:dense");
13pub const MANIFOLD_ATOM_CURVED: u64 = q_hash("q42:manifold10d:curved");
14pub const MANIFOLD_ATOM_STABLE: u64 = q_hash("q42:manifold10d:stable-topology");
15pub const MANIFOLD_ASP_ATOMS: [u64; 5] = [
16    MANIFOLD_ATOM_COHERENT,
17    MANIFOLD_ATOM_RECURRENT,
18    MANIFOLD_ATOM_DENSE,
19    MANIFOLD_ATOM_CURVED,
20    MANIFOLD_ATOM_STABLE,
21];
22
23/// Defines a tensor's precise location in the 10D geometric frameset.
24/// This replaces the concept of integer chronological layers (e.g. "Layer 12")
25/// with a continuous spatial coordinate in P64 containers.
26#[repr(C)]
27#[derive(Clone, Copy, Debug, Default, PartialEq)]
28pub struct ManifoldCoordinate10D {
29    pub scale: f32,
30    pub attention_depth: f32,
31    pub epistemic_weight: f32,
32    pub topological_spin: f32,
33    pub temporal_decay: f32,
34    pub entropy_bias: f32,
35    pub spatial_phase: f32,
36    pub recurrence_frequency: f32,
37    pub density_threshold: f32,
38    pub manifold_curvature: f32,
39}
40
41impl ManifoldCoordinate10D {
42    pub const DIMENSIONS: usize = 10;
43
44    /// Raw f32 representation used by the fixed 64-byte P64 manifold record.
45    pub fn as_f32_array(&self) -> [f32; Self::DIMENSIONS] {
46        [
47            self.scale,
48            self.attention_depth,
49            self.epistemic_weight,
50            self.topological_spin,
51            self.temporal_decay,
52            self.entropy_bias,
53            self.spatial_phase,
54            self.recurrence_frequency,
55            self.density_threshold,
56            self.manifold_curvature,
57        ]
58    }
59
60    /// Decode one cache-line-sized P64 manifold record.
61    pub fn from_p64_bytes(bytes: &[u8]) -> Result<Self, String> {
62        if bytes.len() < 40 {
63            return Err("p64: truncated 10D manifold coordinate".to_string());
64        }
65        let value = |index: usize| {
66            let start = index * 4;
67            f32::from_le_bytes(bytes[start..start + 4].try_into().unwrap())
68        };
69        let coordinate = Self {
70            scale: value(0),
71            attention_depth: value(1),
72            epistemic_weight: value(2),
73            topological_spin: value(3),
74            temporal_decay: value(4),
75            entropy_bias: value(5),
76            spatial_phase: value(6),
77            recurrence_frequency: value(7),
78            density_threshold: value(8),
79            manifold_curvature: value(9),
80        };
81        if coordinate
82            .as_f32_array()
83            .iter()
84            .any(|value| !value.is_finite())
85        {
86            return Err("p64: non-finite 10D manifold coordinate".to_string());
87        }
88        Ok(coordinate)
89    }
90
91    /// Convert to the raw 10D array for math solvers
92    pub fn as_array(&self) -> [f64; 10] {
93        [
94            self.scale as f64,
95            self.attention_depth as f64,
96            self.epistemic_weight as f64,
97            self.topological_spin as f64,
98            self.temporal_decay as f64,
99            self.entropy_bias as f64,
100            self.spatial_phase as f64,
101            self.recurrence_frequency as f64,
102            self.density_threshold as f64,
103            self.manifold_curvature as f64,
104        ]
105    }
106
107    /// Map a legacy 1D sequential Transformer layer onto the 10D geometry
108    pub fn from_sequential_layer(layer: u32, total_layers: u32) -> Self {
109        let max_l = total_layers.max(1) as f32;
110        let l = layer as f32;
111        let depth = l / max_l;
112        Self {
113            scale: depth,
114            attention_depth: 1.0 - depth,
115            epistemic_weight: 1.0,
116            topological_spin: (depth * std::f32::consts::PI).sin(),
117            temporal_decay: 0.1,
118            entropy_bias: 0.5,
119            spatial_phase: (depth * std::f32::consts::TAU).cos(),
120            recurrence_frequency: 1.0,
121            density_threshold: 0.8,
122            manifold_curvature: 0.0,
123        }
124    }
125}
126
127#[repr(u8)]
128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
129pub enum ManifoldDimension {
130    Scale = 0,
131    AttentionDepth = 1,
132    EpistemicWeight = 2,
133    TopologicalSpin = 3,
134    TemporalDecay = 4,
135    EntropyBias = 5,
136    SpatialPhase = 6,
137    RecurrenceFrequency = 7,
138    DensityThreshold = 8,
139    ManifoldCurvature = 9,
140}
141
142impl ManifoldDimension {
143    pub fn from_u8(value: u8) -> Option<Self> {
144        Some(match value {
145            0 => Self::Scale,
146            1 => Self::AttentionDepth,
147            2 => Self::EpistemicWeight,
148            3 => Self::TopologicalSpin,
149            4 => Self::TemporalDecay,
150            5 => Self::EntropyBias,
151            6 => Self::SpatialPhase,
152            7 => Self::RecurrenceFrequency,
153            8 => Self::DensityThreshold,
154            9 => Self::ManifoldCurvature,
155            _ => return None,
156        })
157    }
158
159    pub fn value(self, coordinate: &ManifoldCoordinate10D) -> f32 {
160        coordinate.as_f32_array()[self as usize]
161    }
162}
163
164#[repr(C)]
165#[derive(Clone, Copy, Debug, Default, PartialEq)]
166pub struct ManifoldState10D {
167    /// Unique state identifier (for example `tensor_hash ^ logical_clock`).
168    pub state_id: u64,
169    pub timestamp: u64,
170    pub coordinate: ManifoldCoordinate10D,
171}
172
173#[inline]
174fn pack_f32_pair(low: f32, high: f32) -> u64 {
175    low.to_bits() as u64 | ((high.to_bits() as u64) << 32)
176}
177
178#[inline]
179fn unpack_f32_pair(value: u64) -> (f32, f32) {
180    (
181        f32::from_bits(value as u32),
182        f32::from_bits((value >> 32) as u32),
183    )
184}
185
186#[inline]
187fn quin_with_parity(
188    subject: u64,
189    predicate: u64,
190    object: u64,
191    context: u64,
192    metadata: u64,
193) -> NQuin {
194    NQuin {
195        subject,
196        predicate,
197        object,
198        context,
199        metadata,
200        parity: subject ^ predicate ^ object ^ context,
201    }
202}
203
204/// Encode one 10D state into two normal 48-byte Quins.
205///
206/// The head carries dimensions 0..=5 in three packed f32 pairs. The tail
207/// carries dimensions 6..=9 and the logical timestamp. Both retain the normal
208/// XOR parity contract. These are VM-internal geometry records: their packed
209/// numeric fields are not resolver literals and therefore avoid the known
210/// object type-tag conflict.
211pub fn encode_manifold_state(state: &ManifoldState10D, out: &mut [NQuin; 2]) {
212    let d = state.coordinate.as_f32_array();
213    out[0] = quin_with_parity(
214        state.state_id,
215        MANIFOLD_HEAD_PREDICATE,
216        pack_f32_pair(d[0], d[1]),
217        pack_f32_pair(d[2], d[3]),
218        pack_f32_pair(d[4], d[5]),
219    );
220    out[1] = quin_with_parity(
221        state.state_id,
222        MANIFOLD_TAIL_PREDICATE,
223        pack_f32_pair(d[6], d[7]),
224        pack_f32_pair(d[8], d[9]),
225        state.timestamp,
226    );
227}
228
229pub fn decode_manifold_state(head: &NQuin, tail: &NQuin) -> Option<ManifoldState10D> {
230    if head.subject != tail.subject
231        || head.predicate != MANIFOLD_HEAD_PREDICATE
232        || tail.predicate != MANIFOLD_TAIL_PREDICATE
233        || head.parity != head.subject ^ head.predicate ^ head.object ^ head.context
234        || tail.parity != tail.subject ^ tail.predicate ^ tail.object ^ tail.context
235    {
236        return None;
237    }
238    let (d0, d1) = unpack_f32_pair(head.object);
239    let (d2, d3) = unpack_f32_pair(head.context);
240    let (d4, d5) = unpack_f32_pair(head.metadata);
241    let (d6, d7) = unpack_f32_pair(tail.object);
242    let (d8, d9) = unpack_f32_pair(tail.context);
243    let coordinate = ManifoldCoordinate10D {
244        scale: d0,
245        attention_depth: d1,
246        epistemic_weight: d2,
247        topological_spin: d3,
248        temporal_decay: d4,
249        entropy_bias: d5,
250        spatial_phase: d6,
251        recurrence_frequency: d7,
252        density_threshold: d8,
253        manifold_curvature: d9,
254    };
255    if coordinate
256        .as_f32_array()
257        .iter()
258        .any(|value| !value.is_finite())
259    {
260        return None;
261    }
262    Some(ManifoldState10D {
263        state_id: head.subject,
264        timestamp: tail.metadata,
265        coordinate,
266    })
267}
268
269/// Decode and chronologically order manifold pairs from an arena snapshot.
270/// Invalid/incomplete pairs are ignored. Caller owns the bounded output.
271pub fn collect_manifold_states(quins: &[NQuin], out: &mut [ManifoldState10D]) -> usize {
272    let mut count = 0usize;
273    for head in quins {
274        if head.predicate != MANIFOLD_HEAD_PREDICATE || count == out.len() {
275            continue;
276        }
277        let Some(tail) = quins.iter().find(|candidate| {
278            candidate.subject == head.subject && candidate.predicate == MANIFOLD_TAIL_PREDICATE
279        }) else {
280            continue;
281        };
282        if let Some(state) = decode_manifold_state(head, tail) {
283            out[count] = state;
284            count += 1;
285        }
286    }
287    // Stable bounded insertion sort; no allocation and deterministic for equal timestamps.
288    for index in 1..count {
289        let state = out[index];
290        let mut cursor = index;
291        while cursor > 0 && out[cursor - 1].timestamp > state.timestamp {
292            out[cursor] = out[cursor - 1];
293            cursor -= 1;
294        }
295        out[cursor] = state;
296    }
297    count
298}
299
300/// Convert a 10D coordinate trace into propositions consumable by the existing
301/// LTL evaluator. `at_least=true` means `dimension >= threshold`; false means
302/// `dimension <= threshold`.
303pub fn project_manifold_ltl_trace(
304    states: &[ManifoldState10D],
305    dimension: ManifoldDimension,
306    threshold: f32,
307    at_least: bool,
308    out: &mut [NQuin],
309) -> usize {
310    let count = states.len().min(out.len());
311    for (target, state) in out[..count].iter_mut().zip(states) {
312        let value = dimension.value(&state.coordinate);
313        let holds = if at_least {
314            value >= threshold
315        } else {
316            value <= threshold
317        };
318        *target = quin_with_parity(
319            state.state_id,
320            if holds {
321                MANIFOLD_THRESHOLD_HOLDS
322            } else {
323                MANIFOLD_THRESHOLD_MISS
324            },
325            value.to_bits() as u64,
326            dimension as u64,
327            state.timestamp,
328        );
329    }
330    count
331}
332
333/// Derive bounded topology facts from manifold states and run the real
334/// Gelfond-Lifschitz answer-set evaluator.
335pub fn evaluate_manifold_answer_sets(states: &[ManifoldState10D], out: &mut [u64]) -> usize {
336    use crate::modalities::asp::{compute_answer_sets, AspRule};
337
338    let mut facts = [false; 4];
339    for state in states {
340        let coordinate = &state.coordinate;
341        facts[0] |= coordinate.epistemic_weight >= coordinate.entropy_bias;
342        facts[1] |= coordinate.recurrence_frequency > coordinate.temporal_decay;
343        facts[2] |= coordinate.scale >= coordinate.density_threshold;
344        facts[3] |= coordinate.manifold_curvature.abs() > 0.25;
345    }
346
347    let mut rules = [AspRule::fact(0); 5];
348    let mut rule_count = 0usize;
349    for (present, atom) in facts.iter().zip(MANIFOLD_ASP_ATOMS.iter().take(4)) {
350        if *present {
351            rules[rule_count] = AspRule::fact(*atom);
352            rule_count += 1;
353        }
354    }
355    // stable_topology :- coherent, recurrent, not curved.
356    rules[rule_count] = AspRule::new(
357        MANIFOLD_ATOM_STABLE,
358        &[MANIFOLD_ATOM_COHERENT, MANIFOLD_ATOM_RECURRENT],
359        &[MANIFOLD_ATOM_CURVED],
360    );
361    rule_count += 1;
362    compute_answer_sets(&MANIFOLD_ASP_ATOMS, &rules[..rule_count], out)
363}
364
365/// Project a continuous 10D symmetric matrix representation into a valid 4D unit quaternion.
366/// This avoids gimbal lock and inverse-image discontinuities common in neural orientation regression.
367pub fn project_10d_to_quaternion(parameters: &[f64; 10]) -> SolverResult<Vector4> {
368    // Reconstruct the 4x4 symmetric matrix from the 10 parameters
369    let mut matrix = Matrix4x4::zero();
370    matrix.set(0, 0, parameters[0]);
371    matrix.set(0, 1, parameters[1]);
372    matrix.set(0, 2, parameters[2]);
373    matrix.set(0, 3, parameters[3]);
374
375    matrix.set(1, 0, parameters[1]);
376    matrix.set(1, 1, parameters[4]);
377    matrix.set(1, 2, parameters[5]);
378    matrix.set(1, 3, parameters[6]);
379
380    matrix.set(2, 0, parameters[2]);
381    matrix.set(2, 1, parameters[5]);
382    matrix.set(2, 2, parameters[7]);
383    matrix.set(2, 3, parameters[8]);
384
385    matrix.set(3, 0, parameters[3]);
386    matrix.set(3, 1, parameters[6]);
387    matrix.set(3, 2, parameters[8]);
388    matrix.set(3, 3, parameters[9]);
389
390    let mut solver = FixedLanczosEigensolver {
391        iteration: 0,
392        alpha: [0.0; 100],
393        beta: [0.0; 100],
394        vectors: [Vector4::zero(); 3],
395        eigenvalues: [0.0; 4],
396        config: SolverConfig::default(),
397        solver_state: SolverState::default(),
398    };
399
400    // We solve for the smallest eigenvector.
401    match solver.solve_smallest_eigenvector(&matrix) {
402        Ok(vec) => Ok(vec),
403        Err(e) => Err(e),
404    }
405}
406
407impl FixedLanczosEigensolver {
408    /// Solves for the eigenvector corresponding to the smallest eigenvalue.
409    /// This is a deterministic numerical method.
410    pub fn solve_smallest_eigenvector(&mut self, matrix: &Matrix4x4) -> SolverResult<Vector4> {
411        // Mock implementation of Lanczos iteration for a 4x4 symmetric matrix
412        // In reality, this would run the tridiagonalization and then QR algorithm.
413        // For zero-allocation, we perform power iteration on (c*I - A) to find the smallest eigenpair.
414
415        let mut v = Vector4 {
416            data: [1.0, 0.5, 0.25, 0.125],
417        };
418
419        let mut max_row_sum = 0.0;
420        for i in 0..4 {
421            let mut sum = 0.0;
422            for j in 0..4 {
423                sum += matrix.data[i][j].abs();
424            }
425            if sum > max_row_sum {
426                max_row_sum = sum;
427            }
428        }
429        let c = max_row_sum + 1.0; // Shift to make (c*I - A) positive definite
430
431        for _ in 0..self.config.max_iterations {
432            let mut shifted_matrix = Matrix4x4::zero();
433            for i in 0..4 {
434                for j in 0..4 {
435                    if i == j {
436                        shifted_matrix.set(i, j, c - matrix.get(i, j));
437                    } else {
438                        shifted_matrix.set(i, j, -matrix.get(i, j));
439                    }
440                }
441            }
442
443            let mut next_v = shifted_matrix.multiply_vector(&v);
444
445            // Normalize next_v
446            let norm = (next_v.data[0].powi(2)
447                + next_v.data[1].powi(2)
448                + next_v.data[2].powi(2)
449                + next_v.data[3].powi(2))
450            .sqrt();
451
452            if norm == 0.0 {
453                return Err(SolversError::SingularMatrix);
454            }
455
456            next_v.data[0] /= norm;
457            next_v.data[1] /= norm;
458            next_v.data[2] /= norm;
459            next_v.data[3] /= norm;
460
461            // Check convergence
462            let mut diff = 0.0;
463            for i in 0..4 {
464                diff += (next_v.data[i] - v.data[i]).abs();
465            }
466
467            v = next_v;
468            self.iteration += 1;
469
470            if diff < self.config.tolerance {
471                self.solver_state.converged = true;
472                return Ok(v);
473            }
474        }
475
476        Err(SolversError::ConvergenceFailed)
477    }
478}
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483    use crate::modalities::asp::atom_index;
484    use crate::modalities::temporal_ltl::{evaluate_ltl_trace, LtlFormula};
485
486    fn state(id: u64, timestamp: u64, scale: f32, curvature: f32) -> ManifoldState10D {
487        let mut coordinate = ManifoldCoordinate10D::from_sequential_layer(timestamp as u32, 10);
488        coordinate.scale = scale;
489        coordinate.density_threshold = 0.5;
490        coordinate.manifold_curvature = curvature;
491        ManifoldState10D {
492            state_id: id,
493            timestamp,
494            coordinate,
495        }
496    }
497
498    #[test]
499    fn two_quin_encoding_round_trips_and_sorts() {
500        let older = state(10, 1, 0.7, 0.0);
501        let newer = state(20, 2, 0.8, 0.0);
502        let mut older_pair = [NQuin::default(); 2];
503        let mut newer_pair = [NQuin::default(); 2];
504        encode_manifold_state(&older, &mut older_pair);
505        encode_manifold_state(&newer, &mut newer_pair);
506        assert_eq!(
507            decode_manifold_state(&older_pair[0], &older_pair[1]),
508            Some(older)
509        );
510
511        let arena_order = [newer_pair[1], older_pair[0], newer_pair[0], older_pair[1]];
512        let mut decoded = [ManifoldState10D::default(); 2];
513        let count = collect_manifold_states(&arena_order, &mut decoded);
514        assert_eq!(count, 2);
515        assert_eq!(decoded[0], older);
516        assert_eq!(decoded[1], newer);
517    }
518
519    #[test]
520    fn manifold_projection_drives_existing_ltl_evaluator() {
521        let states = [state(1, 1, 0.6, 0.0), state(2, 2, 0.9, 0.0)];
522        let mut trace = [NQuin::default(); 2];
523        let count =
524            project_manifold_ltl_trace(&states, ManifoldDimension::Scale, 0.5, true, &mut trace);
525        assert!(evaluate_ltl_trace(
526            &trace[..count],
527            &LtlFormula::Globally(MANIFOLD_THRESHOLD_HOLDS)
528        ));
529
530        let count =
531            project_manifold_ltl_trace(&states, ManifoldDimension::Scale, 0.8, true, &mut trace);
532        assert!(!evaluate_ltl_trace(
533            &trace[..count],
534            &LtlFormula::Globally(MANIFOLD_THRESHOLD_HOLDS)
535        ));
536        assert!(evaluate_ltl_trace(
537            &trace[..count],
538            &LtlFormula::Finally(MANIFOLD_THRESHOLD_HOLDS)
539        ));
540    }
541
542    #[test]
543    fn manifold_topology_drives_real_answer_set_semantics() {
544        let states = [state(1, 1, 0.7, 0.0), state(2, 2, 0.8, 0.0)];
545        let mut models = [0u64; 8];
546        let count = evaluate_manifold_answer_sets(&states, &mut models);
547        assert_eq!(count, 1);
548        let stable_index = atom_index(&MANIFOLD_ASP_ATOMS, MANIFOLD_ATOM_STABLE).unwrap();
549        assert_ne!(models[0] & (1u64 << stable_index), 0);
550
551        let curved = [state(3, 3, 0.8, 0.75)];
552        let count = evaluate_manifold_answer_sets(&curved, &mut models);
553        assert_eq!(count, 1);
554        assert_eq!(models[0] & (1u64 << stable_index), 0);
555    }
556}