Skip to content

Commit e434626

Browse files
Harmonic-distinctive collections: hset / hpq / hidx in OMC
Three data structures that only make sense in a language with first- class Fibonacci attractors and HIM scores. Written entirely in OMC on top of dict + fold + phi.him primitives — no new VM opcodes required. * harmonic_set — auto-deduplicates near-fold-equivalent values. Two values are equivalent iff their Fibonacci attractors match, so adding 21 and 23 keeps only the first (canonical representative on the 21-attractor). Storage: dict keyed by fold(v), giving O(log n) membership. * harmonic_pq — priority queue ranked by HIM score. Lower HIM = more harmonically dominant. Popping returns the value closest to any Fibonacci attractor first. Storage: dict { padded_him_str: list_of_values_with_that_score }. Lexicographic key sort matches numeric order via 7-digit zero-pad → no per-pop sort step. * harmonic_index — bucketed index keyed by attractor. Lookup folds the query to its attractor and returns every (key, value) pair in that bucket. Sub-linear in the FULL data set; surfaces "fuzzy match by harmonic neighborhood" semantics for free. Demo: 12 user records across 8 attractors; query for user_id=22 returns alice/bob/carol (all on the 21-attractor). The key observation: traditional collections answer "is this exact key here?". Harmonic collections answer "is something near this value here?" — a primitive that has to be built on top in any other language but is the natural shape here. Also fixed: concat_many now uses to_display_string, so passing an array shows "[1, 2, 3]" instead of the verbose "[HInt(1, φ=..., HIM=...), ...]" dump. Aligns with what str_concat already does post-Phase 4. 43/43 functional examples produce identical output under tree-walk and VM. 92/92 unit tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 5dadbf4 commit e434626

2 files changed

Lines changed: 283 additions & 8 deletions

File tree

examples/harmonic_collections.omc

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
# =============================================================================
2+
# Harmonic-distinctive collections: the OMC-only data structures
3+
# =============================================================================
4+
# Three collections that only make sense in a language with first-class
5+
# Fibonacci attractors and HIM scores:
6+
#
7+
# harmonic_set — auto-deduplicates near-fold-equivalent values
8+
# harmonic_pq — priority queue ranked by HIM score (low = dominant)
9+
# harmonic_index — Fibonacci-attractor bucketing for sub-linear lookup
10+
#
11+
# Pure-functional API (each op returns a fresh structure, mirroring the
12+
# OMC convention for arrays/dicts that pass by value). Built on top of
13+
# existing dict + fold + phi.him primitives — no new VM opcodes needed.
14+
#
15+
# Run:
16+
# ./target/release/omnimcode-standalone examples/harmonic_collections.omc
17+
# OMC_VM=1 ./target/release/omnimcode-standalone examples/harmonic_collections.omc
18+
# =============================================================================
19+
20+
# ---- harmonic_set ---------------------------------------------------------
21+
# Two values are "equivalent" iff their Fibonacci attractors match. So 23
22+
# and 21 BOTH live on the 21-attractor; adding both leaves only the first
23+
# (canonical representative). 89 vs 144 stay distinct.
24+
#
25+
# Internal layout: { fold_key_as_string : original_value }. Keeps insertion
26+
# order via dict's BTreeMap (sorted, deterministic) and lets membership
27+
# tests run in O(log n) via dict_has.
28+
29+
fn hset_new() {
30+
return {};
31+
}
32+
33+
fn _hset_key(v) {
34+
# The fold-attractor IS the equivalence class. Stringify because dict
35+
# keys are strings.
36+
return concat_many("", fold(v));
37+
}
38+
39+
fn hset_add(s, v) {
40+
h key = _hset_key(v);
41+
h out = dict_merge(s, {}); # shallow copy
42+
if dict_has(out, key) == 1 {
43+
return out; # already represented on this attractor
44+
}
45+
dict_set(out, key, v);
46+
return out;
47+
}
48+
49+
fn hset_has(s, v) {
50+
return dict_has(s, _hset_key(v));
51+
}
52+
53+
fn hset_len(s) {
54+
return dict_len(s);
55+
}
56+
57+
fn hset_items(s) {
58+
return dict_values(s);
59+
}
60+
61+
# Set union — right-hand wins on attractor collision (matches dict_merge
62+
# semantics so users don't have to remember a different convention).
63+
fn hset_union(a, b) {
64+
return dict_merge(a, b);
65+
}
66+
67+
# ---- harmonic_pq ----------------------------------------------------------
68+
# Priority queue ranked by HIM score. Lower HIM = more harmonically
69+
# dominant (a "purer" value, closer to an attractor). pop returns the
70+
# dominant element.
71+
#
72+
# Internal layout: { him_score_str : list_of_values_with_that_score }.
73+
# A list-of-lists handles HIM ties naturally. Iteration order from
74+
# dict_keys is sorted-string-ascending — which happens to put "0.000"
75+
# before "0.382" before "0.459", giving us the right priority order
76+
# WITHOUT a sort step on each pop. Saves real time on large queues.
77+
78+
fn hpq_new() {
79+
return {};
80+
}
81+
82+
fn _hpq_him_key(v) {
83+
# Use 6-decimal padding so dict's lexicographic key sort matches
84+
# numeric order. phi.him returns a float in [0, 1].
85+
h h_score = phi.him(v);
86+
h scaled = to_int(h_score * 1000000);
87+
# Zero-pad to 7 digits so lexicographic order matches numeric.
88+
h s = concat_many("", scaled);
89+
h n = str_len(s);
90+
h pad_count = 7 - n;
91+
h padded = "";
92+
h i = 0;
93+
while i < pad_count {
94+
padded = padded + "0";
95+
i = i + 1;
96+
}
97+
return padded + s;
98+
}
99+
100+
fn hpq_push(q, v) {
101+
h key = _hpq_him_key(v);
102+
h out = dict_merge(q, {});
103+
h existing = dict_get(out, key, []);
104+
h fresh = arr_concat(existing, [v]);
105+
dict_set(out, key, fresh);
106+
return out;
107+
}
108+
109+
# Returns [value, new_queue]. If empty: [null, q].
110+
fn hpq_pop(q) {
111+
h ks = dict_keys(q);
112+
if arr_len(ks) == 0 {
113+
return [null, q];
114+
}
115+
# First key is the lowest HIM score — that's the dominant element.
116+
h k = arr_get(ks, 0);
117+
h bucket = dict_get(q, k);
118+
h v = arr_get(bucket, 0);
119+
h rest = arr_slice(bucket, 1, arr_len(bucket));
120+
h out = dict_merge(q, {});
121+
if arr_len(rest) == 0 {
122+
dict_del(out, k);
123+
} else {
124+
dict_set(out, k, rest);
125+
}
126+
return [v, out];
127+
}
128+
129+
fn hpq_peek(q) {
130+
h ks = dict_keys(q);
131+
if arr_len(ks) == 0 { return null; }
132+
h bucket = dict_get(q, arr_get(ks, 0));
133+
return arr_get(bucket, 0);
134+
}
135+
136+
fn hpq_len(q) {
137+
h ks = dict_keys(q);
138+
h total = 0;
139+
h i = 0;
140+
while i < arr_len(ks) {
141+
total = total + arr_len(dict_get(q, arr_get(ks, i)));
142+
i = i + 1;
143+
}
144+
return total;
145+
}
146+
147+
# ---- harmonic_index -------------------------------------------------------
148+
# An index that buckets every key by its Fibonacci attractor. Lookup of
149+
# a query key folds the query to its attractor and returns the entire
150+
# bucket — sub-linear in the FULL data set, but linear within an
151+
# attractor. For data that clusters along the Fibonacci spine
152+
# (resonance-aligned domains), this is significantly faster than a
153+
# naive scan AND it surfaces "fuzzy match" semantics for free.
154+
#
155+
# Internal layout: { fold_key_str : list_of_(key, value)_pairs }.
156+
157+
fn hidx_new() {
158+
return {};
159+
}
160+
161+
fn hidx_insert(idx, key, value) {
162+
h k = _hset_key(key);
163+
h out = dict_merge(idx, {});
164+
h existing = dict_get(out, k, []);
165+
h pair = [key, value];
166+
h fresh = arr_concat(existing, [pair]);
167+
dict_set(out, k, fresh);
168+
return out;
169+
}
170+
171+
# Returns the array of (key, value) pairs whose key folds to the same
172+
# attractor as `query`. Empty array if no match.
173+
fn hidx_lookup(idx, query) {
174+
return dict_get(idx, _hset_key(query), []);
175+
}
176+
177+
# All values across all buckets. Useful for length / iteration.
178+
fn hidx_size(idx) {
179+
h ks = dict_keys(idx);
180+
h total = 0;
181+
h i = 0;
182+
while i < arr_len(ks) {
183+
total = total + arr_len(dict_get(idx, arr_get(ks, i)));
184+
i = i + 1;
185+
}
186+
return total;
187+
}
188+
189+
# How many distinct attractors hold data? Useful for sparsity diagnostics.
190+
fn hidx_attractors(idx) {
191+
return dict_len(idx);
192+
}
193+
194+
# =============================================================================
195+
# Demo
196+
# =============================================================================
197+
198+
println("=== harmonic_set: dedup-by-attractor ======================");
199+
h s = hset_new();
200+
s = hset_add(s, 21);
201+
s = hset_add(s, 23); # folds to 21 — duplicate, ignored
202+
s = hset_add(s, 89);
203+
s = hset_add(s, 91); # folds to 89 — duplicate, ignored
204+
s = hset_add(s, 144);
205+
s = hset_add(s, 200); # folds to 233 — distinct
206+
println(concat_many(" added 6 values, len = ", hset_len(s),
207+
" (because 23 folds to 21, 91 folds to 89)"));
208+
println(concat_many(" members: ", hset_items(s)));
209+
println(concat_many(" has(20)? ", hset_has(s, 20),
210+
" (folds to 21, present)"));
211+
println(concat_many(" has(50)? ", hset_has(s, 50),
212+
" (folds to 55, absent)"));
213+
println("");
214+
215+
println("=== harmonic_pq: HIM-ranked priority queue ================");
216+
h q = hpq_new();
217+
q = hpq_push(q, 100); # off-attractor → high HIM
218+
q = hpq_push(q, 89); # exact Fibonacci → HIM = 0
219+
q = hpq_push(q, 50); # near 55 → middling HIM
220+
q = hpq_push(q, 144); # exact Fibonacci → HIM = 0
221+
q = hpq_push(q, 23); # near 21
222+
println(concat_many(" pushed 5 values, len = ", hpq_len(q)));
223+
println(concat_many(" peek (most dominant) = ", hpq_peek(q)));
224+
println(" popping in HIM order:");
225+
h still = q;
226+
while hpq_len(still) > 0 {
227+
h pair = hpq_pop(still);
228+
h v = arr_get(pair, 0);
229+
still = arr_get(pair, 1);
230+
println(concat_many(" pop -> ", v, " (HIM=", phi.him(v), ")"));
231+
}
232+
println("");
233+
234+
println("=== harmonic_index: attractor-bucketed lookup =============");
235+
h idx = hidx_new();
236+
# Insert 12 user records keyed by user_id (the harmonic value).
237+
idx = hidx_insert(idx, 21, "alice");
238+
idx = hidx_insert(idx, 23, "bob"); # same attractor as alice (21)
239+
idx = hidx_insert(idx, 19, "carol"); # also folds to 21
240+
idx = hidx_insert(idx, 89, "dave");
241+
idx = hidx_insert(idx, 90, "eve"); # folds to 89
242+
idx = hidx_insert(idx, 144, "frank");
243+
idx = hidx_insert(idx, 142, "grace"); # folds to 144
244+
idx = hidx_insert(idx, 233, "heidi");
245+
idx = hidx_insert(idx, 55, "ivan");
246+
idx = hidx_insert(idx, 8, "judy");
247+
idx = hidx_insert(idx, 3, "kim");
248+
idx = hidx_insert(idx, 377, "leo");
249+
250+
println(concat_many(" total records: ", hidx_size(idx),
251+
" across ", hidx_attractors(idx), " attractors"));
252+
253+
println(" query: who is near user_id 22?");
254+
h hits = hidx_lookup(idx, 22);
255+
h k = 0;
256+
while k < arr_len(hits) {
257+
h pair = arr_get(hits, k);
258+
println(concat_many(" user_id=", arr_get(pair, 0),
259+
" -> ", arr_get(pair, 1)));
260+
k = k + 1;
261+
}
262+
263+
println(" query: who is near user_id 91?");
264+
h hits2 = hidx_lookup(idx, 91);
265+
h k2 = 0;
266+
while k2 < arr_len(hits2) {
267+
h pair = arr_get(hits2, k2);
268+
println(concat_many(" user_id=", arr_get(pair, 0),
269+
" -> ", arr_get(pair, 1)));
270+
k2 = k2 + 1;
271+
}
272+
273+
println(" query: who is near user_id 999? (folds off-spine to 610)");
274+
h hits3 = hidx_lookup(idx, 999);
275+
println(concat_many(" -> ", arr_len(hits3), " hits"));
276+
277+
println("");
278+
println("=== Done ====================================================");

omnimcode-core/src/interpreter.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,17 +2103,14 @@ impl Interpreter {
21032103
// Variadic: concat_many(a, b) / concat_many(a, b, c) / concat_many(a, b, c, d).
21042104
// Renders numerics as bare values (89, 1.5) not as HInt(...) display form.
21052105
"concat_many" => {
2106+
// to_display_string for every arg — produces "42" not
2107+
// "HInt(42, φ=..., HIM=...)" and recurses correctly
2108+
// through arrays/dicts so `concat_many("xs: ", xs)`
2109+
// shows "[1, 2, 3]" not the verbose Array dump.
21062110
let mut out = String::new();
21072111
for a in args {
21082112
let v = self.eval_expr(a)?;
2109-
let s = match v {
2110-
Value::HInt(h) => h.value.to_string(),
2111-
Value::HFloat(f) => format!("{}", f),
2112-
Value::String(s) => s,
2113-
Value::Bool(b) => b.to_string(),
2114-
other => other.to_string(),
2115-
};
2116-
out.push_str(&s);
2113+
out.push_str(&v.to_display_string());
21172114
}
21182115
Ok(Value::String(out))
21192116
}

0 commit comments

Comments
 (0)