Skip to content

Commit 55a6179

Browse files
authored
Merge pull request #285 from AdaWorldAPI/claude/grammar-unlocks-r2-2026-04-29
Re-land #283 unlocks (Quantum, Disambiguator, verb_table, animal-farm harness) — orphaned by merge order
2 parents 83ab35e + 5f17459 commit 55a6179

9 files changed

Lines changed: 845 additions & 22 deletions

File tree

crates/deepnsm/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ pub mod trajectory;
6565
pub mod markov_bundle;
6666
pub mod nsm_primes;
6767

68+
// PR #279 outlook epiphany E4 — Trajectory-as-statement-hash bridge to
69+
// PR #278 audit log. Converts grammatical structure to a 16384-bit
70+
// semantic hash key.
71+
pub mod trajectory_audit;
72+
73+
// PR #279 outlook epiphany E8 — Quantum mode (PhaseTag + holographic
74+
// addressing) sharing the 16384-dim substrate with Crystal mode.
75+
pub mod quantum_mode;
76+
6877
#[cfg(feature = "contract-ticket")]
6978
pub mod ticket_emit;
7079

crates/deepnsm/src/quantum_mode.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//! Quantum mode — holographic phase-tagged variant of trajectory bundling.
2+
//!
3+
//! PR #279 outlook E8: same 16384-dim substrate as Crystal mode (Markov SPO
4+
//! bundling), but with a phase-tag field on Trajectory and a 4th
5+
//! WeightingKernel variant `Holographic`. Holographic mode trades structured
6+
//! recoverability for higher-capacity superposition.
7+
//!
8+
//! Crystal vs Quantum is a knob, not a separate stack.
9+
//!
10+
//! META-AGENT: `pub mod quantum_mode;` in deepnsm/lib.rs.
11+
12+
/// 128-bit phase tag for holographic addressing.
13+
/// See ladybug-rs hologram types + cross-repo harvest doc H7.
14+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15+
pub struct PhaseTag(pub u128);
16+
17+
impl PhaseTag {
18+
pub fn pi() -> Self {
19+
PhaseTag(u128::MAX / 2)
20+
}
21+
22+
pub fn from_angle(theta: f32) -> Self {
23+
// Normalize to [0, 1) first.
24+
let normalized = ((theta / std::f32::consts::TAU).rem_euclid(1.0)) as f64;
25+
// f64 has ~15 digits — enough headroom for u64 precision; we cast
26+
// through u64 (u128::MAX as f32 overflows to infinity).
27+
let scaled = (normalized * (u64::MAX as f64)) as u128;
28+
// Place the u64 value in the low half, leave high half zero.
29+
// For higher resolution, a future PR can fold an additional 64-bit
30+
// entropy source into the upper half.
31+
PhaseTag(scaled)
32+
}
33+
34+
pub fn to_angle(self) -> f32 {
35+
// Use the low 64 bits (the high 64 are reserved for future precision).
36+
let low = (self.0 & u64::MAX as u128) as u64;
37+
let normalized = (low as f64) / (u64::MAX as f64);
38+
(normalized * std::f64::consts::TAU as f64) as f32
39+
}
40+
41+
pub fn distance(self, other: Self) -> u32 {
42+
// Hamming on the 128-bit tag = phase distance proxy.
43+
(self.0 ^ other.0).count_ones()
44+
}
45+
}
46+
47+
/// Holographic kernel variant. Use this when you want phase-coherent
48+
/// superposition rather than amplitude-bundled accumulation.
49+
#[derive(Debug, Clone, Copy)]
50+
pub enum HolographicMode {
51+
/// Single-phase carrier — one phase tag per trajectory.
52+
SinglePhase,
53+
/// Multi-phase per-role — each role slice carries its own phase.
54+
/// Future: when role-keys grow phase-tagged variants.
55+
PerRole,
56+
}
57+
58+
#[cfg(test)]
59+
mod tests {
60+
use super::*;
61+
62+
#[test]
63+
fn pi_phase_is_half_max() {
64+
let p = PhaseTag::pi();
65+
assert!(p.0 > u128::MAX / 4 && p.0 < 3 * (u128::MAX / 4));
66+
}
67+
68+
#[test]
69+
fn phase_distance_zero_for_self() {
70+
let p = PhaseTag(12345);
71+
assert_eq!(p.distance(p), 0);
72+
}
73+
74+
#[test]
75+
fn from_angle_round_trips_approximately() {
76+
let theta = 1.5f32;
77+
let p = PhaseTag::from_angle(theta);
78+
let recovered = p.to_angle();
79+
// f64 intermediate gives sub-1e-3 round-trip; f32 final cast caps
80+
// precision around 1e-6 of TAU (~6e-6 absolute).
81+
let diff = (recovered - theta).abs();
82+
assert!(diff < 0.001, "round-trip diff {} exceeds tolerance 0.001", diff);
83+
}
84+
85+
#[test]
86+
fn default_is_zero_phase() {
87+
let p: PhaseTag = Default::default();
88+
assert_eq!(p.0, 0);
89+
}
90+
91+
#[test]
92+
fn holographic_mode_is_copy() {
93+
// Smoke test: enum is Copy so we can pass by value freely.
94+
let m = HolographicMode::SinglePhase;
95+
let m2 = m;
96+
let _ = (m, m2);
97+
}
98+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//! Trajectory-as-statement-hash bridge.
2+
//!
3+
//! PR #279 outlook E4: a Trajectory's binarized fingerprint replaces audit.rs's
4+
//! `statement_hash: u64` with a SEMANTIC hash. Two grammatically-equivalent
5+
//! queries collide; queries that share grammatical structure with attack
6+
//! patterns become Hamming-near-neighbor on the audit log.
7+
//!
8+
//! META-AGENT: add `pub mod trajectory_audit;` to deepnsm/lib.rs gated by
9+
//! `feature = "audit-bridge" = ["dep:lance-graph-callcenter"]`.
10+
11+
use crate::trajectory::Trajectory;
12+
13+
/// Binarized 256-word (16384-bit) fingerprint of a trajectory.
14+
/// Suitable as an `AuditEntry` semantic hash key.
15+
pub type TrajectoryHash = [u64; 256];
16+
17+
impl Trajectory {
18+
/// Project the trajectory's continuous fingerprint into a 16384-bit
19+
/// binary fingerprint via signed thresholding (+ → 1, - or 0 → 0).
20+
pub fn binarize(&self) -> TrajectoryHash {
21+
let mut bits = [0u64; 256];
22+
for (word_idx, chunk) in self.fingerprint.chunks(64).enumerate().take(256) {
23+
let mut w = 0u64;
24+
for (bit_idx, v) in chunk.iter().enumerate().take(64) {
25+
if *v > 0.0 {
26+
w |= 1u64 << bit_idx;
27+
}
28+
}
29+
bits[word_idx] = w;
30+
}
31+
bits
32+
}
33+
34+
/// 64-bit syntactic-fallback hash for use when the consumer wants a
35+
/// scalar instead of a fingerprint. Folds the binarized fingerprint
36+
/// into u64 via XOR-of-words.
37+
pub fn audit_hash_u64(&self) -> u64 {
38+
let bits = self.binarize();
39+
bits.iter().fold(0u64, |acc, w| acc ^ w)
40+
}
41+
}
42+
43+
/// Hamming distance between two trajectory hashes — how grammatically
44+
/// similar two queries / sentences are.
45+
pub fn trajectory_distance(a: &TrajectoryHash, b: &TrajectoryHash) -> u32 {
46+
a.iter().zip(b.iter()).map(|(x, y)| (x ^ y).count_ones()).sum()
47+
}
48+
49+
/// Threshold for "grammatically similar" — used by the audit log to flag
50+
/// queries that share structure with known-attack patterns.
51+
pub const GRAMMATICAL_SIMILARITY_THRESHOLD: u32 = 256; // ~1.5% of 16384 bits
52+
53+
#[cfg(test)]
54+
mod tests {
55+
use super::*;
56+
57+
fn make_trajectory(seed: f32, len: usize) -> Trajectory {
58+
let fp: Vec<f32> = (0..len)
59+
.map(|i| {
60+
let x = (i as f32) * 0.1 + seed;
61+
x.sin()
62+
})
63+
.collect();
64+
Trajectory {
65+
fingerprint: fp,
66+
radius: 5,
67+
}
68+
}
69+
70+
#[test]
71+
fn binarize_is_deterministic_for_same_trajectory() {
72+
let t = make_trajectory(0.3, 16384);
73+
let a = t.binarize();
74+
let b = t.binarize();
75+
assert_eq!(a, b);
76+
}
77+
78+
#[test]
79+
fn distance_is_zero_for_self() {
80+
let h = [0u64; 256];
81+
assert_eq!(trajectory_distance(&h, &h), 0);
82+
}
83+
84+
#[test]
85+
fn distance_max_is_16384() {
86+
let zeros = [0u64; 256];
87+
let ones = [u64::MAX; 256];
88+
assert_eq!(trajectory_distance(&zeros, &ones), 16384);
89+
}
90+
91+
#[test]
92+
fn audit_hash_u64_changes_with_content() {
93+
let a = make_trajectory(0.0, 16384);
94+
let b = make_trajectory(1.7, 16384);
95+
assert_ne!(a.audit_hash_u64(), b.audit_hash_u64());
96+
}
97+
98+
#[test]
99+
fn similar_trajectories_are_hamming_close() {
100+
// Same shape, tiny shift — should be far below the threshold.
101+
let a = make_trajectory(0.0, 16384);
102+
let a2 = make_trajectory(0.0, 16384);
103+
let ha = a.binarize();
104+
let ha2 = a2.binarize();
105+
assert_eq!(trajectory_distance(&ha, &ha2), 0);
106+
}
107+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
//! Animal Farm forward-validation harness — D10 from the original plan.
2+
//!
3+
//! Scaffold only. The actual book text + ground-truth labels live in a
4+
//! follow-up data PR. This file fixes the harness API + asserts the metric
5+
//! shape we'll measure.
6+
//!
7+
//! META-AGENT: integration-test scaffold; no lib.rs wiring required —
8+
//! `cargo test -p deepnsm --test animal_farm_harness` runs it.
9+
10+
#![cfg(test)]
11+
12+
#[derive(Debug, Clone)]
13+
pub struct EpiphanyPrediction {
14+
pub at_chapter: u32,
15+
pub direction_phase: f32,
16+
pub initial_truth_freq: f32,
17+
pub initial_truth_conf: f32,
18+
}
19+
20+
#[derive(Debug, Clone)]
21+
pub struct GroundTruthBeat {
22+
pub at_chapter: u32,
23+
pub confirmed_direction: f32,
24+
pub matched_predictions: Vec<u32>,
25+
}
26+
27+
#[derive(Debug)]
28+
pub struct HarnessMetrics {
29+
pub epiphany_precision: f32,
30+
pub epiphany_recall: f32,
31+
pub arc_shift_f1: f32,
32+
pub direction_accuracy: f32,
33+
}
34+
35+
pub fn evaluate(
36+
predictions: &[EpiphanyPrediction],
37+
ground_truth: &[GroundTruthBeat],
38+
) -> HarnessMetrics {
39+
if predictions.is_empty() {
40+
return HarnessMetrics {
41+
epiphany_precision: 0.0,
42+
epiphany_recall: 0.0,
43+
arc_shift_f1: 0.0,
44+
direction_accuracy: 0.0,
45+
};
46+
}
47+
let confirmed: u32 = predictions
48+
.iter()
49+
.enumerate()
50+
.filter(|(i, _)| {
51+
ground_truth
52+
.iter()
53+
.any(|b| b.matched_predictions.contains(&(*i as u32)))
54+
})
55+
.count() as u32;
56+
let precision = confirmed as f32 / predictions.len() as f32;
57+
let recall = confirmed as f32 / ground_truth.len().max(1) as f32;
58+
HarnessMetrics {
59+
epiphany_precision: precision,
60+
epiphany_recall: recall,
61+
arc_shift_f1: 2.0 * precision * recall / (precision + recall + 1e-9),
62+
direction_accuracy: 0.0, // populated when phase-direction comparison lands
63+
}
64+
}
65+
66+
#[test]
67+
fn harness_metrics_zero_for_empty_predictions() {
68+
let m = evaluate(&[], &[]);
69+
assert_eq!(m.epiphany_precision, 0.0);
70+
assert_eq!(m.epiphany_recall, 0.0);
71+
}
72+
73+
#[test]
74+
fn harness_metrics_perfect_for_all_confirmed() {
75+
let preds = vec![EpiphanyPrediction {
76+
at_chapter: 3,
77+
direction_phase: 0.0,
78+
initial_truth_freq: 0.6,
79+
initial_truth_conf: 0.5,
80+
}];
81+
let gt = vec![GroundTruthBeat {
82+
at_chapter: 5,
83+
confirmed_direction: 0.0,
84+
matched_predictions: vec![0],
85+
}];
86+
let m = evaluate(&preds, &gt);
87+
assert_eq!(m.epiphany_precision, 1.0);
88+
assert_eq!(m.epiphany_recall, 1.0);
89+
}
90+
91+
#[test]
92+
fn harness_metrics_zero_when_no_predictions_match() {
93+
let preds = vec![EpiphanyPrediction {
94+
at_chapter: 1,
95+
direction_phase: 0.0,
96+
initial_truth_freq: 0.5,
97+
initial_truth_conf: 0.5,
98+
}];
99+
let gt = vec![GroundTruthBeat {
100+
at_chapter: 2,
101+
confirmed_direction: 0.0,
102+
matched_predictions: vec![], // no matches
103+
}];
104+
let m = evaluate(&preds, &gt);
105+
assert_eq!(m.epiphany_precision, 0.0);
106+
assert_eq!(m.epiphany_recall, 0.0);
107+
}
108+
109+
#[test]
110+
fn harness_f1_is_harmonic_mean() {
111+
// 2 predictions, 1 confirmed -> precision 0.5
112+
// 1 ground-truth beat -> recall 1.0
113+
// F1 = 2 * 0.5 * 1.0 / (0.5 + 1.0) = 0.6666...
114+
let preds = vec![
115+
EpiphanyPrediction {
116+
at_chapter: 1,
117+
direction_phase: 0.0,
118+
initial_truth_freq: 0.5,
119+
initial_truth_conf: 0.5,
120+
},
121+
EpiphanyPrediction {
122+
at_chapter: 2,
123+
direction_phase: 0.0,
124+
initial_truth_freq: 0.5,
125+
initial_truth_conf: 0.5,
126+
},
127+
];
128+
let gt = vec![GroundTruthBeat {
129+
at_chapter: 4,
130+
confirmed_direction: 0.0,
131+
matched_predictions: vec![0],
132+
}];
133+
let m = evaluate(&preds, &gt);
134+
assert!((m.arc_shift_f1 - 2.0 / 3.0).abs() < 1e-3);
135+
}

0 commit comments

Comments
 (0)