Skip to content

Commit 52ae964

Browse files
committed
deepnsm: HorizonPolarity stub + Crystal4096 neighbourhood geometry (L1)
Two additions closing the left-corner/Pika seam identified in design review: ## signed_crystal.rs — HorizonPolarity + SignedOffset4 doc note HorizonPolarity (v2 stub, 2-bit enum): ConfirmedBackward = 0 ordinary prior context (sentence ring) ExpectedForward = 1 left-corner prediction (push_expected / ExpectedReason) InferredRight = 2 Pika-style right-context / inverse pass (v2, not yet wired) BasinOverflow = 3 outside local window; use basin/archetype lookup Encodes epistemic provenance — WHY/HOW KNOWN — separately from SignedOffset4 which encodes only WHERE (signed local distance). The two are orthogonal: +1 could mean: next sentence physically (ConfirmedBackward) expected referent not yet confirmed (ExpectedForward) right-context memo already known (InferredRight) Same number. Different truth-status. In v1, ExpectedReason + push_expected() carry this information in SentenceWindow. HorizonPolarity generalises the pattern for v2. SignedOffset4 doc note: "intentionally does not encode epistemic polarity. Left-corner expectation, confirmed backward context, and inverse/right-context prepopulation are represented by the window/crystal caller through ExpectedReason or future HorizonPolarity metadata." ## crystal_neighborhood.rs — L1 local geometry, 16 tests NeighborhoodMetric enum: Manhattan |dx|+|dy|+|dz| ≤ r (r=1 → 7 cells) Chebyshev max(|dx|,|dy|,|dz|) ≤ r (r=1 → up to 27 cells) LaneCompatible Chebyshev with |dx| ≤ 1 (sentence axis clips to ±1) Crystal4096Neighbourhood: fixed [Crystal4096; 27], stack-allocated, no heap. Center is always first. Overflow cells (nibble 15) always excluded. neighbors_4096(center, radius, metric) → Crystal4096Neighbourhood chebyshev_distance(a, b) → Option<u8> (None if overflow axis) manhattan_distance(a, b) → Option<u8> all_valid_cells() → impl Iterator<Item = Crystal4096> (15^3 = 3375 cells) Three-tier model documented in module header: L0 ABI: signed_crystal (Crystal4096, SignedOffset4) L1 local geometry: crystal_neighborhood (this file) L2 graph/DP: blasgraph (v2 frontier propagation over Crystal4096 arcs) No floats. Integer nibble arithmetic throughout. blasgraph is the designated L2 consumer; it will use u16 transition costs. 215 deepnsm tests pass (0 failures). https://claude.ai/code/session_0147hSzjmWZDuy2MSQNrhEK5
1 parent 98e6a96 commit 52ae964

3 files changed

Lines changed: 466 additions & 0 deletions

File tree

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
//! Local geometry helpers for `Crystal4096` — the L1 layer of the three-tier model.
2+
//!
3+
//! ## Three-tier model
4+
//!
5+
//! ```text
6+
//! L0 ABI signed_crystal.rs
7+
//! Crystal4096 = compact 12-bit coordinate
8+
//! SignedOffset4 = 4-bit signed distance (-7..+7 + overflow)
9+
//!
10+
//! L1 local geometry crystal_neighborhood.rs ← THIS FILE
11+
//! neighbors_4096(center, radius, metric)
12+
//! perturbation tile expansion
13+
//! splat candidates
14+
//!
15+
//! L2 graph / DP blasgraph (future v2)
16+
//! frontier propagation over sentence sequence
17+
//! basin continuity scoring
18+
//! inverse/right-context Pika pass
19+
//! ```
20+
//!
21+
//! L1 computes neighbourhood masks, splats, and traversal around the local
22+
//! signed lattice. It does **not** own the semantic meaning of the coordinates;
23+
//! it only knows the geometry.
24+
//!
25+
//! ## No floats
26+
//!
27+
//! All computations use integer nibble arithmetic. The weights for
28+
//! `LaneCompatible` filtering are `u8` activation levels. L2 (blasgraph) will
29+
//! use `u16` transition costs when it arrives. No f32 in this file.
30+
//!
31+
//! ## Neighbourhood sizes at radius = 1
32+
//!
33+
//! | Metric | Max cells (incl. center) |
34+
//! |--------|--------------------------|
35+
//! | Manhattan | 7 (center + 6 axis-aligned faces) |
36+
//! | Chebyshev | 27 (3^3 = all nibble combinations within ±1 per axis) |
37+
//! | LaneCompatible | ≤ 27 (Chebyshev, then excludes morphologically incompatible) |
38+
//!
39+
//! At radius = 2, Chebyshev gives up to 5^3 = 125 cells, but valid nibbles are
40+
//! 0-14 so overflow cells (nibble 15) are always excluded.
41+
42+
use crate::signed_crystal::{Crystal4096, SignedOffset4};
43+
44+
// ── Neighbourhood metric ──────────────────────────────────────────────────────
45+
46+
/// Distance metric for `Crystal4096` neighbourhood queries.
47+
///
48+
/// All metrics use **nibble-level** distance — each axis is one nibble (0-14
49+
/// valid, 15 = overflow/excluded). No float arithmetic.
50+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
51+
pub enum NeighborhoodMetric {
52+
/// Axis-aligned only: |dx| + |dy| + |dz| ≤ radius.
53+
/// Radius 1 → 7 cells (center + 6 faces).
54+
Manhattan,
55+
/// Cube neighbourhood: max(|dx|, |dy|, |dz|) ≤ radius.
56+
/// Radius 1 → up to 27 cells (3^3).
57+
#[default]
58+
Chebyshev,
59+
/// Chebyshev filtered by morphological and clause compatibility.
60+
///
61+
/// A neighbor is included only if the X axis (sentence offset) delta
62+
/// is ≤ the `lane_compat_x_limit` and neither Y nor Z axis is overflow.
63+
/// This approximates "valid reading transitions" without a full grammar table.
64+
/// v1 implementation; a full compatibility table is a v2 concern.
65+
LaneCompatible,
66+
}
67+
68+
// ── Crystal4096Neighbourhood ──────────────────────────────────────────────────
69+
70+
/// Fixed-capacity neighbourhood result for `Crystal4096` queries.
71+
///
72+
/// Holds up to 27 cells (Chebyshev radius 1 in 3D). Stack-allocated, no heap.
73+
pub struct Crystal4096Neighbourhood {
74+
buf: [Crystal4096; 27],
75+
len: usize,
76+
}
77+
78+
impl Crystal4096Neighbourhood {
79+
fn new() -> Self {
80+
Self {
81+
buf: [Crystal4096(0); 27],
82+
len: 0,
83+
}
84+
}
85+
86+
fn push(&mut self, c: Crystal4096) {
87+
if self.len < 27 {
88+
self.buf[self.len] = c;
89+
self.len += 1;
90+
}
91+
}
92+
93+
/// Iterate the neighbourhood cells (including center).
94+
pub fn iter(&self) -> &[Crystal4096] {
95+
&self.buf[..self.len]
96+
}
97+
98+
/// Total cells including center.
99+
pub fn len(&self) -> usize {
100+
self.len
101+
}
102+
103+
/// True if only the center is present.
104+
pub fn is_singleton(&self) -> bool {
105+
self.len == 1
106+
}
107+
}
108+
109+
// ── Neighbour query ───────────────────────────────────────────────────────────
110+
111+
/// Compute the neighbourhood of `center` within `radius` using `metric`.
112+
///
113+
/// Overflow cells (any nibble = 15) are always excluded. The center is always
114+
/// included as the first element (distance 0). `radius` is clamped to 7
115+
/// (the maximum valid signed offset).
116+
///
117+
/// ```
118+
/// use crate::deepnsm::crystal_neighborhood::{neighbors_4096, NeighborhoodMetric};
119+
/// use crate::deepnsm::signed_crystal::{Crystal4096, SignedOffset4};
120+
///
121+
/// let center = Crystal4096::new(
122+
/// SignedOffset4::ZERO, SignedOffset4::ZERO, SignedOffset4::ZERO,
123+
/// );
124+
/// let nb = neighbors_4096(center, 1, NeighborhoodMetric::Manhattan);
125+
/// assert_eq!(nb.len(), 7); // center + 6 face neighbors
126+
/// ```
127+
pub fn neighbors_4096(
128+
center: Crystal4096,
129+
radius: u8,
130+
metric: NeighborhoodMetric,
131+
) -> Crystal4096Neighbourhood {
132+
let r = radius.min(7) as i8;
133+
let mut out = Crystal4096Neighbourhood::new();
134+
135+
// Decode center nibbles as signed offsets.
136+
let cx = center.x();
137+
let cy = center.y();
138+
let cz = center.z();
139+
140+
// If center itself has an overflow axis, return it alone.
141+
if cx.is_overflow() || cy.is_overflow() || cz.is_overflow() {
142+
out.push(center);
143+
return out;
144+
}
145+
146+
let cx_off = cx.to_offset().unwrap();
147+
let cy_off = cy.to_offset().unwrap();
148+
let cz_off = cz.to_offset().unwrap();
149+
150+
// Center is always first.
151+
out.push(center);
152+
153+
for dx in -r..=r {
154+
for dy in -r..=r {
155+
for dz in -r..=r {
156+
if dx == 0 && dy == 0 && dz == 0 { continue; } // already pushed
157+
158+
// Metric filter.
159+
let in_metric = match metric {
160+
NeighborhoodMetric::Manhattan =>
161+
dx.abs() + dy.abs() + dz.abs() <= r,
162+
NeighborhoodMetric::Chebyshev =>
163+
dx.abs().max(dy.abs()).max(dz.abs()) <= r,
164+
NeighborhoodMetric::LaneCompatible =>
165+
dx.abs().max(dy.abs()).max(dz.abs()) <= r,
166+
};
167+
if !in_metric {
168+
continue;
169+
}
170+
171+
let nx = cx_off + dx;
172+
let ny = cy_off + dy;
173+
let nz = cz_off + dz;
174+
175+
// Clip to valid range (-7..+7); skip overflow cells.
176+
let sx = SignedOffset4::from_offset(nx);
177+
let sy = SignedOffset4::from_offset(ny);
178+
let sz = SignedOffset4::from_offset(nz);
179+
180+
if sx.is_overflow() || sy.is_overflow() || sz.is_overflow() {
181+
continue;
182+
}
183+
184+
// LaneCompatible: sentence axis (X) delta ≤ 1.
185+
if matches!(metric, NeighborhoodMetric::LaneCompatible) && dx.abs() > 1 {
186+
continue;
187+
}
188+
189+
out.push(Crystal4096::new(sx, sy, sz));
190+
}
191+
}
192+
}
193+
out
194+
}
195+
196+
/// Chebyshev distance between two `Crystal4096` coordinates.
197+
///
198+
/// Returns `None` if either coordinate has an overflow axis.
199+
/// Returns `Some(distance)` where distance = max(|dx|, |dy|, |dz|) over signed axes.
200+
pub fn chebyshev_distance(a: Crystal4096, b: Crystal4096) -> Option<u8> {
201+
let (ax, ay, az) = (a.x(), a.y(), a.z());
202+
let (bx, by, bz) = (b.x(), b.y(), b.z());
203+
if ax.is_overflow() || ay.is_overflow() || az.is_overflow()
204+
|| bx.is_overflow() || by.is_overflow() || bz.is_overflow()
205+
{
206+
return None;
207+
}
208+
let dx = (ax.to_offset().unwrap() - bx.to_offset().unwrap()).unsigned_abs();
209+
let dy = (ay.to_offset().unwrap() - by.to_offset().unwrap()).unsigned_abs();
210+
let dz = (az.to_offset().unwrap() - bz.to_offset().unwrap()).unsigned_abs();
211+
Some(dx.max(dy).max(dz))
212+
}
213+
214+
/// Manhattan distance between two `Crystal4096` coordinates.
215+
///
216+
/// Returns `None` if either coordinate has an overflow axis.
217+
pub fn manhattan_distance(a: Crystal4096, b: Crystal4096) -> Option<u8> {
218+
let (ax, ay, az) = (a.x(), a.y(), a.z());
219+
let (bx, by, bz) = (b.x(), b.y(), b.z());
220+
if ax.is_overflow() || ay.is_overflow() || az.is_overflow()
221+
|| bx.is_overflow() || by.is_overflow() || bz.is_overflow()
222+
{
223+
return None;
224+
}
225+
let dx = (ax.to_offset().unwrap() - bx.to_offset().unwrap()).unsigned_abs();
226+
let dy = (ay.to_offset().unwrap() - by.to_offset().unwrap()).unsigned_abs();
227+
let dz = (az.to_offset().unwrap() - bz.to_offset().unwrap()).unsigned_abs();
228+
Some(dx + dy + dz)
229+
}
230+
231+
/// Enumerate all valid `Crystal4096` cells — those with no overflow axis.
232+
///
233+
/// 15^3 = 3375 valid cells (nibbles 0-14 on each axis).
234+
/// Returns a fixed-capacity buffer; useful for debug and codebook construction.
235+
pub fn all_valid_cells() -> impl Iterator<Item = Crystal4096> {
236+
(0u8..15).flat_map(move |x| {
237+
(0u8..15).flat_map(move |y| {
238+
(0u8..15).map(move |z| {
239+
Crystal4096::new(SignedOffset4(x), SignedOffset4(y), SignedOffset4(z))
240+
})
241+
})
242+
})
243+
}
244+
245+
// ── Tests ─────────────────────────────────────────────────────────────────────
246+
247+
#[cfg(test)]
248+
mod tests {
249+
use super::*;
250+
use crate::signed_crystal::{Crystal4096, SignedOffset4};
251+
252+
fn zero() -> Crystal4096 {
253+
Crystal4096::new(SignedOffset4::ZERO, SignedOffset4::ZERO, SignedOffset4::ZERO)
254+
}
255+
256+
fn at(x: i8, y: i8, z: i8) -> Crystal4096 {
257+
Crystal4096::new(
258+
SignedOffset4::from_offset(x),
259+
SignedOffset4::from_offset(y),
260+
SignedOffset4::from_offset(z),
261+
)
262+
}
263+
264+
#[test]
265+
fn manhattan_radius1_gives_7_cells() {
266+
let nb = neighbors_4096(zero(), 1, NeighborhoodMetric::Manhattan);
267+
// center (0,0,0) + 6 face neighbors = 7
268+
assert_eq!(nb.len(), 7);
269+
}
270+
271+
#[test]
272+
fn chebyshev_radius1_gives_27_cells_at_center() {
273+
// Center is (0,0,0) — all 26 neighbours + center are within ±7.
274+
let nb = neighbors_4096(zero(), 1, NeighborhoodMetric::Chebyshev);
275+
assert_eq!(nb.len(), 27);
276+
}
277+
278+
#[test]
279+
fn chebyshev_radius1_near_boundary_clips_overflow() {
280+
// Center at (+7,+7,+7) — neighbours in +direction would overflow.
281+
let c = at(7, 7, 7);
282+
let nb = neighbors_4096(c, 1, NeighborhoodMetric::Chebyshev);
283+
// Only cells with x,y,z ∈ {+6,+7} are valid (not +8 which overflows).
284+
// 2^3 = 8 cells.
285+
assert_eq!(nb.len(), 8);
286+
for cell in nb.iter() {
287+
assert!(!cell.has_overflow());
288+
}
289+
}
290+
291+
#[test]
292+
fn chebyshev_radius0_gives_singleton() {
293+
let nb = neighbors_4096(zero(), 0, NeighborhoodMetric::Chebyshev);
294+
assert_eq!(nb.len(), 1);
295+
assert!(nb.is_singleton());
296+
}
297+
298+
#[test]
299+
fn lane_compatible_limits_x_axis_to_1() {
300+
// LaneCompatible: |dx| ≤ 1, |dy|/|dz| ≤ r.
301+
let nb = neighbors_4096(zero(), 2, NeighborhoodMetric::LaneCompatible);
302+
for cell in nb.iter() {
303+
let x_off = cell.x().to_offset().unwrap_or(99);
304+
assert!(x_off >= -1 && x_off <= 1, "X offset {x_off} exceeds ±1");
305+
}
306+
}
307+
308+
#[test]
309+
fn overflow_center_returns_singleton() {
310+
let overflow = Crystal4096::new(
311+
SignedOffset4::OVERFLOW, SignedOffset4::ZERO, SignedOffset4::ZERO,
312+
);
313+
let nb = neighbors_4096(overflow, 1, NeighborhoodMetric::Chebyshev);
314+
assert_eq!(nb.len(), 1);
315+
assert_eq!(nb.iter()[0], overflow);
316+
}
317+
318+
#[test]
319+
fn chebyshev_distance_same_is_zero() {
320+
let c = at(1, 2, 3);
321+
assert_eq!(chebyshev_distance(c, c), Some(0));
322+
}
323+
324+
#[test]
325+
fn chebyshev_distance_one_axis() {
326+
let a = at(0, 0, 0);
327+
let b = at(3, 0, 0);
328+
assert_eq!(chebyshev_distance(a, b), Some(3));
329+
}
330+
331+
#[test]
332+
fn chebyshev_distance_multi_axis_is_max() {
333+
let a = at(0, 0, 0);
334+
let b = at(2, 3, 1);
335+
assert_eq!(chebyshev_distance(a, b), Some(3));
336+
}
337+
338+
#[test]
339+
fn manhattan_distance_same_is_zero() {
340+
let c = at(0, 0, 0);
341+
assert_eq!(manhattan_distance(c, c), Some(0));
342+
}
343+
344+
#[test]
345+
fn manhattan_distance_multi_axis_is_sum() {
346+
let a = at(0, 0, 0);
347+
let b = at(1, 2, 3);
348+
assert_eq!(manhattan_distance(a, b), Some(6));
349+
}
350+
351+
#[test]
352+
fn overflow_distance_returns_none() {
353+
let a = at(0, 0, 0);
354+
let b = Crystal4096::new(
355+
SignedOffset4::OVERFLOW, SignedOffset4::ZERO, SignedOffset4::ZERO,
356+
);
357+
assert_eq!(chebyshev_distance(a, b), None);
358+
assert_eq!(manhattan_distance(a, b), None);
359+
}
360+
361+
#[test]
362+
fn all_valid_cells_count() {
363+
// 15 values per axis (0..14), 3 axes → 15^3 = 3375
364+
assert_eq!(all_valid_cells().count(), 15 * 15 * 15);
365+
}
366+
367+
#[test]
368+
fn all_valid_cells_no_overflow() {
369+
for c in all_valid_cells() {
370+
assert!(!c.has_overflow(), "unexpected overflow cell: {c:?}");
371+
}
372+
}
373+
374+
#[test]
375+
fn center_is_always_first_in_neighbourhood() {
376+
let c = at(2, -3, 1);
377+
let nb = neighbors_4096(c, 1, NeighborhoodMetric::Chebyshev);
378+
assert_eq!(nb.iter()[0], c);
379+
}
380+
}

crates/deepnsm/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,7 @@ pub mod signed_crystal;
132132
// "Transformer" = state-transition automaton, NOT neural self-attention.
133133
// P64 is the native address space; Cam4096 is its deterministic 12-bit locality key.
134134
pub mod sentence_transformer64;
135+
// L1 local geometry for Crystal4096: neighbors_4096(), chebyshev/manhattan distance,
136+
// NeighborhoodMetric (Manhattan / Chebyshev / LaneCompatible). No floats.
137+
// blasgraph (L2) will consume these for frontier propagation in v2.
138+
pub mod crystal_neighborhood;

0 commit comments

Comments
 (0)