Skip to content

Commit e857be1

Browse files
Bidirectional Python bridge + 4 integration libs + harmonic ML pipeline
Three additions, all building on the py_* foundation: 1. Python ↔ OMC callbacks (py_callback) `py_callback("omc_fn_name")` returns a Python callable that wraps the OMC function. Python code can then invoke it like any other PyCallable — `df.apply(omc_fn)`, `np.vectorize(omc_fn)`, pandas/sklearn/torch hooks all work. Architecture: thread_local INTERP_PTR (raw pointer) is set by call_function / vm_call_builtin BEFORE invoking a host_builtin handler, cleared on return. The OmcCallback pyclass uses `with_active_interp(|interp| interp.call_function_with_values(...))` to dispatch back into the live interpreter. SAFETY contract documented inline; valid only inside a host call (which is the only time you'd be running OMC code that created the callback). `null` / `true` / `false` as expression-position literals landed alongside (both engines) — sklearn.fit(model, X, y) needed to pass `null` for unsupervised cases, and the parser was treating them as undefined variables. 2. Four new OMC integration libraries (written in OMC, not Rust) * examples/lib/requests.omc — get/post/put/delete, json, headers, ok-test, fetch_json/fetch_text helpers. ~50 lines. * examples/lib/sqlite.omc — connect/execute/query/commit, plus execute_with for parameter binding, query_one, tables() for schema introspection. Real in-memory SQL from OMC. ~80 lines. * examples/lib/sklearn.omc — KMeans, LinearRegression, Logistic, RandomForest{Classifier,Regressor}, train_test_split (with kwargs!), StandardScaler, accuracy/r2/confusion_matrix, load_iris/wine/breast_cancer datasets. ~110 lines. * examples/lib/torch.omc — tensor/zeros/ones/randn, add/sub/mul/matmul, nn.Linear, SGD/Adam optimizers, MSE loss, backward(). ~80 lines. (Pattern demo; torch is a heavy install.) Total: ~320 lines of OMC giving access to four major Python ecosystems. Same pattern user can fork for their own libs. 3. py_call_kw / py_call_fn_kw sklearn (and many Python APIs) distinguish positional from keyword args. `train_test_split(X, y, test_size=0.3)` was passing 0.3 as a third positional array → "Input should have at least 1 dimension" error. Added kwargs variants that take an OMC dict as the kwargs map. Both engines. 4. examples/datascience/harmonic_ml.omc — the end-to-end pipeline Loads sklearn wine dataset (178 samples, 13 features). Engineers a new harmonic_signature feature (sum of harmony_value across each row). Trains baseline RF + augmented RF. Demonstrates Python→OMC callback by registering harmonic_signature_at as a Python callable and using numpy.vectorize to apply it across all 178 sample indices. Both classifiers reached 100% on wine (RF is robust enough that the harmonic feature wasn't load-bearing on this easy dataset); the pipeline mechanics are what matters — OMC ↔ Python ↔ OMC, real ML, real data, no Rust needed for any of it. Smoke tests: requests fetched github zen API, sqlite stored and queried 3 rows correctly, sklearn trained RF on iris with 93.3% accuracy, py_callback let numpy.vectorize call OMC's harmony_score across an 11-element array. 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 49dd7b0 commit e857be1

8 files changed

Lines changed: 814 additions & 15 deletions

File tree

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# =============================================================================
2+
# Harmonic ML pipeline: OMC + scikit-learn end-to-end
3+
# =============================================================================
4+
# The full story:
5+
# 1. Load a real classification dataset (sklearn's wine, 178 samples)
6+
# 2. ENGINEER NEW HARMONIC FEATURES with an OMC-only operation
7+
# (harmony_value of each feature — a real number not in the
8+
# original Python ecosystem)
9+
# 3. Train a classifier on (original + harmonic) features
10+
# 4. Compare to baseline (original features only)
11+
# 5. Use a Python ↔ OMC callback to score each sample's "harmonic
12+
# signature" via numpy.vectorize
13+
#
14+
# This is the harmonic preprocessing pipeline. Replace step 2 with
15+
# any OMC-distinctive transform (attractor partition, HIM ranking,
16+
# fold-and-mod) and you have a model that's measurably aware of
17+
# Fibonacci structure in the input — something no native Python
18+
# library exposes as a primitive.
19+
#
20+
# Run:
21+
# PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 cargo build --release \
22+
# --bin omnimcode-standalone --features python-embed
23+
# OMC_PYTHON=1 ./target/release/omnimcode-standalone \
24+
# examples/datascience/harmonic_ml.omc
25+
# =============================================================================
26+
27+
import "examples/lib/sklearn.omc" as sk;
28+
import "examples/lib/np.omc" as np;
29+
30+
println("=== Harmonic ML pipeline (OMC + scikit-learn) ===");
31+
println("");
32+
33+
# ---- 1. Load wine dataset --------------------------------------------------
34+
35+
h wine = sk.load_wine();
36+
h X_orig = arr_get(wine, 0);
37+
h y = arr_get(wine, 1);
38+
println(concat_many("loaded sklearn wine: ", arr_len(X_orig), " samples, ",
39+
arr_len(arr_get(X_orig, 0)), " features"));
40+
println("");
41+
42+
# ---- 2. Harmonic feature engineering ---------------------------------------
43+
# For each row, compute one new feature: the SUM of harmony_values across
44+
# the original features. This is a single scalar that captures "how
45+
# Fibonacci-aligned is this sample's feature vector overall." Cheap to
46+
# compute, OMC-distinctive, completely absent from the sklearn pipeline.
47+
48+
fn harmonic_signature(row) {
49+
# row is an OMC array of 13 floats. Convert each to nearest int and
50+
# sum the harmony_value scores.
51+
h sum = 0.0;
52+
h i = 0;
53+
while i < arr_len(row) {
54+
h v = to_int(arr_get(row, i) * 100); # scale into integer space
55+
sum = sum + harmony_value(v);
56+
i = i + 1;
57+
}
58+
return sum;
59+
}
60+
61+
# Build the augmented feature matrix: each row = original + 1 harmonic feature.
62+
fn augment(matrix) {
63+
h out = [];
64+
h i = 0;
65+
while i < arr_len(matrix) {
66+
h row = arr_get(matrix, i);
67+
h h_score = harmonic_signature(row);
68+
h new_row = arr_concat(row, [h_score]);
69+
arr_push(out, new_row);
70+
i = i + 1;
71+
}
72+
return out;
73+
}
74+
75+
h t0 = now_ms();
76+
h X_aug = augment(X_orig);
77+
h t1 = now_ms();
78+
println(concat_many("augmented features in ", t1 - t0,
79+
" ms (added 1 harmonic_signature column)"));
80+
println("");
81+
82+
# ---- 3a. Baseline classifier on original features -------------------------
83+
84+
h split_o = sk.train_test_split(X_orig, y, 0.3);
85+
h Xo_train = arr_get(split_o, 0);
86+
h Xo_test = arr_get(split_o, 1);
87+
h yo_train = arr_get(split_o, 2);
88+
h yo_test = arr_get(split_o, 3);
89+
90+
h model_o = sk.random_forest_classifier(50);
91+
sk.fit(model_o, Xo_train, yo_train);
92+
h preds_o = sk.predict(model_o, Xo_test);
93+
h acc_o = sk.accuracy_score(yo_test, preds_o);
94+
println(concat_many("baseline RF (13 features): accuracy = ", acc_o));
95+
96+
# ---- 3b. Augmented classifier on (original + harmonic) features ----------
97+
98+
h split_a = sk.train_test_split(X_aug, y, 0.3);
99+
h Xa_train = arr_get(split_a, 0);
100+
h Xa_test = arr_get(split_a, 1);
101+
h ya_train = arr_get(split_a, 2);
102+
h ya_test = arr_get(split_a, 3);
103+
104+
h model_a = sk.random_forest_classifier(50);
105+
sk.fit(model_a, Xa_train, ya_train);
106+
h preds_a = sk.predict(model_a, Xa_test);
107+
h acc_a = sk.accuracy_score(ya_test, preds_a);
108+
println(concat_many("with harmonic feature (14): accuracy = ", acc_a));
109+
println("");
110+
111+
# ---- 4. Bonus: OMC callback driven by numpy.vectorize ---------------------
112+
# Bind harmonic_signature to a Python callable, then have numpy apply
113+
# it across a 1-D array of indices to compute the signature for every
114+
# sample. Demonstrates Python → OMC dispatch at scale.
115+
116+
fn harmonic_signature_at(idx) {
117+
return harmonic_signature(arr_get(X_orig, idx));
118+
}
119+
120+
h cb = py_callback("harmonic_signature_at");
121+
h np_h = np._np();
122+
h vec = py_call(np_h, "vectorize", [cb]);
123+
124+
# Build index range 0..nsamples in numpy and vectorize-apply.
125+
h idx_range = np.arange(arr_len(X_orig));
126+
h all_sigs = py_call_fn(vec, [idx_range]);
127+
128+
println(concat_many("harmonic signatures computed via Python→OMC callback (",
129+
arr_len(all_sigs), " samples)"));
130+
println(concat_many(" min: ", np.np_min(all_sigs)));
131+
println(concat_many(" max: ", np.np_max(all_sigs)));
132+
println(concat_many(" mean: ", np.mean(all_sigs)));
133+
println(concat_many(" std: ", np.std(all_sigs)));
134+
135+
println("");
136+
println("=== Done ===");

examples/lib/requests.omc

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# =============================================================================
2+
# requests bridge for OMC — HTTP in 30 lines
3+
# =============================================================================
4+
# Wraps python's `requests` library. OMC programs gain real HTTP:
5+
# fetch APIs, post JSON, scrape pages, anything you'd do in Python.
6+
#
7+
# Usage:
8+
# import "examples/lib/requests.omc" as r;
9+
# h resp = r.get("https://api.github.com/zen");
10+
# println(r.text(resp));
11+
# =============================================================================
12+
13+
h _RQ_HANDLE = 0;
14+
15+
fn _rq() {
16+
if _RQ_HANDLE == 0 {
17+
_RQ_HANDLE = py_import("requests");
18+
}
19+
return _RQ_HANDLE;
20+
}
21+
22+
# ---- Verbs (return Response handles) -------------------------------------
23+
24+
fn get(url) { return py_call(_rq(), "get", [url]); }
25+
fn post(url, data) { return py_call(_rq(), "post", [url, data]); }
26+
fn put(url, data) { return py_call(_rq(), "put", [url, data]); }
27+
fn delete(url) { return py_call(_rq(), "delete", [url]); }
28+
fn head(url) { return py_call(_rq(), "head", [url]); }
29+
30+
# ---- Response inspection -------------------------------------------------
31+
32+
fn status(resp) { return py_get(resp, "status_code"); }
33+
fn text(resp) { return py_get(resp, "text"); }
34+
fn json(resp) { return py_call(resp, "json", []); }
35+
fn headers(resp) { return py_get(resp, "headers"); }
36+
fn url_of(resp) { return py_get(resp, "url"); }
37+
fn ok(resp) {
38+
h s = status(resp);
39+
if s >= 200 {
40+
if s < 300 {
41+
return 1;
42+
}
43+
}
44+
return 0;
45+
}
46+
47+
# ---- Convenience: one-line GET-and-parse-JSON ----------------------------
48+
# Returns the parsed JSON as an OMC dict/array, or null on non-200.
49+
fn fetch_json(url) {
50+
h resp = get(url);
51+
if ok(resp) == 1 {
52+
return json(resp);
53+
}
54+
return null;
55+
}
56+
57+
# Returns response text or null.
58+
fn fetch_text(url) {
59+
h resp = get(url);
60+
if ok(resp) == 1 {
61+
return text(resp);
62+
}
63+
return null;
64+
}

examples/lib/sklearn.omc

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# =============================================================================
2+
# scikit-learn bridge for OMC — classical ML
3+
# =============================================================================
4+
# Wraps the most common scikit-learn estimators. Pattern: each fn
5+
# returns a fitted-or-untrained MODEL HANDLE; OMC code calls .fit and
6+
# .predict via py_call. Lazy module imports per submodule.
7+
#
8+
# Usage:
9+
# import "examples/lib/sklearn.omc" as sk;
10+
# h X = [[1, 2], [3, 4], [5, 6]];
11+
# h y = [0, 1, 1];
12+
# h model = sk.kmeans(2);
13+
# sk.fit(model, X, y);
14+
# h preds = sk.predict(model, X);
15+
# =============================================================================
16+
17+
h _CLUSTER = 0;
18+
h _LINEAR = 0;
19+
h _ENSEMBLE = 0;
20+
h _MODEL_SEL = 0;
21+
h _PREPROC = 0;
22+
h _METRICS = 0;
23+
24+
fn _cluster() { if _CLUSTER == 0 { _CLUSTER = py_import("sklearn.cluster"); } return _CLUSTER; }
25+
fn _linear() { if _LINEAR == 0 { _LINEAR = py_import("sklearn.linear_model"); } return _LINEAR; }
26+
fn _ensemble() { if _ENSEMBLE == 0 { _ENSEMBLE = py_import("sklearn.ensemble"); } return _ENSEMBLE; }
27+
fn _model_sel() { if _MODEL_SEL == 0 { _MODEL_SEL = py_import("sklearn.model_selection"); } return _MODEL_SEL; }
28+
fn _preproc() { if _PREPROC == 0 { _PREPROC = py_import("sklearn.preprocessing"); } return _PREPROC; }
29+
fn _metrics() { if _METRICS == 0 { _METRICS = py_import("sklearn.metrics"); } return _METRICS; }
30+
31+
# ---- Models (constructors return untrained model handle) -----------------
32+
33+
fn kmeans(n_clusters) {
34+
h cls = py_get(_cluster(), "KMeans");
35+
return py_call_fn(cls, [n_clusters]);
36+
}
37+
38+
fn linear_regression() {
39+
h cls = py_get(_linear(), "LinearRegression");
40+
return py_call_fn(cls, []);
41+
}
42+
43+
fn logistic_regression() {
44+
h cls = py_get(_linear(), "LogisticRegression");
45+
return py_call_fn(cls, []);
46+
}
47+
48+
fn random_forest_classifier(n_estimators) {
49+
h cls = py_get(_ensemble(), "RandomForestClassifier");
50+
return py_call_fn(cls, [n_estimators]);
51+
}
52+
53+
fn random_forest_regressor(n_estimators) {
54+
h cls = py_get(_ensemble(), "RandomForestRegressor");
55+
return py_call_fn(cls, [n_estimators]);
56+
}
57+
58+
# ---- Universal fit/predict surface ---------------------------------------
59+
# Every sklearn estimator has .fit(X, y) and .predict(X). Some (KMeans,
60+
# clustering) are unsupervised — pass null for y and we'll skip it.
61+
62+
fn fit(model, X, y) {
63+
if y == null {
64+
py_call(model, "fit", [X]);
65+
} else {
66+
py_call(model, "fit", [X, y]);
67+
}
68+
return model;
69+
}
70+
71+
fn predict(model, X) {
72+
return py_call(model, "predict", [X]);
73+
}
74+
75+
fn score(model, X, y) {
76+
return py_call(model, "score", [X, y]);
77+
}
78+
79+
# ---- Train/test split ----------------------------------------------------
80+
# Returns [X_train, X_test, y_train, y_test] as OMC arrays.
81+
# test_size is a kwarg in sklearn's API — passing it positionally
82+
# would make it a third array. py_call_fn_kw handles the split.
83+
fn train_test_split(X, y, test_size) {
84+
h tts = py_get(_model_sel(), "train_test_split");
85+
return py_call_fn_kw(tts, [X, y], {"test_size": test_size});
86+
}
87+
88+
# ---- Preprocessing -------------------------------------------------------
89+
90+
fn standard_scaler() {
91+
h cls = py_get(_preproc(), "StandardScaler");
92+
return py_call_fn(cls, []);
93+
}
94+
95+
fn fit_transform(scaler, X) {
96+
return py_call(scaler, "fit_transform", [X]);
97+
}
98+
99+
fn transform(scaler, X) {
100+
return py_call(scaler, "transform", [X]);
101+
}
102+
103+
# ---- Metrics --------------------------------------------------------------
104+
105+
fn accuracy_score(y_true, y_pred) {
106+
return py_call(_metrics(), "accuracy_score", [y_true, y_pred]);
107+
}
108+
109+
fn r2_score(y_true, y_pred) {
110+
return py_call(_metrics(), "r2_score", [y_true, y_pred]);
111+
}
112+
113+
fn confusion_matrix(y_true, y_pred) {
114+
return py_call(_metrics(), "confusion_matrix", [y_true, y_pred]);
115+
}
116+
117+
# ---- Built-in datasets (handy for demos) --------------------------------
118+
# Each load_* returns a Bunch; we extract data + target as OMC arrays.
119+
120+
fn _datasets() { return py_import("sklearn.datasets"); }
121+
122+
fn load_iris() {
123+
# sklearn returns a Bunch — dict subclass — so py_to_omc auto-
124+
# converts to an OMC dict. Use dict_get, not py_get.
125+
h ds = py_call(_datasets(), "load_iris", []);
126+
return [dict_get(ds, "data"), dict_get(ds, "target")];
127+
}
128+
129+
fn load_wine() {
130+
h ds = py_call(_datasets(), "load_wine", []);
131+
return [dict_get(ds, "data"), dict_get(ds, "target")];
132+
}
133+
134+
fn load_breast_cancer() {
135+
h ds = py_call(_datasets(), "load_breast_cancer", []);
136+
return [dict_get(ds, "data"), dict_get(ds, "target")];
137+
}

0 commit comments

Comments
 (0)