1use ternlang_core::trit::Trit;
19
20pub mod spectra_compat {
23 use super::*;
24
25 pub fn import_spectra_weights(raw_data: &[f32], rows: usize, cols: usize) -> TritMatrix {
28 println!("ternlang-ml: Annexing Spectra-1.1 weights (Scale: 1.2T tokens)...");
29 TritMatrix::from_f32(rows, cols, raw_data, 0.5)
31 }
32}
33
34pub mod coherence;
35
36pub fn quantize(weights: &[f32], threshold: f32) -> Vec<Trit> {
47 weights.iter().map(|&w| {
48 if w > threshold {
49 Trit::Affirm
50 } else if w < -threshold {
51 Trit::Reject
52 } else {
53 Trit::Tend
54 }
55 }).collect()
56}
57
58pub fn bitnet_threshold(weights: &[f32]) -> f32 {
60 let mean_abs = weights.iter().map(|w| w.abs()).sum::<f32>() / weights.len() as f32;
61 0.5 * mean_abs
62}
63
64pub struct TritMatrix {
68 pub rows: usize,
69 pub cols: usize,
70 pub data: Vec<Trit>,
71}
72
73impl TritMatrix {
74 pub fn new(rows: usize, cols: usize) -> Self {
75 Self { rows, cols, data: vec![Trit::Tend; rows * cols] }
76 }
77
78 pub fn from_trits(rows: usize, cols: usize, data: Vec<Trit>) -> Self {
79 assert_eq!(data.len(), rows * cols);
80 Self { rows, cols, data }
81 }
82
83 pub fn from_f32(rows: usize, cols: usize, weights: &[f32], threshold: f32) -> Self {
84 Self::from_trits(rows, cols, quantize(weights, threshold))
85 }
86
87 #[inline]
88 pub fn get(&self, row: usize, col: usize) -> Trit {
89 self.data[row * self.cols + col]
90 }
91
92 #[inline]
93 pub fn set(&mut self, row: usize, col: usize, val: Trit) {
94 self.data[row * self.cols + col] = val;
95 }
96
97 pub fn sparsity(&self) -> f64 {
99 let zeros = self.data.iter().filter(|&&t| t == Trit::Tend).count();
100 zeros as f64 / self.data.len() as f64
101 }
102
103 pub fn nnz(&self) -> usize {
105 self.data.iter().filter(|&&t| t != Trit::Tend).count()
106 }
107
108 pub fn to_i8_vec(&self) -> Vec<i8> {
110 self.data.iter().map(|&t| match t {
111 Trit::Affirm => 1,
112 Trit::Reject => -1,
113 Trit::Tend => 0,
114 }).collect()
115 }
116}
117
118pub fn dense_matmul(a: &TritMatrix, b: &TritMatrix) -> TritMatrix {
124 assert_eq!(a.cols, b.rows, "matmul dimension mismatch: a.cols must equal b.rows");
125 let mut c = TritMatrix::new(a.rows, b.cols);
126 for row in 0..a.rows {
127 for col in 0..b.cols {
128 let mut acc = Trit::Tend;
129 for k in 0..a.cols {
130 let prod = a.get(row, k) * b.get(k, col);
131 let (sum, _carry) = acc + prod;
132 acc = sum;
133 }
134 c.set(row, col, acc);
135 }
136 }
137 c
138}
139
140pub fn sparse_matmul(a: &TritMatrix, b: &TritMatrix) -> (TritMatrix, usize) {
160 use rayon::prelude::*;
161
162 assert_eq!(a.cols, b.rows, "matmul dimension mismatch");
163
164 #[inline(always)]
165 fn t2i(t: Trit) -> i8 {
166 match t { Trit::Reject => -1, Trit::Tend => 0, Trit::Affirm => 1 }
167 }
168
169 let a_flat: Vec<i8> = a.data.iter().map(|&t| t2i(t)).collect();
171 let a_cols = a.cols;
172
173 let mut csc_offsets = vec![0usize; b.cols + 1];
178 for k in 0..b.rows {
180 for j in 0..b.cols {
181 if t2i(b.data[k * b.cols + j]) != 0 {
182 csc_offsets[j + 1] += 1;
183 }
184 }
185 }
186 for j in 0..b.cols {
188 csc_offsets[j + 1] += csc_offsets[j];
189 }
190 let nnz = csc_offsets[b.cols];
191 let mut csc_idx = vec![0u32; nnz];
192 let mut csc_val = vec![0i8; nnz];
193 let mut col_cursor = csc_offsets[..b.cols].to_vec(); for k in 0..b.rows {
195 for j in 0..b.cols {
196 let w = t2i(b.data[k * b.cols + j]);
197 if w != 0 {
198 let pos = col_cursor[j];
199 csc_idx[pos] = k as u32;
200 csc_val[pos] = w;
201 col_cursor[j] += 1;
202 }
203 }
204 }
205
206 let dense_ops = a.rows * b.cols * a.cols;
207 let active_ops = nnz * a.rows;
208 let skipped = dense_ops.saturating_sub(active_ops);
209
210 let mut out_flat = vec![0i8; a.rows * b.cols];
213
214 out_flat
215 .par_chunks_mut(b.cols)
216 .enumerate()
217 .for_each(|(row, row_out)| {
218 let a_row = &a_flat[row * a_cols..(row + 1) * a_cols];
219 for col in 0..b.cols {
220 let start = csc_offsets[col];
221 let end = csc_offsets[col + 1];
222 let mut acc: i32 = 0;
223 for i in start..end {
226 let k = unsafe { *csc_idx.get_unchecked(i) } as usize;
227 let w = unsafe { *csc_val.get_unchecked(i) } as i32;
228 let av = unsafe { *a_row.get_unchecked(k) } as i32;
229 acc += av * w;
230 }
231 row_out[col] = if acc > 0 { 1 } else if acc < 0 { -1 } else { 0 };
232 }
233 });
234
235 let c_data: Vec<Trit> = out_flat.into_iter().map(|v| Trit::from(v)).collect();
237 let c = TritMatrix { rows: a.rows, cols: b.cols, data: c_data };
238
239 (c, skipped)
240}
241
242pub fn linear(input: &TritMatrix, weights: &TritMatrix) -> (TritMatrix, usize) {
250 sparse_matmul(input, weights)
251}
252
253pub struct BenchmarkResult {
257 pub dense_ops: usize,
258 pub sparse_ops: usize,
259 pub skipped_ops: usize,
260 pub skip_rate: f64,
261 pub weight_sparsity: f64,
262}
263
264impl BenchmarkResult {
265 pub fn print_summary(&self) {
266 println!("=== Ternary Sparse Matmul Benchmark ===");
267 println!(" Weight sparsity: {:.1}% zeros", self.weight_sparsity * 100.0);
268 println!(" Dense ops: {}", self.dense_ops);
269 println!(" Sparse ops: {}", self.sparse_ops);
270 println!(" Skipped ops: {}", self.skipped_ops);
271 println!(" Skip rate: {:.1}%", self.skip_rate * 100.0);
272 println!(" Ops saved: {:.1}x fewer multiplies", self.dense_ops as f64 / self.sparse_ops.max(1) as f64);
273 }
274}
275
276pub fn benchmark(a: &TritMatrix, b: &TritMatrix) -> BenchmarkResult {
277 let dense_ops = a.rows * a.cols * b.cols;
278 let (_result, skipped) = sparse_matmul(a, b);
279 let sparse_ops = dense_ops - skipped;
280 BenchmarkResult {
281 dense_ops,
282 sparse_ops,
283 skipped_ops: skipped,
284 skip_rate: skipped as f64 / dense_ops as f64,
285 weight_sparsity: b.sparsity(),
286 }
287}
288
289pub fn trit_activation(t: Trit) -> Trit { t }
295
296pub fn majority(trits: &[Trit]) -> Trit {
299 let sum: i32 = trits.iter().map(|&t| match t {
300 Trit::Affirm => 1,
301 Trit::Reject => -1,
302 Trit::Tend => 0,
303 }).sum();
304 match sum.signum() {
305 1 => Trit::Affirm,
306 -1 => Trit::Reject,
307 _ => Trit::Tend,
308 }
309}
310
311pub struct TernaryMLP {
321 pub w1: TritMatrix, pub w2: TritMatrix, pub in_features: usize,
324 pub hidden_size: usize,
325 pub out_features: usize,
326}
327
328impl TernaryMLP {
329 pub fn new(w1: TritMatrix, w2: TritMatrix) -> Self {
331 let in_features = w1.rows;
332 let hidden_size = w1.cols;
333 let out_features = w2.cols;
334 assert_eq!(w2.rows, hidden_size, "w1.cols must equal w2.rows");
335 Self { w1, w2, in_features, hidden_size, out_features }
336 }
337
338 pub fn from_f32(
340 in_features: usize, hidden_size: usize, out_features: usize,
341 w1_f32: &[f32], w2_f32: &[f32],
342 ) -> Self {
343 let tau1 = bitnet_threshold(w1_f32);
344 let tau2 = bitnet_threshold(w2_f32);
345 let w1 = TritMatrix::from_f32(in_features, hidden_size, w1_f32, tau1);
346 let w2 = TritMatrix::from_f32(hidden_size, out_features, w2_f32, tau2);
347 Self::new(w1, w2)
348 }
349
350 pub fn forward(&self, input: &TritMatrix) -> (TritMatrix, usize, usize) {
354 assert_eq!(input.cols, self.in_features,
355 "input width must match in_features");
356
357 let (hidden, skip1) = sparse_matmul(input, &self.w1);
359
360 let hidden_act = TritMatrix::from_trits(
362 hidden.rows, hidden.cols,
363 hidden.data.iter().map(|&t| trit_activation(t)).collect(),
364 );
365
366 let (output, skip2) = sparse_matmul(&hidden_act, &self.w2);
368
369 (output, skip1, skip2)
370 }
371
372 pub fn predict(&self, input: &TritMatrix) -> usize {
375 let (output, _, _) = self.forward(input);
376 let row = 0;
377 let mut best_col = 0;
378 let mut best_val: i8 = -2;
379 for col in 0..self.out_features {
380 let v = match output.get(row, col) {
381 Trit::Affirm => 1,
382 Trit::Tend => 0,
383 Trit::Reject => -1,
384 };
385 if v > best_val { best_val = v; best_col = col; }
386 }
387 best_col
388 }
389
390 pub fn layer1_sparsity(&self) -> f64 { self.w1.sparsity() }
391 pub fn layer2_sparsity(&self) -> f64 { self.w2.sparsity() }
392}
393
394#[derive(Debug)]
398pub struct TimedResult {
399 pub size: usize, pub dense_ops: usize,
401 pub sparse_ops: usize,
402 pub skipped_ops: usize,
403 pub weight_sparsity: f64,
404 pub skip_rate: f64,
405 pub speedup: f64,
406 pub dense_us: u64, pub sparse_us: u64, }
409
410pub fn timed_benchmark(sizes: &[usize], reps: usize) -> Vec<TimedResult> {
415 use std::time::Instant;
416
417 fn lcg_weights(n: usize, seed: u64) -> Vec<f32> {
419 let mut state = seed;
420 (0..n).map(|_| {
421 state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
422 let f = ((state >> 33) as f32) / (u32::MAX as f32) * 3.0 - 1.5;
425 f
426 }).collect()
427 }
428
429 fn median_us(mut times: Vec<u64>) -> u64 {
430 times.sort_unstable();
431 times[times.len() / 2]
432 }
433
434 sizes.iter().map(|&n| {
435 let weights_a = lcg_weights(n * n, 0xdeadbeef);
436 let weights_b = lcg_weights(n * n, 0xc0ffee42);
437 let tau_a = bitnet_threshold(&weights_a);
438 let tau_b = bitnet_threshold(&weights_b);
439 let a = TritMatrix::from_f32(n, n, &weights_a, tau_a);
440
441 let b = TritMatrix::from_f32(n, n, &weights_b, tau_b);
442
443 let sparsity = b.sparsity();
444 let dense_ops = n * n * n;
445 let (_, skipped) = sparse_matmul(&a, &b); let sparse_ops = dense_ops - skipped;
447
448 let dense_times: Vec<u64> = (0..reps).map(|_| {
450 let t = Instant::now();
451 let _ = dense_matmul(&a, &b);
452 t.elapsed().as_micros() as u64
453 }).collect();
454
455 let sparse_times: Vec<u64> = (0..reps).map(|_| {
457 let t = Instant::now();
458 let _ = sparse_matmul(&a, &b);
459 t.elapsed().as_micros() as u64
460 }).collect();
461
462 let dense_us = median_us(dense_times);
463 let sparse_us = median_us(sparse_times);
464 let speedup = if sparse_us > 0 {
465 dense_us as f64 / sparse_us as f64
466 } else { dense_ops as f64 / sparse_ops.max(1) as f64 };
467
468 TimedResult {
469 size: n, dense_ops, sparse_ops, skipped_ops: skipped,
470 weight_sparsity: sparsity, skip_rate: skipped as f64 / dense_ops as f64,
471 speedup, dense_us, sparse_us,
472 }
473 }).collect()
474}
475
476pub fn print_benchmark_table(results: &[TimedResult]) {
478 println!("\n╔══════════════════════════════════════════════════════════════════════╗");
479 println!( "║ Ternlang Sparse Matmul Benchmark — RFI-IRFOS TIS ║");
480 println!( "╠════════╦══════════╦═══════════╦══════════╦══════════╦═════════════╣");
481 println!( "║ Size ║ Sparsity ║ Dense μs ║ Sparse μs║ Speedup ║ Skip rate ║");
482 println!( "╠════════╬══════════╬═══════════╬══════════╬══════════╬═════════════╣");
483 for r in results {
484 println!("║ {:>4}² ║ {:>5.1}% ║ {:>7} ║ {:>7} ║ {:>5.2}× ║ {:>6.1}% ║",
485 r.size,
486 r.weight_sparsity * 100.0,
487 r.dense_us,
488 r.sparse_us,
489 r.speedup,
490 r.skip_rate * 100.0,
491 );
492 }
493 println!( "╚════════╩══════════╩═══════════╩══════════╩══════════╩═════════════╝");
494}
495
496pub fn bitnet_matrix(rows: usize, cols: usize, seed: u64, target_sparsity: f64) -> TritMatrix {
502 let mut state = seed;
503 let n = rows * cols;
504 let mut data = Vec::with_capacity(n);
505 for _ in 0..n {
506 state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
507 let prob = (state >> 32) as f64 / (u32::MAX as f64 + 1.0);
508 if prob < target_sparsity {
509 data.push(Trit::Tend);
510 } else if (state & 1) == 0 {
511 data.push(Trit::Affirm);
512 } else {
513 data.push(Trit::Reject);
514 }
515 }
516 TritMatrix { rows, cols, data }
517}
518
519pub fn timed_benchmark_bitnet(sizes: &[usize], reps: usize) -> Vec<TimedResult> {
523 timed_benchmark_at_sparsity(0.60, sizes, reps)
524}
525
526pub fn timed_benchmark_at_sparsity(target_sparsity: f64, sizes: &[usize], reps: usize) -> Vec<TimedResult> {
528 use std::time::Instant;
529
530 let bitnet_sparsity: f64 = target_sparsity;
531
532 fn median_us(mut v: Vec<u64>) -> u64 {
533 v.sort_unstable();
534 v[v.len() / 2]
535 }
536
537 sizes.iter().map(|&n| {
538 let a = bitnet_matrix(n, n, 0xdeadbeef, bitnet_sparsity);
539 let b = bitnet_matrix(n, n, 0xc0ffee42, bitnet_sparsity);
540
541 let sparsity = b.sparsity();
542 let dense_ops = n * n * n;
543 let (_, skipped) = sparse_matmul(&a, &b);
544 let sparse_ops = dense_ops - skipped;
545 let speedup_ops = dense_ops as f64 / sparse_ops.max(1) as f64;
546
547 let dense_times: Vec<u64> = (0..reps).map(|_| {
548 let t = Instant::now();
549 let _ = dense_matmul(&a, &b);
550 t.elapsed().as_micros() as u64
551 }).collect();
552
553 let sparse_times: Vec<u64> = (0..reps).map(|_| {
554 let t = Instant::now();
555 let _ = sparse_matmul(&a, &b);
556 t.elapsed().as_micros() as u64
557 }).collect();
558
559 let dense_us = median_us(dense_times);
560 let sparse_us = median_us(sparse_times);
561 let speedup = if sparse_us > 0 {
562 dense_us as f64 / sparse_us as f64
563 } else { speedup_ops };
564
565 TimedResult {
566 size: n, dense_ops, sparse_ops, skipped_ops: skipped,
567 weight_sparsity: sparsity, skip_rate: skipped as f64 / dense_ops as f64,
568 speedup, dense_us, sparse_us,
569 }
570 }).collect()
571}
572
573pub fn xor_dataset() -> Vec<(TritMatrix, usize)> {
578 let inputs = vec![
579 (vec![Trit::Reject, Trit::Reject], 0usize), (vec![Trit::Reject, Trit::Affirm], 1usize), (vec![Trit::Affirm, Trit::Reject], 1usize), (vec![Trit::Affirm, Trit::Affirm], 0usize), ];
584 inputs.into_iter().map(|(row, label)| {
585 (TritMatrix::from_trits(1, 2, row), label)
586 }).collect()
587}
588
589pub fn parity_dataset() -> Vec<(TritMatrix, usize)> {
591 (0u8..8).map(|i| {
592 let bits = vec![
593 if i & 4 != 0 { Trit::Affirm } else { Trit::Reject },
594 if i & 2 != 0 { Trit::Affirm } else { Trit::Reject },
595 if i & 1 != 0 { Trit::Affirm } else { Trit::Reject },
596 ];
597 let parity = (i.count_ones() % 2) as usize;
598 (TritMatrix::from_trits(1, 3, bits), parity)
599 }).collect()
600}
601
602pub fn evaluate(mlp: &TernaryMLP, dataset: &[(TritMatrix, usize)]) -> (usize, usize, f64) {
605 let total = dataset.len();
606 let correct = dataset.iter()
607 .filter(|(input, label)| mlp.predict(input) == *label)
608 .count();
609 let accuracy = correct as f64 / total as f64;
610 (correct, total, accuracy)
611}
612
613pub const TEND_BOUNDARY: f32 = 1.0 / 3.0;
628
629#[derive(Debug, Clone)]
631pub struct TritScalar(pub f32);
632
633impl TritScalar {
634 pub fn new(v: f32) -> Self { TritScalar(v.clamp(-1.0, 1.0)) }
636
637 pub fn trit(&self) -> Trit {
639 if self.0 > TEND_BOUNDARY { Trit::Affirm }
640 else if self.0 < -TEND_BOUNDARY { Trit::Reject }
641 else { Trit::Tend }
642 }
643
644 pub fn label(&self) -> &'static str {
646 match self.trit() {
647 Trit::Affirm => "affirm",
648 Trit::Reject => "reject",
649 Trit::Tend => "tend",
650 }
651 }
652
653 pub fn confidence(&self) -> f32 {
658 let v = self.0.abs();
659 if v > TEND_BOUNDARY {
660 (v - TEND_BOUNDARY) / (1.0 - TEND_BOUNDARY)
661 } else {
662 1.0 - v / TEND_BOUNDARY
663 }
664 }
665
666 pub fn is_actionable(&self, min_confidence: f32) -> bool {
669 self.trit() != Trit::Tend && self.confidence() >= min_confidence
670 }
671
672 pub fn raw(&self) -> f32 { self.0 }
674
675 pub fn trit_i8(&self) -> i8 {
677 match self.trit() { Trit::Affirm => 1, Trit::Reject => -1, Trit::Tend => 0 }
678 }
679}
680
681pub struct TritEvidenceVec {
695 pub dimensions: Vec<String>,
696 pub values: Vec<f32>, pub weights: Vec<f32>, }
699
700impl TritEvidenceVec {
701 pub fn new(dimensions: Vec<String>, values: Vec<f32>, weights: Vec<f32>) -> Self {
702 assert_eq!(dimensions.len(), values.len(), "dimensions and values must match");
703 assert_eq!(dimensions.len(), weights.len(), "dimensions and weights must match");
704 let values = values.iter().map(|&v| v.clamp(-1.0, 1.0)).collect();
705 TritEvidenceVec { dimensions, values, weights }
706 }
707
708 pub fn aggregate(&self) -> TritScalar {
710 let total_weight: f32 = self.weights.iter().sum();
711 if total_weight == 0.0 { return TritScalar::new(0.0); }
712 let weighted_sum: f32 = self.values.iter()
713 .zip(self.weights.iter())
714 .map(|(v, w)| v * w)
715 .sum();
716 TritScalar::new(weighted_sum / total_weight)
717 }
718
719 pub fn scalars(&self) -> Vec<TritScalar> {
721 self.values.iter().map(|&v| TritScalar::new(v)).collect()
722 }
723
724 pub fn dominant(&self) -> Option<(&str, TritScalar)> {
726 self.values.iter()
727 .enumerate()
728 .max_by(|(_, a), (_, b)| a.abs().partial_cmp(&b.abs()).unwrap_or(std::cmp::Ordering::Equal))
729 .map(|(i, &v)| (self.dimensions[i].as_str(), TritScalar::new(v)))
730 }
731}
732
733#[cfg(test)]
736mod tests {
737 use super::*;
738
739 #[test]
740 fn test_quantize_basic() {
741 let weights = vec![-0.9f32, -0.2, 0.0, 0.3, 0.8];
742 let threshold = 0.5;
743 let trits = quantize(&weights, threshold);
744 assert_eq!(trits, vec![Trit::Reject, Trit::Tend, Trit::Tend, Trit::Tend, Trit::Affirm]);
745 }
746
747 #[test]
748 fn test_bitnet_threshold() {
749 let weights = vec![1.0f32, -1.0, 0.5, -0.5];
750 let tau = bitnet_threshold(&weights);
751 assert!((tau - 0.375).abs() < 1e-6);
753 }
754 #[test]
755 fn test_dense_matmul_identity() {
756 let mut id = TritMatrix::new(2, 2);
758 id.set(0, 0, Trit::Affirm);
759 id.set(1, 1, Trit::Affirm);
760
761 let result = dense_matmul(&id, &id);
762 assert_eq!(result.get(0, 0), Trit::Affirm);
763 assert_eq!(result.get(0, 1), Trit::Tend);
764 assert_eq!(result.get(1, 0), Trit::Tend);
765 assert_eq!(result.get(1, 1), Trit::Affirm);
766 }
767
768 #[test]
769 fn test_sparse_matmul_matches_dense() {
770 let weights = vec![0.9f32, -0.1, 0.05, -0.8, 0.0, 0.7, -0.6, 0.2, 0.0];
772 let threshold = 0.5;
773 let w = TritMatrix::from_f32(3, 3, &weights, threshold);
774 let mut input = TritMatrix::new(3, 3);
775 input.set(0, 0, Trit::Affirm);
776 input.set(1, 1, Trit::Reject);
777 input.set(2, 2, Trit::Affirm);
778
779 let dense = dense_matmul(&input, &w);
780 let (sparse, skipped) = sparse_matmul(&input, &w);
781
782 for r in 0..3 {
784 for c in 0..3 {
785 assert_eq!(dense.get(r, c), sparse.get(r, c),
786 "mismatch at ({}, {})", r, c);
787 }
788 }
789 assert!(skipped > 0, "expected skips for a sparse weight matrix");
791 }
792
793 #[test]
794 fn test_sparsity_measurement() {
795 let weights = vec![0.9f32, 0.1, -0.9]; let threshold = 0.5;
797 let m = TritMatrix::from_f32(1, 3, &weights, threshold);
798 assert!((m.sparsity() - 1.0/3.0).abs() < 1e-9);
800 assert_eq!(m.nnz(), 2);
801 }
802
803 #[test]
804 fn test_majority_vote() {
805 assert_eq!(majority(&[Trit::Affirm, Trit::Affirm, Trit::Reject]), Trit::Affirm);
806 assert_eq!(majority(&[Trit::Reject, Trit::Reject, Trit::Affirm]), Trit::Reject);
807 assert_eq!(majority(&[Trit::Affirm, Trit::Reject]), Trit::Tend);
808 assert_eq!(majority(&[Trit::Tend, Trit::Tend]), Trit::Tend);
809 }
810
811 #[test]
812 fn test_mlp_forward_runs() {
813 let w1_f32: Vec<f32> = vec![
815 0.9, -0.8, 0.7, -0.6,
816 -0.7, 0.9, -0.5, 0.8,
817 ];
818 let w2_f32: Vec<f32> = vec![
819 0.9, -0.9,
820 -0.8, 0.8,
821 0.7, -0.7,
822 -0.6, 0.6,
823 ];
824 let mlp = TernaryMLP::from_f32(2, 4, 2, &w1_f32, &w2_f32);
825 let input = TritMatrix::from_trits(1, 2, vec![Trit::Affirm, Trit::Reject]);
826 let (out, s1, s2) = mlp.forward(&input);
827 assert_eq!(out.rows, 1);
828 assert_eq!(out.cols, 2);
829 let _ = (s1, s2);
831 }
832
833 #[test]
834 fn test_mlp_predict_returns_valid_class() {
835 let w1_f32: Vec<f32> = vec![0.9, -0.8, -0.7, 0.9];
836 let w2_f32: Vec<f32> = vec![0.9, -0.9, -0.8, 0.8];
837 let mlp = TernaryMLP::from_f32(2, 2, 2, &w1_f32, &w2_f32);
838 let input = TritMatrix::from_trits(1, 2, vec![Trit::Affirm, Trit::Reject]);
839 let pred = mlp.predict(&input);
840 assert!(pred < 2, "prediction must be a valid class index");
841 }
842
843 #[test]
844 fn test_xor_dataset_shape() {
845 let ds = xor_dataset();
846 assert_eq!(ds.len(), 4);
847 for (input, label) in &ds {
848 assert_eq!(input.rows, 1);
849 assert_eq!(input.cols, 2);
850 assert!(*label < 2);
851 }
852 }
853
854 #[test]
855 fn test_parity_dataset_shape() {
856 let ds = parity_dataset();
857 assert_eq!(ds.len(), 8);
858 for (input, label) in &ds {
859 assert_eq!(input.cols, 3);
860 assert!(*label < 2);
861 }
862 }
863
864 #[test]
865 fn test_xor_mlp_with_known_weights() {
866 let w1_f32 = vec![
872 1.0, -1.0,
873 -1.0, 1.0,
874 ];
875 let w2_f32 = vec![
878 -1.0, 1.0,
879 -1.0, 1.0,
880 ];
881 let mlp = TernaryMLP::from_f32(2, 2, 2, &w1_f32, &w2_f32);
882 let ds = xor_dataset();
883 let (correct, total, acc) = evaluate(&mlp, &ds);
884 println!("XOR MLP: {}/{} = {:.0}%", correct, total, acc * 100.0);
885 assert!(correct >= 2, "MLP should get at least half of XOR correct");
888 }
889
890 #[test]
891 fn test_timed_benchmark_small() {
892 let results = timed_benchmark(&[8, 16], 3);
893 assert_eq!(results.len(), 2);
894 for r in &results {
895 assert!(r.dense_ops > 0);
896 assert!(r.weight_sparsity >= 0.0 && r.weight_sparsity <= 1.0);
897 assert!(r.skip_rate >= 0.0 && r.skip_rate <= 1.0);
898 }
899 print_benchmark_table(&results);
900 }
901
902 #[test]
903 fn test_benchmark_reports_skips() {
904 let weights: Vec<f32> = vec![
906 0.9, 0.1, -0.9, 0.0,
907 0.1, 0.8, 0.0, -0.7,
908 0.0, 0.1, 0.6, 0.2,
909 -0.8, 0.0, 0.1, 0.9,
910 ];
911 let threshold = 0.5;
912 let w = TritMatrix::from_f32(4, 4, &weights, threshold);
913 let input = TritMatrix::new(4, 4); let result = benchmark(&input, &w);
915 assert!(result.skipped_ops > 0);
916 assert!(result.skip_rate > 0.0 && result.skip_rate <= 1.0);
917 result.print_summary();
918 }
919
920 #[test]
921 fn test_full_benchmark() {
922 let results = timed_benchmark(&[32, 64, 128, 256, 512], 5);
923 assert_eq!(results.len(), 5);
924 print_benchmark_table(&results);
925 }
926
927 #[test]
930 fn test_bitnet_benchmark() {
931 let results = timed_benchmark_bitnet(&[32, 64, 128, 256, 512], 5);
932 assert_eq!(results.len(), 5);
933 println!("\n╔══════════════════════════════════════════════════════════════════════╗");
934 println!( "║ BitNet b1.58 Realistic Benchmark — 60% Sparsity — RFI-IRFOS TIS ║");
935 println!( "╠════════╦══════════╦═══════════╦══════════╦══════════╦═════════════╣");
936 println!( "║ Size ║ Sparsity ║ Dense μs ║ Sparse μs║ Speedup ║ Skip rate ║");
937 println!( "╠════════╬══════════╬═══════════╬══════════╬══════════╬═════════════╣");
938 for r in &results {
939 println!("║ {:>4}² ║ {:>5.1}% ║ {:>7} ║ {:>7} ║ {:>5.2}× ║ {:>6.1}% ║",
940 r.size,
941 r.weight_sparsity * 100.0,
942 r.dense_us,
943 r.sparse_us,
944 r.speedup,
945 r.skip_rate * 100.0,
946 );
947 }
948 println!( "╚════════╩══════════╩═══════════╩══════════╩══════════╩═════════════╝");
949 for r in &results {
950 assert!(r.skip_rate >= 0.50, "Expected ≥50% skip rate at 60% sparsity, got {:.1}%", r.skip_rate * 100.0);
951 }
952 }
953
954 #[test]
956 fn test_extreme_sparsity_99() {
957 let results = timed_benchmark_at_sparsity(0.99, &[32, 64, 128, 256, 512], 5);
958 assert_eq!(results.len(), 5);
959 println!("\n╔══════════════════════════════════════════════════════════════════════╗");
960 println!( "║ EXTREME SPARSITY — 99% Zeros — What Happens? ║");
961 println!( "╠════════╦══════════╦═══════════╦══════════╦══════════╦═════════════╣");
962 println!( "║ Size ║ Sparsity ║ Dense μs ║ Sparse μs║ Speedup ║ Skip rate ║");
963 println!( "╠════════╬══════════╬═══════════╬══════════╬══════════╬═════════════╣");
964 for r in &results {
965 println!("║ {:>4}² ║ {:>5.1}% ║ {:>7} ║ {:>7} ║ {:>6.1}× ║ {:>6.1}% ║",
966 r.size,
967 r.weight_sparsity * 100.0,
968 r.dense_us,
969 r.sparse_us,
970 r.speedup,
971 r.skip_rate * 100.0,
972 );
973 }
974 println!( "╚════════╩══════════╩═══════════╩══════════╩══════════╩═════════════╝");
975 for r in &results {
976 assert!(r.skip_rate >= 0.95, "Expected ≥95% skip rate at 99% sparsity");
977 }
978 }
979
980 #[test]
983 fn test_sparsity_sweep() {
984 let sparsities: &[f64] = &[0.25, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 0.95, 0.99];
985 let sizes: &[usize] = &[32, 64, 128, 256, 512];
986
987 let mut grid: Vec<Vec<f64>> = Vec::new();
989 for &sp in sparsities {
990 let row: Vec<f64> = timed_benchmark_at_sparsity(sp, sizes, 3)
991 .into_iter().map(|r| r.speedup).collect();
992 grid.push(row);
993 }
994
995 println!();
997 println!("╔══════════════ SPARSITY GOLDILOCKS SWEEP ══════════════════════════╗");
998 println!("║ Speedup (sparse / dense) across sparsity × matrix size ║");
999 println!("╠══════════╦═══════╦═══════╦════════╦════════╦════════╣");
1000 print!( "║ Sparsity ║");
1001 for &n in sizes { print!(" {:>4}² ║", n); }
1002 println!();
1003 println!("╠══════════╬═══════╬═══════╬════════╬════════╬════════╣");
1004
1005 let mut peak_speedup = 0f64;
1006 let mut peak_sp = 0f64;
1007 let mut peak_n = 0usize;
1008
1009 for (i, &sp) in sparsities.iter().enumerate() {
1010 print!("║ {:>5.1}% ║", sp * 100.0);
1011 for (j, &speedup) in grid[i].iter().enumerate() {
1012 if speedup > peak_speedup {
1013 peak_speedup = speedup;
1014 peak_sp = sp;
1015 peak_n = sizes[j];
1016 }
1017 print!(" {:>5.1}× ║", speedup);
1018 }
1019 println!();
1020 }
1021
1022 println!("╚══════════╩═══════╩═══════╩════════╩════════╩════════╝");
1023 println!();
1024 println!(" ★ Peak: {:.1}× at {:.0}% sparsity, {}×{} matrix", peak_speedup, peak_sp * 100.0, peak_n, peak_n);
1025
1026 let avg_speedups: Vec<(f64, f64)> = sparsities.iter().zip(grid.iter())
1028 .map(|(&sp, row)| (sp, row.iter().sum::<f64>() / row.len() as f64))
1029 .collect();
1030 let (best_sp, best_avg) = avg_speedups.iter()
1031 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
1032 .copied().unwrap();
1033 println!(" ◆ Goldilocks zone: {:.0}% sparsity → {:.1}× average across all sizes", best_sp * 100.0, best_avg);
1034 println!();
1035
1036 for row in &grid {
1039 for &s in &row[1..] { assert!(s >= 1.0, "Speedup dropped below 1× — something is wrong");
1041 }
1042 }
1043 }
1044
1045 #[test]
1048 fn test_trit_scalar_zones() {
1049 assert_eq!(TritScalar::new(0.9).label(), "affirm");
1050 assert_eq!(TritScalar::new(-0.9).label(), "reject");
1051 assert_eq!(TritScalar::new(0.0).label(), "tend");
1052 assert_eq!(TritScalar::new(0.33).label(), "tend"); assert_eq!(TritScalar::new(0.34).label(), "affirm"); }
1055
1056 #[test]
1057 fn test_trit_scalar_confidence() {
1058 let s = TritScalar::new(0.0);
1060 assert_eq!(s.label(), "tend");
1061 assert!((s.confidence() - 1.0).abs() < 0.01);
1062
1063 let s = TritScalar::new(1.0);
1065 assert_eq!(s.label(), "affirm");
1066 assert!((s.confidence() - 1.0).abs() < 0.01);
1067
1068 let s = TritScalar::new(TEND_BOUNDARY + 0.001);
1070 assert_eq!(s.label(), "affirm");
1071 assert!(s.confidence() < 0.01);
1072 }
1073
1074 #[test]
1075 fn test_trit_scalar_actionable() {
1076 assert!(TritScalar::new(0.9).is_actionable(0.5));
1078 assert!(!TritScalar::new(0.35).is_actionable(0.8));
1080 assert!(!TritScalar::new(0.0).is_actionable(0.0));
1082 }
1083
1084 #[test]
1085 fn test_trit_scalar_clamp() {
1086 assert!((TritScalar::new(5.0).raw() - 1.0).abs() < 0.001);
1087 assert!((TritScalar::new(-5.0).raw() + 1.0).abs() < 0.001);
1088 }
1089
1090 #[test]
1093 fn test_evidence_vec_aggregate_uniform() {
1094 let ev = TritEvidenceVec::new(
1096 vec!["a".into(), "b".into(), "c".into()],
1097 vec![0.8, 0.9, 0.7],
1098 vec![1.0, 1.0, 1.0],
1099 );
1100 let agg = ev.aggregate();
1101 assert_eq!(agg.label(), "affirm");
1102 assert!(agg.confidence() > 0.5);
1103 }
1104
1105 #[test]
1106 fn test_evidence_vec_mixed_signals() {
1107 let ev = TritEvidenceVec::new(
1109 vec!["strong_reject".into(), "weak_affirm".into()],
1110 vec![-0.9, 0.1],
1111 vec![1.0, 1.0],
1112 );
1113 let agg = ev.aggregate();
1114 assert_eq!(agg.label(), "reject");
1116 }
1117
1118 #[test]
1119 fn test_evidence_vec_weighted_override() {
1120 let ev = TritEvidenceVec::new(
1122 vec!["weak_reject".into(), "strong_affirm".into()],
1123 vec![-0.4, 0.9],
1124 vec![10.0, 1.0], );
1126 let agg = ev.aggregate();
1127 assert_eq!(agg.label(), "tend");
1129 }
1130
1131 #[test]
1132 fn test_evidence_vec_dominant() {
1133 let ev = TritEvidenceVec::new(
1134 vec!["low".into(), "high".into(), "mid".into()],
1135 vec![0.2, -0.95, 0.5],
1136 vec![1.0, 1.0, 1.0],
1137 );
1138 let (label, scalar) = ev.dominant().unwrap();
1139 assert_eq!(label, "high");
1140 assert_eq!(scalar.label(), "reject");
1141 }
1142}
1143
1144#[derive(Debug, Clone)]
1162pub struct DeliberationRound {
1163 pub round: usize,
1164 pub new_evidence: Vec<f32>, pub cumulative_mean: f32, pub scalar: TritScalar,
1167 pub converged: bool, }
1169
1170#[derive(Debug, Clone)]
1172pub struct DeliberationResult {
1173 pub final_trit: i8,
1174 pub final_label: String,
1175 pub final_confidence: f32,
1176 pub converged: bool,
1177 pub rounds_used: usize,
1178 pub trace: Vec<DeliberationRound>,
1179 pub convergence_reason: String,
1180}
1181
1182pub struct DeliberationEngine {
1191 pub target_confidence: f32,
1193 pub max_rounds: usize,
1195 pub alpha: f32,
1197}
1198
1199impl DeliberationEngine {
1200 pub fn new(target_confidence: f32, max_rounds: usize) -> Self {
1201 Self { target_confidence, max_rounds, alpha: 0.4 }
1202 }
1203
1204 pub fn with_alpha(mut self, alpha: f32) -> Self { self.alpha = alpha.clamp(0.01, 1.0); self }
1205
1206 pub fn run(&self, rounds_evidence: Vec<Vec<f32>>) -> DeliberationResult {
1209 let mut ema: f32 = 0.0; let mut initialized = false;
1211 let mut trace = Vec::new();
1212
1213 let rounds_to_run = self.max_rounds.min(
1214 if rounds_evidence.is_empty() { self.max_rounds } else { rounds_evidence.len() }
1215 );
1216
1217 for round in 0..rounds_to_run {
1218 let new_ev: Vec<f32> = rounds_evidence.get(round).cloned().unwrap_or_default();
1219
1220 if !new_ev.is_empty() {
1222 let round_mean = new_ev.iter().sum::<f32>() / new_ev.len() as f32;
1223 ema = if !initialized {
1224 initialized = true;
1225 round_mean
1226 } else {
1227 self.alpha * round_mean + (1.0 - self.alpha) * ema
1228 };
1229 }
1230
1231 let scalar = TritScalar::new(ema);
1232 let converged = scalar.confidence() >= self.target_confidence;
1233
1234 trace.push(DeliberationRound {
1235 round,
1236 new_evidence: new_ev,
1237 cumulative_mean: ema,
1238 scalar: scalar.clone(),
1239 converged,
1240 });
1241
1242 if converged { break; }
1243 }
1244
1245 let last = trace.last().cloned().unwrap_or_else(|| DeliberationRound {
1246 round: 0, new_evidence: vec![], cumulative_mean: 0.0,
1247 scalar: TritScalar::new(0.0), converged: false,
1248 });
1249
1250 let convergence_reason = if last.converged {
1251 format!("confidence {:.1}% ≥ target {:.1}% after {} round(s)",
1252 last.scalar.confidence() * 100.0,
1253 self.target_confidence * 100.0,
1254 last.round + 1)
1255 } else {
1256 format!("max rounds ({}) reached — confidence {:.1}% below target {:.1}%",
1257 self.max_rounds,
1258 last.scalar.confidence() * 100.0,
1259 self.target_confidence * 100.0)
1260 };
1261
1262 DeliberationResult {
1263 final_trit: last.scalar.trit_i8(),
1264 final_label: last.scalar.label().to_string(),
1265 final_confidence: last.scalar.confidence(),
1266 converged: last.converged,
1267 rounds_used: last.round + 1,
1268 trace,
1269 convergence_reason,
1270 }
1271 }
1272}
1273
1274#[derive(Debug, Clone)]
1278pub struct CoalitionMember {
1279 pub label: String,
1280 pub trit: i8, pub confidence: f32, pub weight: f32, }
1284
1285impl CoalitionMember {
1286 pub fn new(label: impl Into<String>, trit: i8, confidence: f32, weight: f32) -> Self {
1287 Self {
1288 label: label.into(),
1289 trit: trit.clamp(-1, 1),
1290 confidence: confidence.clamp(0.0, 1.0),
1291 weight: weight.max(0.0),
1292 }
1293 }
1294}
1295
1296#[derive(Debug, Clone)]
1298pub struct CoalitionResult {
1299 pub trit: i8,
1300 pub label: String,
1301 pub aggregate_score: f32, pub quorum: f32, pub dissent_rate: f32, pub abstain_rate: f32, pub member_count: usize,
1306 pub effective_weight: f32, pub breakdown: Vec<(String, i8, f32)>, }
1309
1310pub fn coalition_vote(members: &[CoalitionMember]) -> CoalitionResult {
1315 if members.is_empty() {
1316 return CoalitionResult {
1317 trit: 0, label: "tend".into(), aggregate_score: 0.0,
1318 quorum: 0.0, dissent_rate: 0.0, abstain_rate: 1.0,
1319 member_count: 0, effective_weight: 0.0, breakdown: vec![],
1320 };
1321 }
1322
1323 let total_weight: f32 = members.iter().map(|m| m.weight).sum();
1324 let total_weight = if total_weight == 0.0 { 1.0 } else { total_weight };
1325
1326 let mut weighted_sum: f32 = 0.0;
1327 let mut non_zero_weight: f32 = 0.0;
1328 let mut breakdown = Vec::new();
1329
1330 for m in members {
1331 let contribution = (m.trit as f32) * m.confidence * m.weight;
1332 weighted_sum += contribution;
1333 if m.trit != 0 { non_zero_weight += m.weight; }
1334 breakdown.push((m.label.clone(), m.trit, contribution / total_weight));
1335 }
1336
1337 let aggregate_score = weighted_sum / total_weight;
1338 let scalar = TritScalar::new(aggregate_score);
1339 let result_trit: i8 = scalar.trit_i8();
1340
1341 let quorum = non_zero_weight / total_weight;
1342 let abstain_rate = 1.0 - quorum;
1343 let dissent_rate = members.iter()
1344 .filter(|m| m.trit != 0 && m.trit.signum() != result_trit.signum())
1345 .map(|m| m.weight)
1346 .sum::<f32>() / total_weight;
1347
1348 CoalitionResult {
1349 trit: result_trit,
1350 label: scalar.label().to_string(),
1351 aggregate_score,
1352 quorum,
1353 dissent_rate,
1354 abstain_rate,
1355 member_count: members.len(),
1356 effective_weight: non_zero_weight,
1357 breakdown,
1358 }
1359}
1360
1361#[derive(Debug, Clone)]
1365pub struct GateDimension {
1366 pub name: String,
1367 pub evidence: f32, pub weight: f32, pub hard_block: bool,
1372}
1373
1374impl GateDimension {
1375 pub fn new(name: impl Into<String>, evidence: f32, weight: f32) -> Self {
1376 Self { name: name.into(), evidence, weight, hard_block: false }
1377 }
1378 pub fn hard(mut self) -> Self { self.hard_block = true; self }
1379}
1380
1381#[derive(Debug, Clone, PartialEq, Eq)]
1383pub enum GateVerdict {
1384 Proceed,
1386 Hold,
1388 Block,
1390}
1391
1392impl GateVerdict {
1393 pub fn label(&self) -> &'static str {
1394 match self {
1395 GateVerdict::Proceed => "proceed",
1396 GateVerdict::Hold => "hold",
1397 GateVerdict::Block => "block",
1398 }
1399 }
1400}
1401
1402#[derive(Debug, Clone)]
1404pub struct GateResult {
1405 pub verdict: GateVerdict,
1406 pub aggregate: TritScalar,
1407 pub hard_blocked_by: Vec<String>, pub dim_results: Vec<(String, TritScalar, bool)>, pub explanation: String,
1410}
1411
1412pub fn action_gate(dimensions: &[GateDimension]) -> GateResult {
1419 let mut hard_blocked_by = Vec::new();
1420 let mut dim_results = Vec::new();
1421 let mut weighted_sum = 0.0f32;
1422 let mut total_weight = 0.0f32;
1423
1424 for dim in dimensions {
1425 let scalar = TritScalar::new(dim.evidence);
1426 let is_neg = matches!(scalar.trit(), Trit::Reject);
1427
1428 if dim.hard_block && is_neg {
1429 hard_blocked_by.push(dim.name.clone());
1430 }
1431
1432 weighted_sum += dim.evidence * dim.weight;
1433 total_weight += dim.weight;
1434 dim_results.push((dim.name.clone(), scalar, dim.hard_block));
1435 }
1436
1437 if !hard_blocked_by.is_empty() {
1439 let explanation = format!(
1440 "BLOCKED — hard constraint(s) violated: {}",
1441 hard_blocked_by.join(", ")
1442 );
1443 return GateResult {
1444 verdict: GateVerdict::Block,
1445 aggregate: TritScalar::new(-1.0),
1446 hard_blocked_by,
1447 dim_results,
1448 explanation,
1449 };
1450 }
1451
1452 let agg_score = if total_weight > 0.0 { weighted_sum / total_weight } else { 0.0 };
1453 let aggregate = TritScalar::new(agg_score);
1454
1455 let verdict = match aggregate.trit() {
1456 Trit::Affirm => GateVerdict::Proceed,
1457 Trit::Tend => GateVerdict::Hold,
1458 Trit::Reject => GateVerdict::Block,
1459 };
1460
1461 let explanation = match &verdict {
1462 GateVerdict::Proceed => format!(
1463 "PROCEED — all dimensions pass (aggregate confidence {:.0}%)",
1464 aggregate.confidence() * 100.0
1465 ),
1466 GateVerdict::Hold => format!(
1467 "HOLD — insufficient evidence (aggregate {:.3} within deliberation zone)",
1468 aggregate.raw()
1469 ),
1470 GateVerdict::Block => format!(
1471 "BLOCK — weighted aggregate {:.3} below threshold (confidence {:.0}%)",
1472 aggregate.raw(), aggregate.confidence() * 100.0
1473 ),
1474 };
1475
1476 GateResult { verdict, aggregate, hard_blocked_by, dim_results, explanation }
1477}
1478
1479#[derive(Debug, Clone)]
1494pub struct ScalarTemperature {
1495 pub trit: i8,
1496 pub confidence: f32,
1497 pub temperature: f32,
1498 pub reasoning: String,
1499 pub prompt_hint: String,
1501}
1502
1503pub fn scalar_temperature(scalar: &TritScalar) -> ScalarTemperature {
1504 let t = scalar.trit();
1505 let c = scalar.confidence(); let (temp, reasoning, prompt_hint) = match t {
1508 Trit::Affirm => {
1509 let temp = 0.3 - (c * 0.25); (
1512 temp.max(0.05),
1513 format!("Affirm (confidence {:.0}%) — execute precisely, minimal exploration", c * 100.0),
1514 "Be concise and direct. Evidence is clear. Do not hedge.".to_string(),
1515 )
1516 }
1517 Trit::Reject => {
1518 let temp = 0.15 - (c * 0.10); (
1521 temp.max(0.05),
1522 format!("Reject (confidence {:.0}%) — decline firmly, minimal hedging", c * 100.0),
1523 "Decline clearly. Do not offer alternatives unless explicitly asked. Evidence is against.".to_string(),
1524 )
1525 }
1526 Trit::Tend => {
1527 let temp = 0.7 + ((1.0 - c) * 0.3); (
1530 temp.min(1.0),
1531 format!("Tend (confidence {:.0}%) — evidence is conflicted, explore broadly", c * 100.0),
1532 "You are in deliberation. Present multiple perspectives. Ask clarifying questions. Do not commit.".to_string(),
1533 )
1534 }
1535 };
1536
1537 ScalarTemperature {
1538 trit: scalar.trit_i8(),
1539 confidence: c,
1540 temperature: (temp * 1000.0).round() / 1000.0,
1541 reasoning,
1542 prompt_hint,
1543 }
1544}
1545
1546#[derive(Debug, Clone)]
1558pub struct HallucinationScore {
1559 pub trust_trit: i8,
1560 pub trust_label: String,
1561 pub mean: f32, pub variance: f32, pub consistency: f32, pub signal_count: usize,
1565 pub explanation: String,
1566}
1567
1568pub fn hallucination_score(signals: &[f32]) -> HallucinationScore {
1569 if signals.is_empty() {
1570 return HallucinationScore {
1571 trust_trit: 0, trust_label: "tend".into(), mean: 0.0,
1572 variance: 0.0, consistency: 0.0, signal_count: 0,
1573 explanation: "No signals provided — cannot assess consistency.".into(),
1574 };
1575 }
1576
1577 let n = signals.len() as f32;
1578 let mean = signals.iter().sum::<f32>() / n;
1579 let variance = signals.iter().map(|&s| (s - mean).powi(2)).sum::<f32>() / n;
1580
1581 let norm_variance = variance.min(1.0);
1583 let consistency = 1.0 - norm_variance;
1584
1585 let trust_evidence = (consistency * 2.0 - 1.0) * mean.abs(); let trust = TritScalar::new(trust_evidence);
1590
1591 let explanation = if trust.trit() == Trit::Affirm {
1592 format!(
1593 "Consistent signals (variance {:.3}, consistency {:.0}%) — evidence coheres around {:.3}",
1594 variance, consistency * 100.0, mean
1595 )
1596 } else if trust.trit() == Trit::Reject {
1597 format!(
1598 "HIGH VARIANCE (variance {:.3}) — signals are internally contradictory. Possible hallucination or conflated sources.",
1599 variance
1600 )
1601 } else {
1602 format!(
1603 "Mixed consistency (variance {:.3}, mean {:.3}) — gather more evidence before relying on this claim.",
1604 variance, mean
1605 )
1606 };
1607
1608 HallucinationScore {
1609 trust_trit: trust.trit_i8(),
1610 trust_label: trust.label().to_string(),
1611 mean,
1612 variance,
1613 consistency,
1614 signal_count: signals.len(),
1615 explanation,
1616 }
1617}
1618
1619#[cfg(test)]
1622mod reasoning_tests {
1623 use super::*;
1624
1625 #[test]
1628 fn test_deliberation_converges_on_strong_evidence() {
1629 let engine = DeliberationEngine::new(0.7, 10).with_alpha(0.7);
1631 let rounds = vec![
1632 vec![0.85, 0.9], vec![0.9, 0.95], vec![0.92, 0.95, 0.98], ];
1636 let result = engine.run(rounds);
1637 assert!(result.converged, "should converge on strong positive evidence (got confidence {:.2})", result.final_confidence);
1638 assert_eq!(result.final_trit, 1, "should be +1 (affirm)");
1639 assert!(result.rounds_used <= 3);
1640 }
1641
1642 #[test]
1643 fn test_deliberation_holds_on_weak_evidence() {
1644 let engine = DeliberationEngine::new(0.95, 3);
1645 let rounds = vec![
1646 vec![0.1f32],
1647 vec![-0.05],
1648 vec![0.15],
1649 ];
1650 let result = engine.run(rounds);
1651 assert!(!result.converged, "should not converge on weak conflicting evidence");
1652 assert_eq!(result.final_trit, 0, "should stay at hold/tend");
1653 assert_eq!(result.rounds_used, 3);
1654 }
1655
1656 #[test]
1657 fn test_deliberation_negative_convergence() {
1658 let engine = DeliberationEngine::new(0.8, 10);
1659 let rounds = vec![
1660 vec![-0.9f32, -0.85],
1661 vec![-0.95, -0.99],
1662 ];
1663 let result = engine.run(rounds);
1664 assert!(result.converged);
1665 assert_eq!(result.final_trit, -1);
1666 }
1667
1668 #[test]
1671 fn test_coalition_unanimous_affirm() {
1672 let members = vec![
1673 CoalitionMember::new("safety", 1, 0.9, 3.0),
1674 CoalitionMember::new("utility", 1, 0.8, 1.0),
1675 CoalitionMember::new("alignment", 1, 0.95, 2.0),
1676 ];
1677 let result = coalition_vote(&members);
1678 assert_eq!(result.trit, 1);
1679 assert_eq!(result.label, "affirm");
1680 assert!(result.quorum > 0.99, "all voted");
1681 assert!(result.dissent_rate < 0.01);
1682 }
1683
1684 #[test]
1685 fn test_coalition_split_vote_tends_to_hold() {
1686 let members = vec![
1687 CoalitionMember::new("agent_a", 1, 0.8, 1.0),
1688 CoalitionMember::new("agent_b", -1, 0.8, 1.0),
1689 CoalitionMember::new("agent_c", 0, 0.5, 1.0),
1690 ];
1691 let result = coalition_vote(&members);
1692 assert_eq!(result.trit, 0);
1694 assert!(result.dissent_rate > 0.0, "there is dissent");
1695 }
1696
1697 #[test]
1698 fn test_coalition_high_weight_overrides() {
1699 let members = vec![
1700 CoalitionMember::new("expert", 1, 0.95, 10.0), CoalitionMember::new("novice_a", -1, 0.5, 1.0),
1702 CoalitionMember::new("novice_b", -1, 0.5, 1.0),
1703 ];
1704 let result = coalition_vote(&members);
1705 assert_eq!(result.trit, 1, "high-weight expert should dominate");
1707 }
1708
1709 #[test]
1712 fn test_gate_all_positive_proceeds() {
1713 let dims = vec![
1714 GateDimension::new("safety", 0.8, 3.0),
1715 GateDimension::new("utility", 0.7, 1.0),
1716 GateDimension::new("legality", 0.9, 2.0),
1717 ];
1718 let result = action_gate(&dims);
1719 assert_eq!(result.verdict, GateVerdict::Proceed);
1720 }
1721
1722 #[test]
1723 fn test_gate_hard_block_fires() {
1724 let dims = vec![
1725 GateDimension::new("utility", 0.9, 1.0),
1726 GateDimension::new("safety", -0.8, 3.0).hard(), GateDimension::new("legality", 0.7, 1.0),
1728 ];
1729 let result = action_gate(&dims);
1730 assert_eq!(result.verdict, GateVerdict::Block);
1731 assert!(result.hard_blocked_by.contains(&"safety".to_string()));
1732 }
1733
1734 #[test]
1735 fn test_gate_mixed_soft_dims_holds() {
1736 let dims = vec![
1737 GateDimension::new("utility", 0.8, 1.0),
1738 GateDimension::new("risk", -0.7, 1.0), ];
1740 let result = action_gate(&dims);
1742 assert_ne!(result.verdict, GateVerdict::Block); }
1745
1746 #[test]
1749 fn test_temperature_affirm_is_low() {
1750 let sc = TritScalar::new(0.9);
1751 let temp = scalar_temperature(&sc);
1752 assert_eq!(temp.trit, 1);
1753 assert!(temp.temperature < 0.3, "affirm → low temperature");
1754 }
1755
1756 #[test]
1757 fn test_temperature_tend_is_high() {
1758 let sc = TritScalar::new(0.05); let temp = scalar_temperature(&sc);
1760 assert_eq!(temp.trit, 0);
1761 assert!(temp.temperature >= 0.7, "tend → high temperature for exploration");
1762 }
1763
1764 #[test]
1765 fn test_temperature_reject_is_low() {
1766 let sc = TritScalar::new(-0.9);
1767 let temp = scalar_temperature(&sc);
1768 assert_eq!(temp.trit, -1);
1769 assert!(temp.temperature < 0.15, "reject → low temperature, firm");
1770 }
1771
1772 #[test]
1775 fn test_hallucination_consistent_signals_trusted() {
1776 let signals = vec![0.8, 0.82, 0.79, 0.81, 0.83];
1778 let score = hallucination_score(&signals);
1779 assert_eq!(score.trust_trit, 1, "consistent signals should be trusted");
1780 assert!(score.variance < 0.01);
1781 assert!(score.consistency > 0.99);
1782 }
1783
1784 #[test]
1785 fn test_hallucination_chaotic_signals_flagged() {
1786 let signals = vec![0.9, -0.9, 0.8, -0.8, 0.95, -0.7];
1788 let score = hallucination_score(&signals);
1789 assert!(score.variance > 0.5, "should have high variance");
1791 assert!(score.trust_trit <= 0, "chaotic signals should not be trusted");
1792 }
1793
1794 #[test]
1795 fn test_hallucination_empty_returns_hold() {
1796 let score = hallucination_score(&[]);
1797 assert_eq!(score.trust_trit, 0);
1798 assert_eq!(score.signal_count, 0);
1799 }
1800}
1801
1802use std::collections::HashMap;
1817use crate::coherence::ModelCoherence;
1818
1819pub struct TritTransformerConfig {
1820 pub dim: usize,
1821 pub n_layers: usize,
1822 pub n_heads: usize,
1823 pub n_kv_heads: usize,
1824 pub vocab_size: usize,
1825 pub multiple_of: usize,
1826 pub ffn_dim_multiplier: Option<f64>,
1827 pub norm_eps: f32,
1828 pub max_seq_len: usize,
1829}
1830
1831impl Default for TritTransformerConfig {
1832 fn default() -> Self {
1833 Self {
1834 dim: 2048,
1835 n_layers: 16,
1836 n_heads: 32,
1837 n_kv_heads: 8,
1838 vocab_size: 128256, multiple_of: 256,
1840 ffn_dim_multiplier: None,
1841 norm_eps: 1e-5,
1842 max_seq_len: 2048,
1843 }
1844 }
1845}
1846
1847pub struct TritBlock {
1849 pub wq: TritMatrix,
1850 pub wk: TritMatrix,
1851 pub wv: TritMatrix,
1852 pub wo: TritMatrix,
1853 pub w1: TritMatrix,
1854 pub w2: TritMatrix,
1855 pub w3: TritMatrix,
1856 pub attention_norm: Vec<f32>, pub ffn_norm: Vec<f32>,
1858}
1859
1860pub struct TritTransformer {
1862 pub config: TritTransformerConfig,
1863 pub tok_embeddings: TritMatrix,
1864 pub layers: Vec<TritBlock>,
1865 pub norm: Vec<f32>,
1866 pub output: TritMatrix,
1867 pub freq_cis: Vec<(f32, f32)>, }
1869
1870impl TritTransformer {
1871 pub fn from_coherence(coherence: ModelCoherence, config: TritTransformerConfig) -> Self {
1873 println!("ternlang-ml: Building TritTransformer (Layers: {})...", config.n_layers);
1874
1875 let mut layers = Vec::with_capacity(config.n_layers);
1876 let mut layer_map: HashMap<String, TritMatrix> = HashMap::new();
1877
1878 for layer in coherence.layers {
1879 layer_map.insert(layer.name.clone(), layer.to_trit_matrix());
1880 }
1881
1882 let mut get = |name: &str| {
1884 layer_map.remove(name).unwrap_or_else(|| panic!("Missing layer: {}", name))
1885 };
1886
1887 let tok_embeddings = get("token_embd.weight");
1888 let output = get("output.weight");
1889
1890 let norm = vec![1.0; config.dim];
1895
1896 for i in 0..config.n_layers {
1897 layers.push(TritBlock {
1898 wq: get(&format!("layers.{}.attention.wq.weight", i)),
1899 wk: get(&format!("layers.{}.attention.wk.weight", i)),
1900 wv: get(&format!("layers.{}.attention.wv.weight", i)),
1901 wo: get(&format!("layers.{}.attention.wo.weight", i)),
1902 w1: get(&format!("layers.{}.feed_forward.w1.weight", i)),
1903 w2: get(&format!("layers.{}.feed_forward.w2.weight", i)),
1904 w3: get(&format!("layers.{}.feed_forward.w3.weight", i)),
1905 attention_norm: vec![1.0; config.dim],
1906 ffn_norm: vec![1.0; config.dim],
1907 });
1908 }
1909
1910 let freq_cis = precompute_freqs_cis(config.dim / config.n_heads, config.max_seq_len);
1912
1913 Self {
1914 config,
1915 tok_embeddings,
1916 layers,
1917 norm,
1918 output,
1919 freq_cis,
1920 }
1921 }
1922
1923 pub fn forward(&self, token: usize, pos: usize) -> Vec<f32> {
1926 let mut h = self.get_embedding(token);
1927
1928 for layer in &self.layers {
1929 let h_norm = rms_norm(&h, &layer.attention_norm, self.config.norm_eps);
1931 let attn_out = self.attention(layer, &h_norm, pos);
1932 for i in 0..h.len() { h[i] += attn_out[i]; }
1933
1934 let h_norm = rms_norm(&h, &layer.ffn_norm, self.config.norm_eps);
1936 let ffn_out = self.feed_forward(layer, &h_norm);
1937 for i in 0..h.len() { h[i] += ffn_out[i]; }
1938 }
1939
1940 let h = rms_norm(&h, &self.norm, self.config.norm_eps);
1941 self.project_output(&h)
1942 }
1943
1944 fn get_embedding(&self, token: usize) -> Vec<f32> {
1945 let start = token * self.config.dim;
1946 let mut embd = Vec::with_capacity(self.config.dim);
1947 for i in 0..self.config.dim {
1948 embd.push(trit_to_f32(self.tok_embeddings.data[start + i]));
1949 }
1950 embd
1951 }
1952
1953 fn attention(&self, layer: &TritBlock, x: &[f32], pos: usize) -> Vec<f32> {
1954 let x_trit = TritMatrix::from_trits(1, x.len(), x.iter().map(|&v| trit_from_f32_approx(v)).collect());
1957
1958 let (q_trit, _) = sparse_matmul(&x_trit, &layer.wq);
1959 let (k_trit, _) = sparse_matmul(&x_trit, &layer.wk);
1960 let (v_trit, _) = sparse_matmul(&x_trit, &layer.wv);
1961
1962 let mut q = q_trit.data.iter().map(|&t| trit_to_f32(t)).collect::<Vec<_>>();
1963 let mut k = k_trit.data.iter().map(|&t| trit_to_f32(t)).collect::<Vec<_>>();
1964 let v = v_trit.data.iter().map(|&t| trit_to_f32(t)).collect::<Vec<_>>();
1965
1966 apply_rope(&mut q, pos, &self.freq_cis, self.config.n_heads);
1968 apply_rope(&mut k, pos, &self.freq_cis, self.config.n_heads);
1969
1970 let v_trit = TritMatrix::from_trits(1, v.len(), v.iter().map(|&val| trit_from_f32_approx(val)).collect());
1975 let (out, _) = sparse_matmul(&v_trit, &layer.wo);
1976 out.data.iter().map(|&t| trit_to_f32(t)).collect()
1977 }
1978
1979 fn feed_forward(&self, layer: &TritBlock, x: &[f32]) -> Vec<f32> {
1980 let x_trit = TritMatrix::from_trits(1, x.len(), x.iter().map(|&v| trit_from_f32_approx(v)).collect());
1981
1982 let (w1_x, _) = sparse_matmul(&x_trit, &layer.w1);
1984 let (w3_x, _) = sparse_matmul(&x_trit, &layer.w3);
1985
1986 let mut hidden = Vec::with_capacity(w1_x.data.len());
1987 for i in 0..w1_x.data.len() {
1988 let v1 = trit_to_f32(w1_x.data[i]);
1989 let v3 = trit_to_f32(w3_x.data[i]);
1990 let silu_v3 = v3 / (1.0 + (-v3).exp());
1992 hidden.push(v1 * silu_v3);
1993 }
1994
1995 let hidden_trit = TritMatrix::from_trits(1, hidden.len(), hidden.iter().map(|&v| trit_from_f32_approx(v)).collect());
1996 let (out, _) = sparse_matmul(&hidden_trit, &layer.w2);
1997 out.data.iter().map(|&t| trit_to_f32(t)).collect()
1998 }
1999
2000 fn project_output(&self, x: &[f32]) -> Vec<f32> {
2001 let x_trit = TritMatrix::from_trits(1, x.len(), x.iter().map(|&v| trit_from_f32_approx(v)).collect());
2002 let (logits, _) = sparse_matmul(&x_trit, &self.output);
2003 logits.data.iter().map(|&t| trit_to_f32(t)).collect()
2004 }
2005}
2006
2007fn rms_norm(x: &[f32], weight: &[f32], eps: f32) -> Vec<f32> {
2010 let sum_sq = x.iter().map(|&v| v * v).sum::<f32>();
2011 let inv_rms = 1.0 / (sum_sq / x.len() as f32 + eps).sqrt();
2012 x.iter().zip(weight.iter()).map(|(&v, &w)| v * inv_rms * w).collect()
2013}
2014
2015fn precompute_freqs_cis(dim: usize, end: usize) -> Vec<(f32, f32)> {
2016 let mut freqs_cis = Vec::with_capacity(end * (dim / 2));
2017 for pos in 0..end {
2018 for i in 0..(dim / 2) {
2019 let freq = 1.0 / 10000.0f32.powf((i * 2) as f32 / dim as f32);
2020 let val = pos as f32 * freq;
2021 freqs_cis.push((val.cos(), val.sin()));
2022 }
2023 }
2024 freqs_cis
2025}
2026
2027fn apply_rope(x: &mut [f32], pos: usize, freq_cis: &[(f32, f32)], n_heads: usize) {
2028 let head_dim = x.len() / n_heads;
2029 for h in 0..n_heads {
2030 let start = h * head_dim;
2031 for i in 0..(head_dim / 2) {
2032 let (cos, sin) = freq_cis[pos * (head_dim / 2) + i];
2033 let x0 = x[start + i];
2034 let x1 = x[start + i + head_dim / 2];
2035 x[start + i] = x0 * cos - x1 * sin;
2036 x[start + i + head_dim / 2] = x0 * sin + x1 * cos;
2037 }
2038 }
2039}
2040
2041pub fn trit_to_f32(t: Trit) -> f32 {
2042 match t {
2043 Trit::Affirm => 1.0,
2044 Trit::Reject => -1.0,
2045 Trit::Tend => 0.0,
2046 }
2047}
2048
2049pub fn trit_from_f32_approx(v: f32) -> Trit {
2050 if v > 0.5 { Trit::Affirm }
2051 else if v < -0.5 { Trit::Reject }
2052 else { Trit::Tend }
2053}