Skip to main content

qualia_cli/
evaluate.rs

1use qualia_core_db::deontic_logic::{evaluate_accessible_layers, VcAttributes};
2use qualia_core_db::modalities::epistemic::{
3    evaluate_epistemic_frame, EpistemicStatus, EpistemicVerdict,
4};
5use qualia_core_db::modalities::paraconsistent::route_paraconsistent;
6use qualia_core_db::NQuin;
7use std::fs::File;
8use std::path::Path;
9
10pub fn map_quins(path: &Path) -> std::io::Result<(memmap2::Mmap, usize)> {
11    let file = File::open(path)?;
12    let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? };
13    let len = mmap.len();
14    Ok((mmap, len))
15}
16
17const EMPTY_QUIN: NQuin = NQuin {
18    subject: 0,
19    predicate: 0,
20    object: 0,
21    context: 0,
22    metadata: 0,
23    parity: 0,
24};
25
26/// Try to cast a memory-mapped file to a slice of NQuins.
27/// Prints a diagnostic and returns an empty slice on failure.
28fn cast_quins(mmap: &memmap2::Mmap) -> &[NQuin] {
29    match bytemuck::try_cast_slice(mmap.as_ref()) {
30        Ok(q) => q,
31        Err(e) => {
32            eprintln!("cast_slice failed ({e:?}) — file may have a volume header; try stripping with `qualia-cli inspect`");
33            &[]
34        }
35    }
36}
37
38// ── Existing modalities ───────────────────────────────────────────────────────
39
40pub fn run_deontic(dataset: &Path, contract_hash: u64) {
41    let (mmap, len) = match map_quins(dataset) {
42        Ok(r) => r,
43        Err(e) => {
44            eprintln!("Cannot open dataset: {e}");
45            return;
46        }
47    };
48    let quins = cast_quins(&mmap);
49    println!("Mapped {} quins ({len} bytes).", quins.len());
50
51    let vc = VcAttributes::from_quins(contract_hash, quins);
52    let storage_dir = std::env::var("QUALIA_DATA_DIR").unwrap_or_else(|_| ".".to_string());
53    let vault = match qualia_core_db::key_vault::KeyVault::load_or_generate(&storage_dir) {
54        Ok(v) => v,
55        Err(e) => {
56            eprintln!("KeyVault error: {e}");
57            return;
58        }
59    };
60    let mut out: [Option<(
61        qualia_core_db::key_vault::SubgraphLayer,
62        qualia_core_db::key_vault::SubgraphKey,
63    )>; 5] = [None, None, None, None, None];
64    let count = evaluate_accessible_layers(&vault, &vc, &[], &mut out);
65    println!("Deontic evaluation for contract 0x{contract_hash:016x}:");
66    if count == 0 {
67        println!("  No permitted layers found.");
68    }
69    for (layer, _key) in out.iter().take(count).flatten() {
70        println!("  Permitted layer: {layer:?}");
71    }
72}
73
74pub fn run_epistemic(dataset: &Path, agent_hash: u64) {
75    let (mmap, len) = match map_quins(dataset) {
76        Ok(r) => r,
77        Err(e) => {
78            eprintln!("Cannot open dataset: {e}");
79            return;
80        }
81    };
82    let quins = cast_quins(&mmap);
83    println!("Mapped {} quins ({len} bytes).", quins.len());
84
85    let empty = EpistemicVerdict {
86        claim: EMPTY_QUIN,
87        status: EpistemicStatus::Uncertain,
88        certainty: 0,
89    };
90    let mut out = vec![empty; 128];
91    match evaluate_epistemic_frame(quins, agent_hash, 0, &mut out) {
92        Ok(n) => println!("Epistemic: {n} verdicts for agent 0x{agent_hash:016x}."),
93        Err(e) => eprintln!("Epistemic error: {e:?}"),
94    }
95}
96
97pub fn run_paraconsistent(dataset: &Path) {
98    let (mmap, len) = match map_quins(dataset) {
99        Ok(r) => r,
100        Err(e) => {
101            eprintln!("Cannot open dataset: {e}");
102            return;
103        }
104    };
105    let quins = cast_quins(&mmap);
106    println!("Mapped {} quins ({len} bytes).", quins.len());
107
108    let mut out_ok = vec![EMPTY_QUIN; quins.len().max(1)];
109    let mut out_iso = vec![EMPTY_QUIN; quins.len().max(1)];
110    match route_paraconsistent(quins, &mut out_ok, &mut out_iso) {
111        Ok((ok, iso)) => {
112            println!("Paraconsistent routing:");
113            println!("  Consistent   : {ok}");
114            println!("  Isolated (⊥) : {iso}");
115        }
116        Err(e) => eprintln!("Paraconsistent error: {e:?}"),
117    }
118}
119
120// ── New modalities ────────────────────────────────────────────────────────────
121
122pub fn run_ltl(dataset: &Path, formula_type: &str, hash_a: u64, hash_b: u64) {
123    use qualia_core_db::modalities::temporal_ltl::{evaluate_ltl_trace, LtlFormula};
124
125    let formula = match formula_type.to_ascii_lowercase().as_str() {
126        "globally" | "g" => LtlFormula::Globally(hash_a),
127        "next" | "x" => LtlFormula::Next(hash_a),
128        "until" | "u" => LtlFormula::Until {
129            ante: hash_a,
130            consequent: hash_b,
131        },
132        other => {
133            eprintln!("Unknown LTL operator '{other}'. Use: globally, next, until");
134            return;
135        }
136    };
137
138    let (mmap, _) = match map_quins(dataset) {
139        Ok(r) => r,
140        Err(e) => {
141            eprintln!("Cannot open dataset: {e}");
142            return;
143        }
144    };
145    let trace = cast_quins(&mmap);
146
147    let result = evaluate_ltl_trace(trace, &formula);
148    println!(
149        "LTL trace evaluation ({formula_type} 0x{hash_a:x}): {}",
150        if result { "HOLDS" } else { "VIOLATED" }
151    );
152}
153
154pub fn run_asp(dataset: &Path, base_index: usize) {
155    use qualia_core_db::modalities::asp::{enumerate_stable_models, MAX_STABLE_MODELS};
156
157    let (mmap, _) = match map_quins(dataset) {
158        Ok(r) => r,
159        Err(e) => {
160            eprintln!("Cannot open dataset: {e}");
161            return;
162        }
163    };
164    let quins = cast_quins(&mmap);
165
166    if quins.is_empty() {
167        eprintln!("Empty dataset.");
168        return;
169    }
170    let base = quins.get(base_index).copied().unwrap_or(EMPTY_QUIN);
171    let mut out = [0u64; MAX_STABLE_MODELS];
172    let count = enumerate_stable_models(&base, quins, &mut out);
173    println!("ASP: {count} stable model(s) from base quin at index {base_index}:");
174    for h in &out[..count] {
175        println!("  0x{h:016x}");
176    }
177}
178
179pub fn run_dl(dataset: &Path, sub_class: u64, super_class: u64) {
180    use qualia_core_db::modalities::dl::check_subsumption_quin;
181
182    let (mmap, _) = match map_quins(dataset) {
183        Ok(r) => r,
184        Err(e) => {
185            eprintln!("Cannot open dataset: {e}");
186            return;
187        }
188    };
189    let tbox = cast_quins(&mmap);
190
191    let result = check_subsumption_quin(sub_class, super_class, tbox);
192    println!(
193        "DL subsumption: 0x{sub_class:x} ⊑ 0x{super_class:x}  →  {}",
194        if result {
195            "TRUE (subclass confirmed)"
196        } else {
197            "FALSE (not subsumed)"
198        }
199    );
200}
201
202pub fn run_probabilistic(weight: f32, threshold: f32) {
203    use qualia_core_db::modalities::probabilistic::evaluate_threshold;
204    let result = evaluate_threshold(weight, threshold);
205    println!(
206        "Probabilistic: weight={weight:.4}, threshold={threshold:.4}  →  {}",
207        if result {
208            "ABOVE THRESHOLD"
209        } else {
210            "BELOW THRESHOLD"
211        }
212    );
213}
214
215pub fn run_linear_logic(dataset: &Path, quin_index: usize) {
216    use qualia_core_db::modalities::linear::{consume_quin, is_consumed};
217
218    let (mmap, _) = match map_quins(dataset) {
219        Ok(r) => r,
220        Err(e) => {
221            eprintln!("Cannot open dataset: {e}");
222            return;
223        }
224    };
225    let quins = cast_quins(&mmap);
226
227    match quins.get(quin_index) {
228        None => eprintln!(
229            "Quin index {quin_index} out of range (dataset has {} quins).",
230            quins.len()
231        ),
232        Some(q) => {
233            let consumed_before = is_consumed(q);
234            let mut mutable_q = *q;
235            consume_quin(&mut mutable_q);
236            println!("Linear logic consume on quin[{quin_index}]:");
237            println!(
238                "  Before consume: {}",
239                if consumed_before {
240                    "already consumed"
241                } else {
242                    "available"
243                }
244            );
245            println!(
246                "  After  consume: {}",
247                if is_consumed(&mutable_q) {
248                    "consumed"
249                } else {
250                    "still available (check bit encoding)"
251                }
252            );
253        }
254    }
255}
256
257pub fn run_dialectical(dataset: &Path, var1: u64, var2: u64) {
258    use qualia_core_db::modalities::dialectical::{are_confounded, do_intervention};
259
260    let (mmap, _) = match map_quins(dataset) {
261        Ok(r) => r,
262        Err(e) => {
263            eprintln!("Cannot open dataset: {e}");
264            return;
265        }
266    };
267    let graph = cast_quins(&mmap);
268
269    let confounded = are_confounded(graph, var1, var2);
270    println!("Dialectical analysis:");
271    println!("  var1=0x{var1:x}, var2=0x{var2:x}");
272    println!("  Confounded: {confounded}");
273
274    if let Some(effect) = do_intervention(graph, var1, 1, var2) {
275        println!("  Intervention (set var1=1) → effect on var2: {effect:.4}");
276    } else {
277        println!("  Intervention: insufficient data for causal effect estimate.");
278    }
279}
280
281pub fn run_diffusion(graph_id: &str) {
282    use qualia_core_db::modalities::diffusion::trigger_diffusion;
283    let triggered = trigger_diffusion(graph_id);
284    println!(
285        "Diffusion trigger for graph '{}': {}",
286        graph_id,
287        if triggered {
288            "initiated"
289        } else {
290            "already active or no-op"
291        }
292    );
293}
294
295pub fn run_spatio_temporal(
296    action: &str,
297    ax1: f64,
298    ay1: f64,
299    ax2: f64,
300    ay2: f64,
301    bx1: f64,
302    by1: f64,
303    bx2: f64,
304    by2: f64,
305) {
306    use qualia_core_db::modalities::spatio_temporal::{
307        evaluate_rcc8, evaluate_temporal, SpatialRegion, TemporalOp,
308    };
309
310    match action.to_ascii_lowercase().as_str() {
311        "rcc8" => {
312            let region_a =
313                SpatialRegion::new(1, vec![(ax1, ay1), (ax2, ay1), (ax2, ay2), (ax1, ay2)]);
314            let region_b =
315                SpatialRegion::new(2, vec![(bx1, by1), (bx2, by1), (bx2, by2), (bx1, by2)]);
316            let relation = evaluate_rcc8(&region_a, &region_b);
317            println!("RCC8 relation A({ax1},{ay1})-({ax2},{ay2}) vs B({bx1},{by1})-({bx2},{by2}): {relation:?}");
318        }
319        "temporal-before" => {
320            let result = evaluate_temporal(
321                TemporalOp::Before,
322                ax1 as i64,
323                ay1 as i64,
324                bx1 as i64,
325                by1 as i64,
326            );
327            println!("Temporal BEFORE [{ax1}..{ay1}] vs [{bx1}..{by1}]: {result}");
328        }
329        "temporal-meets" => {
330            let result = evaluate_temporal(
331                TemporalOp::Meets,
332                ax1 as i64,
333                ay1 as i64,
334                bx1 as i64,
335                by1 as i64,
336            );
337            println!("Temporal MEETS [{ax1}..{ay1}] vs [{bx1}..{by1}]: {result}");
338        }
339        "temporal-overlaps" => {
340            let result = evaluate_temporal(
341                TemporalOp::Overlaps,
342                ax1 as i64,
343                ay1 as i64,
344                bx1 as i64,
345                by1 as i64,
346            );
347            println!("Temporal OVERLAPS [{ax1}..{ay1}] vs [{bx1}..{by1}]: {result}");
348        }
349        "temporal-during" => {
350            let result = evaluate_temporal(
351                TemporalOp::During,
352                ax1 as i64,
353                ay1 as i64,
354                bx1 as i64,
355                by1 as i64,
356            );
357            println!("Temporal DURING [{ax1}..{ay1}] vs [{bx1}..{by1}]: {result}");
358        }
359        other => {
360            eprintln!("Unknown spatio-temporal action '{other}'. Use: rcc8, temporal-before, temporal-meets, temporal-overlaps, temporal-during");
361        }
362    }
363}
364
365pub fn run_interval(action: &str, start1: i64, end1: i64, start2: i64, end2: i64, point: i64) {
366    use qualia_core_db::modalities::interval_reasoning::TemporalInterval;
367
368    let a = TemporalInterval::new(1, start1, end1);
369    let b = TemporalInterval::new(2, start2, end2);
370
371    match action.to_ascii_lowercase().as_str() {
372        "contains" => println!(
373            "Interval [{start1}..{end1}].contains({point}): {}",
374            a.contains(point)
375        ),
376        "overlaps" => println!(
377            "Interval [{start1}..{end1}] overlaps [{start2}..{end2}]: {}",
378            a.overlaps(&b)
379        ),
380        "intersection" => match a.intersection(&b) {
381            Some(i) => println!("Intersection: [{}..{}]", i.start, i.end),
382            None => println!("Intervals do not intersect."),
383        },
384        "union" => {
385            let u = a.union(&b);
386            println!("Union: [{}..{}]", u.start, u.end);
387        }
388        "gap" => match a.gap(&b) {
389            Some(g) => println!("Gap between intervals: {g} time units"),
390            None => println!("Intervals overlap — no gap."),
391        },
392        other => {
393            eprintln!("Unknown interval action '{other}'. Use: contains, overlaps, intersection, union, gap");
394        }
395    }
396}
397
398pub fn run_graph_topology(dataset: &Path, context: u64) {
399    use qualia_core_db::modalities::graph_theory::analyze_graph_topology;
400
401    let (mmap, _) = match map_quins(dataset) {
402        Ok(r) => r,
403        Err(e) => {
404            eprintln!("Cannot open dataset: {e}");
405            return;
406        }
407    };
408    let quins = cast_quins(&mmap);
409
410    let result = match analyze_graph_topology(quins, context) {
411        Ok(result) => result,
412        Err(err) => {
413            eprintln!("Graph topology analysis failed: {err:?}");
414            return;
415        }
416    };
417    println!("Graph topology analysis (context=0x{context:x}):");
418    println!("  Nodes        : {}", result.node_count);
419    println!("  Edges        : {}", result.edge_count);
420    println!("  Density      : {:.4}", result.density);
421    println!("  Communities  : {}", result.communities.len());
422    println!("  Motifs found : {}", result.motifs.len());
423    if !result.top_nodes.is_empty() {
424        println!("  Top nodes by centrality:");
425        for (node_id, score) in result.top_nodes.iter().take(5) {
426            println!("    0x{node_id:016x}  centrality={score:.4}");
427        }
428    }
429}
430
431pub fn run_argumentation(demo: bool, dataset: Option<&Path>) {
432    use qualia_core_db::modalities::argumentation::{
433        create_sanctuary_debate, ArgumentationFramework,
434    };
435
436    let framework: ArgumentationFramework = if demo || dataset.is_none() {
437        create_sanctuary_debate()
438    } else {
439        // Build a minimal framework from the dataset's quins:
440        // each quin.subject attacks quin.object if predicate matches "attacks" hash
441        let (mmap, _) = match dataset.map(|p| map_quins(p)).unwrap() {
442            Ok(r) => r,
443            Err(e) => {
444                eprintln!("Cannot open dataset: {e}");
445                return;
446            }
447        };
448        let quins = cast_quins(&mmap);
449        let attack_pred = qualia_core_db::q_hash("attacks");
450        let mut fw = ArgumentationFramework::new();
451        for q in quins {
452            if q.predicate == attack_pred {
453                fw.add_attack(qualia_core_db::modalities::argumentation::Attack {
454                    attacker: q.subject,
455                    target: q.object,
456                    attack_type: qualia_core_db::modalities::argumentation::AttackType::Rebuttal,
457                    strength: 1.0,
458                });
459            }
460        }
461        fw
462    };
463
464    let grounded = framework.grounded_extension();
465    println!("Argumentation framework analysis:");
466    println!("  Arguments    : {}", framework.arguments.len());
467    println!("  Attacks      : {}", framework.attacks.len());
468    println!("  Grounded ext : {} argument(s)", grounded.len());
469    if grounded.is_empty() {
470        println!("  (empty grounded extension — all arguments attacked)");
471    }
472    for id in &grounded {
473        if let Some(arg) = framework.arguments.get(id) {
474            println!("    0x{id:016x}  \"{}\"", arg.content);
475        } else {
476            println!("    0x{id:016x}");
477        }
478    }
479}
480
481pub fn run_control_feedback(kp: f64, ki: f64, kd: f64, setpoint: f64, measurement: f64) {
482    use qualia_core_db::modalities::control_feedback::{FeedbackController, PidParameters};
483
484    let params = PidParameters {
485        kp,
486        ki,
487        kd,
488        output_min: f64::NEG_INFINITY,
489        output_max: f64::INFINITY,
490    };
491    let mut ctrl = FeedbackController::new("cli".to_string(), setpoint, measurement, params);
492    let output = ctrl.compute_output();
493    println!("PID controller:");
494    println!("  Kp={kp}, Ki={ki}, Kd={kd}");
495    println!("  Setpoint={setpoint}, Measurement={measurement}");
496    println!("  Control output: {output:.6}");
497}
498
499pub fn run_neuro_symbolic() {
500    use qualia_core_db::neuro_symbolic_sieve::SieveLexSpec;
501    use qualia_core_db::q_hash;
502
503    // Demonstrate the grammar sieve spec — shows which NQuin hashes constrain token generation
504    let spec = SieveLexSpec::fever_observation();
505
506    println!("Neuro-symbolic grammar sieve — fever observation demo");
507    println!("  The sieve constrains LLM token generation to well-typed NQuins.");
508    println!("  Token masks are built from the '.q42.lex' lexicon at runtime.");
509    println!();
510    println!("  State machine: ExpectSubject → ExpectPredicate → ExpectObject → Complete");
511    println!();
512    println!("  Allowed hashes:");
513    for i in 0..spec.subjects_len as usize {
514        println!(
515            "    Subject[{i}]   : 0x{:016x}  (q_hash(\"Patient\") = 0x{:016x})",
516            spec.subjects[i],
517            q_hash("Patient")
518        );
519    }
520    for i in 0..spec.predicates_len as usize {
521        println!(
522            "    Predicate[{i}] : 0x{:016x}  (q_hash(\"fever\") = 0x{:016x})",
523            spec.predicates[i],
524            q_hash("fever")
525        );
526    }
527    for i in 0..spec.objects_len as usize {
528        println!(
529            "    Object[{i}]    : 0x{:016x}  (q_hash(\"True\") = 0x{:016x})",
530            spec.objects[i],
531            q_hash("True")
532        );
533    }
534    println!();
535    // Full clinical spec
536    let full = SieveLexSpec::graph_mutation_default();
537    println!(
538        "  graph_mutation_default spec ({} subjects, {} predicates, {} objects):",
539        full.subjects_len, full.predicates_len, full.objects_len
540    );
541    for i in 0..full.subjects_len as usize {
542        println!("    Subject[{i}]   : 0x{:016x}", full.subjects[i]);
543    }
544    for i in 0..full.predicates_len as usize {
545        println!("    Predicate[{i}] : 0x{:016x}", full.predicates[i]);
546    }
547    for i in 0..full.objects_len as usize {
548        println!("    Object[{i}]    : 0x{:016x}", full.objects[i]);
549    }
550    println!();
551    println!("  At inference time, NeuroSymbolicSieve::from_lex_and_tokenizer() maps each");
552    println!("  hash to allowed token IDs from the GGUF vocabulary.  Any token not in the");
553    println!("  current mask has its logit set to -∞, enforcing SHACL-typed output.");
554}