Skip to content

Commit bb775f8

Browse files
committed
Experiment 7: phi_pi_fib search wired in as OMC builtins, sublinear
Four new builtins exposing omnimcode-core/src/phi_pi_fib.rs to OMC: phi_pi_fib_search(arr, target) -> int exact match or -(insert+1) phi_pi_fib_nearest(arr, target) -> int always-valid nearest index phi_pi_fib_stats() -> [s, cmps] global counters phi_pi_fib_reset() -> null zero the counters phi_pi_fib_nearest is the gate primitive: missing-key lookups route to the nearest surviving library entry. Returns -1 only for an empty array; otherwise always a valid index. This is what gives the compression-gate architecture its "die gracefully" property. Experiment 7 reruns experiment 6's compression gate on top of the real Fibonacci-step search and measures the work: N=8 -> 3.79 compares/search N=16 -> 5.25 N=32 -> 6.18 N=64 -> 8.10 N=128 -> 8.67 N=256 -> 10.33 N=512 -> 11.29 N=1024 -> 12.57 128x library growth -> ~3.3x compare growth. Tracks log_2(N) closely (slightly better than log_phi(N) = 1.44 * log_2(N)). A linear scan would show avg_compares ~ N/2, growing 64x over the same range. Sanity: chain [3, 8, 13, 5, 21] on state 7 -> final state 9, matching experiment 6. Death tolerance: same per-deletion shifts as exp 6. All 12 deletions complete without crashing. 148/148 existing tests still pass. Both engines audit byte-identical on the experiment file (2627 bytes). Stops here for review before experiment 8 (learnable routing policy).
1 parent e8ee208 commit bb775f8

3 files changed

Lines changed: 405 additions & 3 deletions

File tree

experiments/hybrid_llm/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ OMC_VM=1 ./target/release/omnimcode-standalone experiments/hybrid_llm/experiment
9191
| 4B | Same gates, **magnitude-matched** structural OOD (inverted attractor weights: 10%/30%/60% small/med/large vs in-dist's 60%/30%/10%). | **Harmonic edges past L2 in AUROC: 0.956 vs 0.946.** At low FPR L2 still wins (TPR@FPR=1%: L2 0.60 vs harmonic 0.48), but on overall ranking the structural rarity signal beats the L2 metric once magnitude is no longer a giveaway. |
9292
| 5 | HBit cross-cutting tension (no reference) + combined gate (sum of z-normalised HBit, marginal rarity, L2) on both scenarios. | **Scenario A: HBit tension AUROC = 1.0** (perfect — mean tension 0.0 in-dist vs 20.1 OOD). Combined: 0.999. **Scenario B: HBit AUROC = 0.5** (random — both sides on-manifold, tension = 0 everywhere). Combined: 0.967, beating every single gate. Each gate owns a different OOD axis: HBit→off-manifold, marginal→distribution-shift, L2→magnitude. |
9393
| 6 | Phi-Pi-Fib compression gate: model as `(library + chain of keys)` instead of dense weights. 12-primitive library keyed by Fibonacci attractors, gate = nearest-key lookup, chains = "parameters". | Composition: trace `[3, 8, 13, 5, 21]` on state 7 → 9. Compression: 29 ints (library+chain) vs ~1001 ints dense table over [0,1000] = ~34× smaller (extrapolates to 9 orders of magnitude at LLM scale). **Death tolerance: all 12 library deletions complete without crashing — biggest deltas: kill key=13 → +12, kill key=5 → +5, kill key=21 → +3. 8 of 12 deletions invisible to output (unused capabilities or path coincidence).** Interchangeability: 6 different chains over the same library yield 6 different outputs (9, 22, 9, 5, 5, 52). |
94+
| 7 | Wire `phi_pi_fib::fibonacci_search` in as four OMC builtins (`phi_pi_fib_search`, `phi_pi_fib_nearest`, `phi_pi_fib_stats`, `phi_pi_fib_reset`). Rerun exp 6's gate using the real Fibonacci-step search; measure comparison counts vs library size. | **Sublinear scaling confirmed.** N=8 → 3.8 compares/search, N=1024 → 12.6. Going 128× wider in library size grows the per-lookup work only ~3.3×, vs ~64× for a linear scan. Empirically tracks `~log₂(N)`, slightly better than `log_φ(N) ≈ 1.44·log₂(N)`. Sanity check passes (same final state as exp 6). Death tolerance preserved across all 12 library deletions. 148/148 existing tests still pass. |
9495

9596
### Cumulative read across experiments 0–5
9697

@@ -170,9 +171,8 @@ result.
170171
- **5** HBit cross-cutting tension + 3-gate combined detector. ✓ done
171172
- **6** Phi-Pi-Fib compression gate: model = library + chain. ✓ done
172173
- **7** Wire `omnimcode-core/src/phi_pi_fib.rs::fibonacci_search` in
173-
as an OMC builtin so the gate uses sublinear search instead of the
174-
linear scan in experiment 6. Semantics identical at small library
175-
size; necessary at 10^6+ entries.
174+
as four OMC builtins; rerun exp 6's gate on top; measure compare
175+
counts. ✓ done
176176
- **8** Learnable routing policy: a function `state -> chain` that
177177
picks WHICH chain to run from input state. Start with a simple
178178
hand-authored policy (if state on small attractor use chain A,
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
# =============================================================================
2+
# Experiment 7 — Compression gate backed by the real phi-pi-fib search.
3+
#
4+
# Experiment 6 used a linear scan inside the gate. That stands in for the
5+
# Fibonacci-step search at small library sizes but makes the "sublinear
6+
# lookup" claim implied rather than proven. This experiment wires the
7+
# Rust phi_pi_fib::fibonacci_search in as four new OMC builtins and
8+
# reruns the compression-gate machinery on top:
9+
#
10+
# phi_pi_fib_search(sorted_arr, target) -> int
11+
# Fibonacci-step binary search. Returns the exact-match index when
12+
# found, or -(insert_pos + 1) when not found (Rust binary_search
13+
# sign convention).
14+
#
15+
# phi_pi_fib_nearest(sorted_arr, target) -> int
16+
# Same search, returns the index of the nearest entry by absolute
17+
# integer distance. -1 only when the array is empty. This is the
18+
# gate primitive — missing-key lookups route to nearest surviving
19+
# entry.
20+
#
21+
# phi_pi_fib_stats() -> [total_searches, total_comparisons]
22+
# Global counters since last reset. Used here to measure that
23+
# the gate's cost grows as O(log_phi n), not O(n).
24+
#
25+
# phi_pi_fib_reset() -> null
26+
# Zero the counters.
27+
#
28+
# What this experiment shows:
29+
#
30+
# (i) The new builtins audit byte-identical across tree-walk and
31+
# bytecode VM. The phi-pi-fib search lifts cleanly into OMC.
32+
# (ii) Sublinear behaviour: average comparisons per search across
33+
# libraries of increasing size scales sub-linearly with N.
34+
# (iii) The exp-6 chain produces the SAME final state under the new
35+
# gate, because phi_pi_fib_nearest agrees with the linear-scan
36+
# nearest-search on this library (sanity check).
37+
# (iv) The dead-key fallback still works: kill one library entry,
38+
# rerun, observe re-routing through the nearest survivor.
39+
#
40+
# Run:
41+
# ./target/release/omnimcode-standalone experiments/hybrid_llm/experiment_7_phi_fib_gate.omc
42+
# =============================================================================
43+
44+
# ---------------------------------------------------------------------------
45+
# Library — same 12 primitives as experiment 6, but keys live in an
46+
# actual sorted array so the search can index it directly.
47+
# ---------------------------------------------------------------------------
48+
49+
h LIB_KEYS = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233];
50+
h LIB_OPS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
51+
h LIB_SIZE = arr_len(LIB_KEYS);
52+
53+
fn apply_op(op, x) -> int {
54+
if op == 0 { return x; }
55+
if op == 1 { return x + 1; }
56+
if op == 2 { return x * 2; }
57+
if op == 3 { return phi.fold(x); }
58+
if op == 4 { return x + phi.fold(x); }
59+
if op == 5 { return to_int(harmonic_interfere(x, 5)); }
60+
if op == 6 { return to_int(harmonic_interfere(x, 13)); }
61+
if op == 7 { return x * x; }
62+
if op == 8 { return phi.fold(x * 2); }
63+
if op == 9 { return x - phi.fold(x); }
64+
if op == 10 { return to_int(harmonic_interfere(x, 21)); }
65+
if op == 11 { return phi.fold(x + 7); }
66+
return x;
67+
}
68+
69+
fn op_name(op) -> string {
70+
if op == 0 { return "identity"; }
71+
if op == 1 { return "increment"; }
72+
if op == 2 { return "double"; }
73+
if op == 3 { return "fold"; }
74+
if op == 4 { return "add_fold"; }
75+
if op == 5 { return "interfere_5"; }
76+
if op == 6 { return "interfere_13"; }
77+
if op == 7 { return "square"; }
78+
if op == 8 { return "double_fold"; }
79+
if op == 9 { return "residual"; }
80+
if op == 10 { return "interfere_21"; }
81+
if op == 11 { return "phi_shift_fold"; }
82+
return "?";
83+
}
84+
85+
# ---------------------------------------------------------------------------
86+
# The new gate: keys and ops live in two parallel arrays. The gate masks
87+
# dead entries by COPYING the alive keys into a smaller sorted array, then
88+
# running phi_pi_fib_nearest on that. The op_index returned is into the
89+
# ALIVE arrays, so we project back to the underlying op_id.
90+
# ---------------------------------------------------------------------------
91+
92+
fn alive_keys_and_ops(alive_mask) -> array {
93+
h alive_keys = [];
94+
h alive_ops = [];
95+
h i = 0;
96+
while i < LIB_SIZE {
97+
if arr_get(alive_mask, i) == 1 {
98+
arr_push(alive_keys, arr_get(LIB_KEYS, i));
99+
arr_push(alive_ops, arr_get(LIB_OPS, i));
100+
}
101+
i = i + 1;
102+
}
103+
h pack = arr_new(2, 0);
104+
arr_set(pack, 0, alive_keys);
105+
arr_set(pack, 1, alive_ops);
106+
return pack;
107+
}
108+
109+
# Apply the chain using phi-pi-fib nearest-key search at each step.
110+
# Returns final state; the comparison-count counter records total
111+
# work inside the gate via phi_pi_fib_stats().
112+
fn run_chain_with_phi_fib(initial_state, keys, alive_mask) -> int {
113+
h packed = alive_keys_and_ops(alive_mask);
114+
h alive_keys = arr_get(packed, 0);
115+
h alive_ops = arr_get(packed, 1);
116+
h n_alive = arr_len(alive_keys);
117+
118+
h state = initial_state;
119+
h n = arr_len(keys);
120+
h i = 0;
121+
while i < n {
122+
h target = arr_get(keys, i);
123+
h idx = phi_pi_fib_nearest(alive_keys, target);
124+
if idx >= 0 {
125+
h op = arr_get(alive_ops, idx);
126+
state = apply_op(op, state);
127+
}
128+
i = i + 1;
129+
}
130+
return state;
131+
}
132+
133+
fn run_chain_traced_with_phi_fib(initial_state, keys, alive_mask) -> int {
134+
h packed = alive_keys_and_ops(alive_mask);
135+
h alive_keys = arr_get(packed, 0);
136+
h alive_ops = arr_get(packed, 1);
137+
138+
h state = initial_state;
139+
h n = arr_len(keys);
140+
h i = 0;
141+
while i < n {
142+
h target = arr_get(keys, i);
143+
h idx = phi_pi_fib_nearest(alive_keys, target);
144+
if idx >= 0 {
145+
h actual_key = arr_get(alive_keys, idx);
146+
h op = arr_get(alive_ops, idx);
147+
h prev = state;
148+
state = apply_op(op, state);
149+
print(concat_many(
150+
" step ", i, ": target=", target,
151+
" -> alive_idx=", idx,
152+
" (key=", actual_key, ", op=", op_name(op), ")",
153+
" state ", prev, " -> ", state
154+
));
155+
}
156+
i = i + 1;
157+
}
158+
return state;
159+
}
160+
161+
fn alive_all() -> array {
162+
h m = arr_new(LIB_SIZE, 0);
163+
h i = 0;
164+
while i < LIB_SIZE { arr_set(m, i, 1); i = i + 1; }
165+
return m;
166+
}
167+
168+
fn alive_kill(base, kill_idx) -> array {
169+
h m = arr_new(LIB_SIZE, 0);
170+
h i = 0;
171+
while i < LIB_SIZE { arr_set(m, i, arr_get(base, i)); i = i + 1; }
172+
arr_set(m, kill_idx, 0);
173+
return m;
174+
}
175+
176+
# ===========================================================================
177+
# (i) Sanity — chain trace under the new gate.
178+
# ===========================================================================
179+
180+
print("== Experiment 7: compression gate backed by phi_pi_fib_nearest ==");
181+
print("");
182+
print(concat_many("Library keys = ", LIB_KEYS));
183+
print("");
184+
185+
h chain_main = [3, 8, 13, 5, 21];
186+
h initial = 7;
187+
h alive = alive_all();
188+
189+
phi_pi_fib_reset();
190+
print(concat_many("Chain: ", chain_main, " initial state = ", initial));
191+
print("Trace:");
192+
h final_main = run_chain_traced_with_phi_fib(initial, chain_main, alive);
193+
print(concat_many("Final state: ", final_main));
194+
h stats_main = phi_pi_fib_stats();
195+
print(concat_many(
196+
" gate work: searches=", arr_get(stats_main, 0),
197+
" comparisons=", arr_get(stats_main, 1)
198+
));
199+
print("");
200+
201+
# ===========================================================================
202+
# (ii) Sublinear scaling — comparisons per search across growing libraries.
203+
#
204+
# We synthesise sorted arrays of length N over consecutive integers, then
205+
# run M random nearest-key lookups against each. For phi_pi_fib_search
206+
# this should scale roughly as M * log_phi(N) comparisons.
207+
# ===========================================================================
208+
209+
fn build_sorted_n(n) -> array {
210+
h arr = arr_new(n, 0);
211+
h i = 0;
212+
while i < n {
213+
arr_set(arr, i, i * 2 + 1); # 1, 3, 5, ..., 2n-1
214+
i = i + 1;
215+
}
216+
return arr;
217+
}
218+
219+
fn measure_avg_compares(arr, n_queries) -> float {
220+
h n = arr_len(arr);
221+
h max_target = arr_get(arr, n - 1) + 5;
222+
phi_pi_fib_reset();
223+
h k = 0;
224+
while k < n_queries {
225+
h target = random_int(0, max_target);
226+
h _idx = phi_pi_fib_nearest(arr, target);
227+
k = k + 1;
228+
}
229+
h s = phi_pi_fib_stats();
230+
return to_float(arr_get(s, 1)) / to_float(arr_get(s, 0));
231+
}
232+
233+
random_seed(42);
234+
print("== (ii) Sublinear scaling of phi_pi_fib_nearest ==");
235+
print(concat_many("(random queries against sorted [1,3,5,...,2N-1]; ",
236+
"200 queries per N)"));
237+
print("");
238+
print(" N avg_compares log_phi(N)");
239+
print(" ----- ------------ ----------");
240+
241+
h sizes = [8, 16, 32, 64, 128, 256, 512, 1024];
242+
h ns = arr_len(sizes);
243+
h si = 0;
244+
while si < ns {
245+
h N = arr_get(sizes, si);
246+
h arr_n = build_sorted_n(N);
247+
h avg = measure_avg_compares(arr_n, 200);
248+
# log_phi(N) = ln(N) / ln(phi)
249+
h lphi = log(to_float(N)) / log(1.6180339887498948);
250+
print(concat_many(" ", N, " ", avg, " ", lphi));
251+
si = si + 1;
252+
}
253+
print("");
254+
print("If avg_compares grows like log_phi(N) (about 1.44 * log2(N)), the");
255+
print("search is doing sublinear work — the gate's cost stays bounded as");
256+
print("the library grows. A linear scan would show avg_compares ~ N/2.");
257+
print("");
258+
259+
# ===========================================================================
260+
# (iii) Sanity vs experiment 6 — same chain, same final state.
261+
# ===========================================================================
262+
263+
print("== (iii) Sanity: same chain, same final state as experiment 6 ==");
264+
print(concat_many("phi-fib gate final state: ", final_main));
265+
print("experiment 6 final state: 9");
266+
print("(should match exactly — both gates pick the same nearest entry");
267+
print("when keys match exactly.)");
268+
print("");
269+
270+
# ===========================================================================
271+
# (iv) Death tolerance, re-measured under phi-fib search.
272+
# ===========================================================================
273+
274+
print("== (iv) Death tolerance under phi_pi_fib_nearest ==");
275+
print("");
276+
print("Kill | final state | shift from baseline");
277+
print("-----+-------------+--------------------");
278+
279+
h kill_idx = 0;
280+
while kill_idx < LIB_SIZE {
281+
h km = alive_kill(alive, kill_idx);
282+
h killed = run_chain_with_phi_fib(initial, chain_main, km);
283+
h shift = killed - final_main;
284+
if shift < 0 { shift = 0 - shift; }
285+
h k_key = arr_get(LIB_KEYS, kill_idx);
286+
h k_op = arr_get(LIB_OPS, kill_idx);
287+
print(concat_many(
288+
" key=", k_key, " (", op_name(k_op), ")",
289+
" -> ", killed,
290+
" delta=", shift
291+
));
292+
kill_idx = kill_idx + 1;
293+
}
294+
print("");
295+
296+
print("== Summary ==");
297+
print("- Four new builtins: phi_pi_fib_search, phi_pi_fib_nearest,");
298+
print(" phi_pi_fib_stats, phi_pi_fib_reset. Audit clean against the VM.");
299+
print("- Comparison counters confirm the search is sublinear in N.");
300+
print("- The compression-gate prototype now uses the real Fibonacci-step");
301+
print(" search instead of a linear scan. Semantics match experiment 6 on");
302+
print(" this library (sanity), death tolerance preserved.");
303+
print("- Ready for experiment 8: learnable routing policy that emits");
304+
print(" chains from inputs (state -> chain_of_keys).");
305+
print("== End ==");

0 commit comments

Comments
 (0)