Skip to main content

qualia_core_db/modalities/logic/
qubo.rs

1//! Semantic-to-QUBO boil-down compiler for zero-context quantum offloading.
2//!
3//! Strips DIDs and URIs into ephemeral local indices, emits linear biases and
4//! quadratic coupler weights, then re-hydrates binary solutions back to Quins.
5
6use crate::NQuin;
7
8pub const OP_EMIT_WEIGHT: u8 = 0x50;
9pub const MAX_QUBO_VARS: usize = 64;
10pub const MAX_COUPLERS: usize = 512;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum QuboCompileError {
14    VarBufferFull,
15    CouplerBufferFull,
16    IndexMapFull,
17    ClassifiedEgress,
18}
19
20#[repr(C)]
21#[derive(Debug, Clone, Copy)]
22pub struct QuboWeightEmit {
23    pub var_a: u8,
24    pub var_b: u8,
25    pub weight: f32,
26}
27
28#[derive(Debug, Clone)]
29pub struct QuboMatrix {
30    pub num_vars: u8,
31    pub linear: [f32; MAX_QUBO_VARS],
32    pub couplers: [QuboWeightEmit; MAX_COUPLERS],
33    pub coupler_count: usize,
34    pub index_map: [(u64, u8); MAX_QUBO_VARS],
35    pub index_count: usize,
36}
37
38impl Default for QuboMatrix {
39    fn default() -> Self {
40        Self {
41            num_vars: 0,
42            linear: [0.0; MAX_QUBO_VARS],
43            couplers: [QuboWeightEmit {
44                var_a: 0,
45                var_b: 0,
46                weight: 0.0,
47            }; MAX_COUPLERS],
48            coupler_count: 0,
49            index_map: [(0, 0); MAX_QUBO_VARS],
50            index_count: 0,
51        }
52    }
53}
54
55impl QuboMatrix {
56    fn map_var(&mut self, entity_hash: u64) -> Result<u8, QuboCompileError> {
57        for i in 0..self.index_count {
58            if self.index_map[i].0 == entity_hash {
59                return Ok(self.index_map[i].1);
60            }
61        }
62        if self.index_count >= MAX_QUBO_VARS {
63            return Err(QuboCompileError::IndexMapFull);
64        }
65        let idx = self.index_count as u8;
66        self.index_map[self.index_count] = (entity_hash, idx);
67        self.index_count += 1;
68        if idx as usize + 1 > self.num_vars as usize {
69            self.num_vars = idx + 1;
70        }
71        Ok(idx)
72    }
73
74    pub fn emit_linear(&mut self, var: u8, bias: f32) -> Result<(), QuboCompileError> {
75        if var as usize >= MAX_QUBO_VARS {
76            return Err(QuboCompileError::VarBufferFull);
77        }
78        self.linear[var as usize] += bias;
79        Ok(())
80    }
81
82    pub fn emit_coupler(&mut self, a: u8, b: u8, weight: f32) -> Result<(), QuboCompileError> {
83        if self.coupler_count >= MAX_COUPLERS {
84            return Err(QuboCompileError::CouplerBufferFull);
85        }
86        self.couplers[self.coupler_count] = QuboWeightEmit {
87            var_a: a,
88            var_b: b,
89            weight,
90        };
91        self.coupler_count += 1;
92        Ok(())
93    }
94
95    pub fn wipe_index_map(&mut self) {
96        for slot in &mut self.index_map {
97            unsafe {
98                std::ptr::write_volatile(&mut slot.0, 0);
99            }
100        }
101        self.index_count = 0;
102    }
103}
104
105/// Walk constraint Quins and compile a blind QUBO matrix.
106pub fn compile_quins_to_qubo(
107    quins: &[NQuin],
108    out: &mut QuboMatrix,
109) -> Result<(), QuboCompileError> {
110    *out = QuboMatrix::default();
111    for q in quins {
112        if q.get_sensitivity_byte() == NQuin::SENSITIVITY_CLASSIFIED {
113            return Err(QuboCompileError::ClassifiedEgress);
114        }
115        let subj = out.map_var(q.subject)?;
116        let obj = out.map_var(q.object)?;
117        let pred_low = (q.predicate & 0xFF) as u8;
118        let weight = if pred_low == OP_EMIT_WEIGHT {
119            decode_inline_weight(q.object)
120        } else {
121            penalty_from_predicate(pred_low)
122        };
123        if subj == obj {
124            out.emit_linear(subj, weight)?;
125        } else {
126            out.emit_coupler(subj, obj, weight)?;
127            out.emit_linear(subj, weight * 0.5)?;
128            out.emit_linear(obj, weight * 0.5)?;
129        }
130    }
131    Ok(())
132}
133
134/// VM opcode handler: push a float weight from the object register.
135pub fn emit_weight_from_quin(
136    quin: &NQuin,
137    matrix: &mut QuboMatrix,
138) -> Result<(), QuboCompileError> {
139    let subj = matrix.map_var(quin.subject)?;
140    let obj = matrix.map_var(quin.object)?;
141    let w = decode_inline_weight(quin.object);
142    if subj == obj {
143        matrix.emit_linear(subj, w)
144    } else {
145        matrix.emit_coupler(subj, obj, w)
146    }
147}
148
149fn decode_inline_weight(object: u64) -> f32 {
150    let tag = (object >> 60) & 0x7;
151    if tag == 0b010 {
152        let scaled = (object & 0x0FFF_FFFF_FFFF_FFFF) as i64;
153        (scaled as f32) / 1_000_000.0
154    } else {
155        let raw = (object & 0xFFFF) as i32;
156        (raw as f32) / 100.0
157    }
158}
159
160fn penalty_from_predicate(opcode: u8) -> f32 {
161    match opcode {
162        0x10 => 5.0,  // OP_OBLIGATE — violation penalty
163        0x11 => -2.0, // OP_PERMIT — reward
164        0x12 => 8.0,  // OP_FORBID — hard penalty
165        _ => 1.0,
166    }
167}
168
169/// Classical fallback: greedy energy minimization on small QUBO.
170pub fn solve_classical(matrix: &QuboMatrix, assignment: &mut [u8; MAX_QUBO_VARS]) -> f32 {
171    let n = matrix.num_vars as usize;
172    for i in 0..n {
173        assignment[i] = 0;
174    }
175    let mut improved = true;
176    let mut energy = qubo_energy(matrix, assignment, n);
177    while improved {
178        improved = false;
179        for i in 0..n {
180            assignment[i] = 1 - assignment[i];
181            let new_e = qubo_energy(matrix, assignment, n);
182            if new_e < energy {
183                energy = new_e;
184                improved = true;
185            } else {
186                assignment[i] = 1 - assignment[i];
187            }
188        }
189    }
190    energy
191}
192
193fn qubo_energy(matrix: &QuboMatrix, assignment: &[u8; MAX_QUBO_VARS], n: usize) -> f32 {
194    let mut e = 0.0f32;
195    for i in 0..n {
196        if assignment[i] == 1 {
197            e += matrix.linear[i];
198        }
199    }
200    for c in 0..matrix.coupler_count {
201        let cw = matrix.couplers[c];
202        let a = cw.var_a as usize;
203        let b = cw.var_b as usize;
204        if a < n && b < n && assignment[a] == 1 && assignment[b] == 1 {
205            e += cw.weight;
206        }
207    }
208    e
209}
210
211/// Re-hydrate a binary solution using the ephemeral index map.
212pub fn rehydrate_solution(
213    matrix: &mut QuboMatrix,
214    assignment: &[u8; MAX_QUBO_VARS],
215    out: &mut [NQuin],
216) -> usize {
217    let mut count = 0;
218    for i in 0..matrix.index_count {
219        if count >= out.len() {
220            break;
221        }
222        let (entity, var) = matrix.index_map[i];
223        let val = assignment[var as usize];
224        let predicate = crate::q_hash("q42:quantumAssignment");
225        let context = crate::q_hash("q42:rehydrated");
226        let object = if val == 1 { 1 } else { 0 };
227        let q = NQuin {
228            subject: entity,
229            predicate,
230            object,
231            context,
232            metadata: 0xC000_0000_0000_0003,
233            parity: entity ^ predicate ^ object ^ context,
234        };
235        out[count] = q;
236        count += 1;
237    }
238    matrix.wipe_index_map();
239    count
240}
241
242/// Pre-flight gate: reject prompts that signal classified quantum egress.
243pub fn quantum_prompt_gate(prompt: &str) -> Option<&'static str> {
244    let lower = prompt.to_lowercase();
245    if lower.contains("classified") && (lower.contains("[qpu:") || lower.contains("quantum")) {
246        return Some("FATAL: Cannot egress 0x02_CLASSIFIED assertions to a remote QPU.");
247    }
248    if lower.contains("sensitivitylabel") && lower.contains("0x02") {
249        return Some("FATAL: Classified sensitivity label blocks QPU egress.");
250    }
251    None
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn compile_empty_quins() {
260        let mut m = QuboMatrix::default();
261        assert!(compile_quins_to_qubo(&[], &mut m).is_ok());
262        assert_eq!(m.num_vars, 0);
263    }
264
265    #[test]
266    fn classified_quin_blocks_egress() {
267        let mut q = NQuin {
268            subject: 1,
269            predicate: 0,
270            object: 2,
271            context: 0,
272            metadata: 0,
273            parity: 0,
274        };
275        q.set_sensitivity_byte(NQuin::SENSITIVITY_CLASSIFIED);
276        let mut m = QuboMatrix::default();
277        assert_eq!(
278            compile_quins_to_qubo(&[q], &mut m),
279            Err(QuboCompileError::ClassifiedEgress)
280        );
281    }
282
283    #[test]
284    fn classical_solver_finds_low_energy() {
285        let mut m = QuboMatrix::default();
286        m.num_vars = 2;
287        m.linear[0] = -1.0;
288        m.linear[1] = -1.0;
289        m.couplers[0] = QuboWeightEmit {
290            var_a: 0,
291            var_b: 1,
292            weight: 2.0,
293        };
294        m.coupler_count = 1;
295        let mut assign = [0u8; MAX_QUBO_VARS];
296        let e = solve_classical(&m, &mut assign);
297        assert!(e <= 0.0);
298    }
299}