Skip to content

Commit 20f1112

Browse files
Add 22 new builtins: math, stats, substrate primitives + 25 tests
Part of the 200+ optimization campaign. Three groups: MATH (10 mods): - log2, log10 — base-2 and base-10 logarithms - asin, acos, atan, atan2 — inverse trig functions - hypot(a, b) — Euclidean distance helper - lerp(a, b, t) — linear interpolation primitive ARRAY STATS (9 mods): - arr_mean — arithmetic mean as float - arr_variance — population variance (divides by N) - arr_stddev — sqrt of variance - arr_median — even-length avg of two middle elements - arr_harmonic_mean — n / sum(1/x_i) - arr_geometric_mean — log-sum exp form (overflow-safe) - arr_sum_sq — sum of squared elements - arr_norm — L2 norm of array as vector - arr_dot(a, b) — dot product SUBSTRATE PRIMITIVES (5 mods): - attractor_distance(n) — distance to nearest Fibonacci attractor - nearest_attractor(n) — sign-preserving nearest attractor - largest_attractor_at_most(n) — greedy chunk size helper - crt_residues(pos, moduli) — exposes the E2 CRT-PE construction directly to OMC code - hbit_tension(value) — same as attractor_distance, named to match the experiments-paper vocabulary All 22 are registered in is_known_builtin and HEAL_BUILTIN_NAMES. Compiler infer_type tags them in the right return-type table so the JIT type-specialization picks DivFloat/MulFloat etc. when the math composes float operands. TESTS: 25-test suite in examples/tests/test_new_builtins.omc covering each new builtin against a known-good value or small-tolerance approximation. All 25 pass. DISCOVERED: OMC parser doesn't support scientific-notation float literals (1e-9 is misparsed as int(1) + call(e, -9)). Filed as followup task to add the lexer rule. Workspace: 149 omnimcode-core unit tests + 25 new OMC builtin tests pass. The harmonic library tests (18) still pass too. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 0fc9fa4 commit 20f1112

3 files changed

Lines changed: 472 additions & 1 deletion

File tree

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# =============================================================================
2+
# Tests for the optimization-campaign builtins (math + stats + substrate).
3+
# =============================================================================
4+
# Validates the new builtins added in the 200+ optimization campaign:
5+
# - Math: log2, log10, asin, acos, atan, atan2, hypot, lerp
6+
# - Stats: arr_mean, arr_variance, arr_stddev, arr_median,
7+
# arr_harmonic_mean, arr_geometric_mean, arr_sum_sq,
8+
# arr_norm, arr_dot
9+
# - Substrate: attractor_distance, nearest_attractor,
10+
# largest_attractor_at_most, crt_residues, hbit_tension
11+
#
12+
# Each test asserts an exact known-good value or a small-tolerance
13+
# approximation. Run with: omnimcode-standalone --test test_new_builtins.omc
14+
# =============================================================================
15+
16+
# ---------- Assertion helpers (mirroring test_harmonic_libs.omc) ----------
17+
18+
fn assert_eq(actual, expected, msg) {
19+
if actual != expected {
20+
error(concat_many("FAIL ", msg, ": expected ", expected, " got ", actual));
21+
}
22+
}
23+
24+
fn assert_true(cond, msg) {
25+
if cond != 1 { error(concat_many("FAIL ", msg, ": expected truthy")); }
26+
}
27+
28+
# ---------- Math builtins ----------
29+
30+
fn approx_eq(a, b, tol) {
31+
h diff = a - b;
32+
if diff < 0.0 { diff = 0.0 - diff; }
33+
return diff < tol;
34+
}
35+
36+
fn test_log2() {
37+
h r = log2(8.0);
38+
assert_true(approx_eq(r, 3.0, 0.000000001), "log2(8) should be 3.0");
39+
}
40+
41+
fn test_log10() {
42+
h r = log10(1000.0);
43+
assert_true(approx_eq(r, 3.0, 0.000000001), "log10(1000) should be 3.0");
44+
}
45+
46+
fn test_asin() {
47+
h r = asin(1.0);
48+
h pi_half = pi() / 2.0;
49+
assert_true(approx_eq(r, pi_half, 0.000000001), "asin(1) should be pi/2");
50+
}
51+
52+
fn test_acos() {
53+
h r = acos(0.0);
54+
h pi_half = pi() / 2.0;
55+
assert_true(approx_eq(r, pi_half, 0.000000001), "acos(0) should be pi/2");
56+
}
57+
58+
fn test_atan() {
59+
h r = atan(1.0);
60+
h pi_quarter = pi() / 4.0;
61+
assert_true(approx_eq(r, pi_quarter, 0.000000001), "atan(1) should be pi/4");
62+
}
63+
64+
fn test_atan2() {
65+
h r = atan2(1.0, 1.0);
66+
h pi_quarter = pi() / 4.0;
67+
assert_true(approx_eq(r, pi_quarter, 0.000000001), "atan2(1, 1) should be pi/4");
68+
}
69+
70+
fn test_hypot() {
71+
h r = hypot(3.0, 4.0);
72+
assert_true(approx_eq(r, 5.0, 0.000000001), "hypot(3, 4) should be 5");
73+
}
74+
75+
fn test_lerp_endpoints() {
76+
h r0 = lerp(10.0, 20.0, 0.0);
77+
h r1 = lerp(10.0, 20.0, 1.0);
78+
assert_true(approx_eq(r0, 10.0, 0.000000001), "lerp(a, b, 0) = a");
79+
assert_true(approx_eq(r1, 20.0, 0.000000001), "lerp(a, b, 1) = b");
80+
}
81+
82+
fn test_lerp_midpoint() {
83+
h r = lerp(10.0, 20.0, 0.5);
84+
assert_true(approx_eq(r, 15.0, 0.000000001), "lerp midpoint should be 15");
85+
}
86+
87+
# ---------- Array stats ----------
88+
89+
fn test_arr_mean() {
90+
h r = arr_mean([1, 2, 3, 4, 5]);
91+
assert_true(approx_eq(r, 3.0, 0.000000001), "arr_mean([1..5]) should be 3");
92+
}
93+
94+
fn test_arr_variance() {
95+
# population variance of [2, 4, 4, 4, 5, 5, 7, 9] is 4
96+
h r = arr_variance([2, 4, 4, 4, 5, 5, 7, 9]);
97+
assert_true(approx_eq(r, 4.0, 0.000000001), "variance of textbook example is 4");
98+
}
99+
100+
fn test_arr_stddev() {
101+
h r = arr_stddev([2, 4, 4, 4, 5, 5, 7, 9]);
102+
assert_true(approx_eq(r, 2.0, 0.000000001), "stddev should be sqrt(4) = 2");
103+
}
104+
105+
fn test_arr_median_odd() {
106+
h r = arr_median([7, 1, 5, 3, 9]);
107+
assert_true(approx_eq(r, 5.0, 0.000000001), "median of odd-length set");
108+
}
109+
110+
fn test_arr_median_even() {
111+
h r = arr_median([1, 2, 3, 4]);
112+
assert_true(approx_eq(r, 2.5, 0.000000001), "median of even-length set is mean of middle two");
113+
}
114+
115+
fn test_arr_harmonic_mean() {
116+
# H([1, 2, 4]) = 3 / (1/1 + 1/2 + 1/4) = 3 / 1.75 ≈ 1.7142857...
117+
h r = arr_harmonic_mean([1, 2, 4]);
118+
assert_true(approx_eq(r, 1.7142857142857142, 0.000000001), "harmonic mean of [1,2,4]");
119+
}
120+
121+
fn test_arr_geometric_mean() {
122+
# G([2, 8]) = sqrt(16) = 4
123+
h r = arr_geometric_mean([2, 8]);
124+
assert_true(approx_eq(r, 4.0, 0.000000001), "geometric mean of [2, 8] is 4");
125+
}
126+
127+
fn test_arr_sum_sq() {
128+
h r = arr_sum_sq([1, 2, 3]);
129+
assert_true(approx_eq(r, 14.0, 0.000000001), "sum of squares 1+4+9 = 14");
130+
}
131+
132+
fn test_arr_norm() {
133+
h r = arr_norm([3, 4]);
134+
assert_true(approx_eq(r, 5.0, 0.000000001), "L2 norm of [3, 4] is 5");
135+
}
136+
137+
fn test_arr_dot() {
138+
h r = arr_dot([1, 2, 3], [4, 5, 6]);
139+
assert_true(approx_eq(r, 32.0, 0.000000001), "dot product 1*4 + 2*5 + 3*6 = 32");
140+
}
141+
142+
# ---------- Substrate primitives ----------
143+
144+
fn test_attractor_distance_zero() {
145+
# 89 IS a Fibonacci attractor → distance 0
146+
assert_eq(attractor_distance(89), 0, "89 is on attractor");
147+
}
148+
149+
fn test_attractor_distance_nonzero() {
150+
# 100 nearest attractor is 89 (dist 11) or 144 (dist 44) → 11
151+
assert_eq(attractor_distance(100), 11, "100 → 89, dist 11");
152+
}
153+
154+
fn test_nearest_attractor() {
155+
assert_eq(nearest_attractor(100), 89, "100 nearest = 89");
156+
assert_eq(nearest_attractor(0 - 100), 0 - 89, "-100 nearest = -89 (sign preserved)");
157+
assert_eq(nearest_attractor(89), 89, "exact attractor returns itself");
158+
}
159+
160+
fn test_largest_attractor_at_most() {
161+
assert_eq(largest_attractor_at_most(100), 89, "largest <= 100 is 89");
162+
assert_eq(largest_attractor_at_most(89), 89, "largest <= 89 is 89");
163+
assert_eq(largest_attractor_at_most(0), 0, "largest <= 0 is 0");
164+
}
165+
166+
fn test_crt_residues() {
167+
# CRT-PE construction from experiment 10
168+
h r = crt_residues(42, [5, 8, 13, 21]);
169+
assert_eq(arr_get(r, 0), 2, "42 % 5 = 2");
170+
assert_eq(arr_get(r, 1), 2, "42 % 8 = 2");
171+
assert_eq(arr_get(r, 2), 3, "42 % 13 = 3");
172+
assert_eq(arr_get(r, 3), 0, "42 % 21 = 0");
173+
}
174+
175+
fn test_hbit_tension() {
176+
# Same as attractor_distance — different name for the same primitive.
177+
assert_eq(hbit_tension(89), 0, "on-attractor tension is 0");
178+
assert_eq(hbit_tension(100), 11, "off-attractor tension matches distance");
179+
}

omnimcode-core/src/compiler.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ impl Compiler {
151151
| "pow_int" | "square" | "cube" | "sign" | "to_int"
152152
| "int" | "classify_resonance" | "safe_add" | "safe_sub"
153153
| "safe_mul"
154+
// Substrate primitives — int returns
155+
| "attractor_distance" | "nearest_attractor"
156+
| "largest_attractor_at_most" | "hbit_tension"
154157
// 2026-05-14 stdlib expansion (ints)
155158
| "str_index_of" | "str_starts_with" | "str_ends_with"
156159
| "file_exists" | "write_file" | "gcd" | "lcm"
@@ -159,8 +162,14 @@ impl Compiler {
159162
| "random_int" | "random_seed"
160163
// test runner ints
161164
| "test_failure_count" | "test_record_failure" => Some("int"),
162-
"pow" | "sqrt" | "log" | "exp" | "sin" | "cos" | "tan"
165+
"pow" | "sqrt" | "log" | "log2" | "log10"
166+
| "exp" | "sin" | "cos" | "tan" | "asin" | "acos"
167+
| "atan" | "atan2" | "hypot" | "lerp"
163168
| "tanh" | "erf" | "sigmoid" | "frac" | "clamp"
169+
| "arr_mean" | "arr_variance" | "arr_stddev"
170+
| "arr_median" | "arr_harmonic_mean"
171+
| "arr_geometric_mean" | "arr_sum_sq"
172+
| "arr_norm" | "arr_dot"
164173
| "pi" | "e" | "phi" | "tau" | "phi_inv" | "phi_sq"
165174
| "phi_squared" | "sqrt_2" | "sqrt_5" | "ln_2"
166175
| "to_float" | "float" | "interfere"

0 commit comments

Comments
 (0)