Skip to content

Commit 5ec7857

Browse files
committed
MeTTa TS 1.1.2
1 parent a4f547c commit 5ec7857

36 files changed

Lines changed: 1494 additions & 448 deletions

README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -282,23 +282,23 @@ The pure-MeTTa path stays TypeScript throughout, with no escape to native code.
282282

283283
### Head-to-head with PeTTa
284284

285-
A reproducible benchmark ([`packages/node/bench/corpus-bench.mjs`](packages/node/bench/corpus-bench.mjs)) runs the PeTTa example corpus through both engines as subprocesses and checks each program's embedded `(test …)` assertions. On the Hyperon-faithful subset (host-FFI examples and PeTTa-only execution-model examples are excluded, with the reason recorded for each), MeTTa TS passes 97 of the shared programs and is **faster than PeTTa on all 97**, median ~2x, on SWI-Prolog's GMP-backed integers, from pure TypeScript.
285+
A reproducible benchmark ([`packages/node/bench/corpus-bench.mjs`](packages/node/bench/corpus-bench.mjs)) runs the PeTTa example corpus through both engines as subprocesses and checks each program's embedded `(test …)` assertions. On the Hyperon-faithful subset (host-FFI examples and PeTTa-only execution-model examples are excluded, with the reason recorded for each), MeTTa TS passes 98 of the shared programs and is **faster than PeTTa on all 98**, median 1.82x and geomean 1.85x, on SWI-Prolog's GMP-backed integers, from pure TypeScript. Both-pass totals are PeTTa 28.8s and MeTTa TS 15.9s.
286286

287287
A representative slice (wall-clock, subprocess including startup; `speedup` = PeTTa / MeTTa TS):
288288

289289
| Program | PeTTa | MeTTa TS | Speedup |
290290
| ------------------ | ------: | -------: | -------: |
291-
| `peano` | 1692 ms | 220 ms | **7.7×** |
292-
| `fib` | 456 ms | 79 ms | **5.8×** |
293-
| `fibadd` | 459 ms | 84 ms | **5.5×** |
294-
| `peanofast` | 520 ms | 112 ms | **4.6×** |
295-
| `tilepuzzle` | 1554 ms | 402 ms | **3.9×** |
296-
| `he_minimalmetta` | 1807 ms | 484 ms | 3.7× |
297-
| `matespacefast` | 4258 ms | 1890 ms | 2.3× |
298-
| `factorial` | 166 ms | 76 ms | 2.2× |
299-
| `permutations` | 889 ms | 451 ms | 2.0× |
300-
| `nilbc` | 713 ms | 399 ms | **1.8×** |
301-
| `hyperpose_primes` | 1100 ms | 1009 ms | 1.1× |
291+
| `peano` | 1588 ms | 306 ms | **5.19×** |
292+
| `fib` | 454 ms | 88 ms | **5.14×** |
293+
| `fibadd` | 451 ms | 100 ms | **4.53×** |
294+
| `peanofast` | 516 ms | 114 ms | **4.52×** |
295+
| `tilepuzzle` | 1602 ms | 426 ms | **3.76×** |
296+
| `permutations` | 867 ms | 483 ms | 1.80× |
297+
| `factorial` | 160 ms | 91 ms | 1.76× |
298+
| `he_minimalmetta` | 1825 ms | 1065 ms | 1.71× |
299+
| `matespacefast` | 4348 ms | 3043 ms | 1.43× |
300+
| `nilbc` | 761 ms | 709 ms | 1.07× |
301+
| `hyperpose_primes` | 1116 ms | 1062 ms | 1.05× |
302302

303303
The full per-program table is in [`RESULTS-corpus.md`](packages/node/bench/RESULTS-corpus.md).
304304

@@ -310,16 +310,16 @@ That speed comes from general engine work:
310310
- an O(1)-stack worklist for nondeterminism;
311311
- ground-atom type memoisation;
312312
- an exact-match ground-fact index;
313-
- automatic tabling of pure functions, including ones defined at runtime (via rule-set-versioned keys);
313+
- bounded automatic tabling of pure overlapping-recursive functions, with structural token keys and runtime rule-versioned entries;
314314
- a native-code compiler for the pure deterministic int/bool/tuple subset, with tail-recursion compiled to loops and PeTTa-style **higher-order specialisation** so a function passed as an argument (e.g. `iterate`'s `$step`) is bound and compiled rather than interpreted;
315315
- a compiler for **nondeterministic `let*`-chain functions** (the backward-chainer class): a multi-equation function whose clause bodies chain space matches and recursive calls compiles to a clause-major depth-first search, the same fragment PeTTa hands to Prolog's clause alternatives;
316316
- a compiler for **add-atom saturation loops**: the add-if-absent idiom becomes one exact-membership probe plus append, and a single-branch `case` over a space match becomes a snapshot-and-thread loop with Empty-pruned branches.
317317

318318
Every one of these is verified against the 270-assertion Hyperon oracle and the LeaTTa differential; all are byte-identical except the nondeterministic compiler, whose results are alpha-equivalent (fresh variables get different gensym numbers, consistently renamed, deterministic run to run).
319319

320-
The last holdouts fell in order. `permutations` is a 28-relation conjunctive `(length (collapse (match &self (, …) …)))`: MeTTa TS folds the worst-case-optimal join and counts each solution rather than materialising the ~360k answer atoms, which drops it from 3.6 s to 0.45 s, under PeTTa's 0.85 s. `hyperpose_primes` races `(once (hyperpose …))` across Node worker threads. `nilbc` is a dependently-typed backward chainer: compiling its clauses to a collect-all search with the interpreter's own unification takes it from 2.2 s to 0.40 s, under PeTTa's 0.71 s. `peano`, the final one, is an impure dedup-build loop: compiling its saturation step (a `case` over the space with add-if-absent branches) to membership probes on the exact-match index takes it from 2.7 s to 0.22 s, under PeTTa's 1.69 s. The remaining parity work (PLN/NARS library ports, PeTTa-only execution-model examples) is tracked in [`packages/node/bench/TODO-parity.md`](packages/node/bench/TODO-parity.md).
320+
The last holdouts fell in order. `permutations` is a 28-relation conjunctive `(length (collapse (match &self (, …) …)))`: MeTTa TS folds the worst-case-optimal join and counts each solution rather than materialising the ~360k answer atoms, which brings the current corpus run to 483 ms, under PeTTa's 867 ms. `hyperpose_primes` races `(once (hyperpose …))` across Node worker threads. `nilbc` is a dependently-typed backward chainer: compiling its clauses to a collect-all search with the interpreter's own unification brings the current run to 709 ms, just under PeTTa's 761 ms. `peano`, the final one, is an impure dedup-build loop: compiling its saturation step (a `case` over the space with add-if-absent branches) to membership probes on the exact-match index brings the current run to 306 ms, under PeTTa's 1588 ms. The remaining parity work (PLN/NARS library ports, PeTTa-only execution-model examples) is tracked in [`packages/node/bench/TODO-parity.md`](packages/node/bench/TODO-parity.md).
321321

322-
`matespace`/`matespace2` are **PeTTa-specific** and excluded from the faithful subset. Their expected counts, 1063919 and 1297533, are produced only by PeTTa's compilation to Prolog: native backtracking over a globally-persistent atomspace, with duplicate adds pruned by failure, which is not minimal-MeTTa semantics. Run through `hyperon-experimental` itself, `(collapse (mate-space-demo K))` is empty, and LeaTTa agrees. PeTTa, real Hyperon, and MeTTa TS each compute a different result for the same program, so no Hyperon-faithful engine reproduces PeTTa's number. The faithful rewrite of the same workload is `matespacefast`, which uses deterministic tuple recursion instead of a `case`-driven non-deterministic build. MeTTa TS runs it about 2.0× faster than PeTTa, byte-identical.
322+
`matespace`/`matespace2` are **PeTTa-specific** and excluded from the faithful subset. Their expected counts, 1063919 and 1297533, are produced only by PeTTa's compilation to Prolog: native backtracking over a globally-persistent atomspace, with duplicate adds pruned by failure, which is not minimal-MeTTa semantics. Run through `hyperon-experimental` itself, `(collapse (mate-space-demo K))` is empty, and LeaTTa agrees. PeTTa, real Hyperon, and MeTTa TS each compute a different result for the same program, so no Hyperon-faithful engine reproduces PeTTa's number. The faithful rewrite of the same workload is `matespacefast`, which uses deterministic tuple recursion instead of a `case`-driven non-deterministic build. MeTTa TS runs it about 1.4× faster than PeTTa in the latest corpus run, byte-identical.
323323

324324
## Provenance
325325

RELEASE_NOTES.md

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# MeTTa TS 1.1.1
1+
# MeTTa TS 1.1.2
22

33
A pure-TypeScript implementation of [MeTTa](https://metta-lang.dev), the
44
OpenCog Hyperon language. The core engine runs in the browser, Node, Deno, Bun,
@@ -10,19 +10,27 @@ asks for them.
1010

1111
This release is prepared on Linux with Node 22 and pnpm 11. The release gate
1212
builds every package, typechecks the workspace, runs the full Vitest suite,
13-
builds the GitHub Pages documentation, runs the live Python, Pyodide,
14-
SWI-Prolog, and SWI-WASM adapter checks, and runs the benchmark suite before
15-
tagging.
16-
17-
The Hyperon experimental conformance run is unchanged from the previous
18-
host-import slice. Core/ST reports 431 passed, 77 failed, 60 expected failures,
19-
and 0 skipped. Full/ST with `fileio,json,random` reports 470 passed, 174 failed,
20-
4 expected failures, and 26 skipped. The remaining gaps are the tracked
21-
parser/directive, kernel, typing, stdlib, feature, and concurrency divergences,
22-
so this release should not be described as full Hyperon conformance.
13+
builds the GitHub Pages documentation, runs the oracle checks, and runs the
14+
benchmark suite before tagging. Optional Python, Pyodide, SWI-Prolog, and
15+
SWI-WASM adapters are built and covered by their available tests, with live
16+
runtime checks depending on the host tools installed in the release
17+
environment.
18+
19+
The current conformance rerun reports Core/ST at 431 passed, 77 failed, 60
20+
expected failures, and 0 skipped. Full/ST with `fileio,json,random` reports 470
21+
passed, 174 failed, 4 expected failures, and 26 skipped. The remaining gaps are
22+
the tracked parser/directive, kernel, typing, stdlib, feature, and concurrency
23+
divergences, so this release should not be described as full Hyperon
24+
conformance.
2325

2426
## What's new
2527

28+
### Integrated host interop release train
29+
30+
This release includes the full local host-interop train that followed 1.1.0:
31+
browser host adapters, Python and Prolog runtime splits, SWI-WASM, Pyodide,
32+
source runners, async effects, CLI checks, and the eDSL host builders.
33+
2634
### Browser Python and Prolog
2735

2836
`@metta-ts/browser/host` composes optional host runtimes into the browser
@@ -92,6 +100,25 @@ pyCall("math.add", 40, 2); // (py-call (math.add 40 2))
92100
prologCall(["edge", "alice", x]); // (prolog-call (edge alice $x))
93101
```
94102

103+
### Bounded structural tabling
104+
105+
Automatic tabling now uses structural token keys and a bounded table space
106+
instead of recursive printed-form keys. Pure overlapping-recursive calls can be
107+
memoized without table growth being unbounded:
108+
109+
- completed pure entries are capped and LRU-evictable;
110+
- interner resets invalidate old opaque table keys;
111+
- runtime rules are version-keyed;
112+
- impure, meta, state, import, `match`, `collapse`, and concurrency paths stay
113+
outside the table cache;
114+
- direct active variant recursion uses local-linear completion for finite answer
115+
sets;
116+
- non-cyclic calls keep exact ordered-bag memoization.
117+
118+
The stale-key path reported during release review is covered by a regression:
119+
old tail-call pending keys cannot write into a new interner generation after a
120+
table-space reset.
121+
95122
## Install
96123

97124
```bash

heatmap-example.png

-148 KB
Binary file not shown.

heatmap-mapper.png

-152 KB
Binary file not shown.

packages/browser/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,53 @@ await runner.dispose();
7474
See `examples/browser-interop` for a runnable smoke check and a bundle isolation
7575
check.
7676

77+
For embedders that already resolved imports, `@metta-ts/browser/source` exposes source runners:
78+
79+
```ts
80+
import { runSourceAsync } from "@metta-ts/browser/source";
81+
82+
const results = await runSourceAsync(`
83+
!(import! &self concurrency)
84+
!(par (+ 1 1) (+ 2 2))
85+
`);
86+
```
87+
88+
The async runner supports MeTTa's async forms (`par`, `race`, `with-mutex`) and uses Web Workers for
89+
`(once (hyperpose ...))` when the browser host provides them.
90+
91+
## Host Interop
92+
93+
`@metta-ts/browser/host` composes optional host runtimes such as Pyodide and
94+
SWI-Prolog WASM. The base browser package stays pure TypeScript. Import a host
95+
adapter only when the page needs it.
96+
97+
```ts
98+
import { createBrowserRunner, createBrowserTextLoader } from "@metta-ts/browser/host";
99+
import { createPyodideInterop } from "@metta-ts/py/pyodide";
100+
import { createSwiWasmInterop } from "@metta-ts/prolog/swi-wasm";
101+
102+
const files = new Map([
103+
["math.py", "def add(a, b):\n return a + b\n"],
104+
["facts.pl", "edge(alice, bob).\n"],
105+
]);
106+
const loadText = createBrowserTextLoader({ files, baseUrl: import.meta.url });
107+
const py = await createPyodideInterop({ loadText });
108+
const prolog = await createSwiWasmInterop({ loadText });
109+
const runner = createBrowserRunner({ files, interops: [py, prolog] });
110+
111+
const results = await runner.run(`
112+
!(import! &self "math.py")
113+
!(py-call (math.add 40 2))
114+
!(import! &self "facts.pl")
115+
!(prolog-call (edge alice $x))
116+
`);
117+
118+
await runner.dispose();
119+
```
120+
121+
See `examples/browser-interop` for a runnable smoke check and a bundle isolation
122+
check.
123+
77124
## License
78125

79126
[MIT](https://github.com/MesTTo/Meta-TypeScript-Talk/blob/main/LICENSE).

packages/browser/bench/RESULTS-concurrency.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ async hyperpose hook. Web Worker startup is covered by the LSP browser benchmark
66

77
| benchmark | runs | min ms | median ms |
88
|---|---:|---:|---:|
9-
| browser run VFS import | 21 | 0.87 | 1.54 |
10-
| browser runSourceAsync (+ 1 2) | 21 | 0.44 | 0.54 |
11-
| browser par 4x resolved async op | 21 | 0.49 | 0.59 |
12-
| browser race slow/fast async op | 13 | 0.49 | 0.60 |
13-
| browser with-mutex 4x awaited sections | 9 | 5.07 | 5.55 |
14-
| browser async hyperpose hook | 13 | 0.89 | 1.00 |
9+
| browser run VFS import | 21 | 0.61 | 1.11 |
10+
| browser runSourceAsync (+ 1 2) | 21 | 0.37 | 0.53 |
11+
| browser par 4x resolved async op | 21 | 0.55 | 0.77 |
12+
| browser race slow/fast async op | 13 | 0.45 | 0.53 |
13+
| browser with-mutex 4x awaited sections | 9 | 4.29 | 5.12 |
14+
| browser async hyperpose hook | 13 | 0.75 | 0.87 |

packages/browser/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@metta-ts/browser",
3-
"version": "1.1.1",
3+
"version": "1.1.2",
44
"license": "MIT",
55
"type": "module",
66
"main": "./dist/index.js",

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@metta-ts/core",
3-
"version": "1.1.1",
3+
"version": "1.1.2",
44
"license": "MIT",
55
"type": "module",
66
"main": "./dist/index.js",
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// SPDX-FileCopyrightText: 2026 MesTTo
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
import { describe, expect, it } from "vitest";
6+
import { format } from "./parser";
7+
import { runProgram } from "./runner";
8+
9+
const results = (src: string): string[] =>
10+
runProgram(src, 100_000, new Map(), { tabling: true })[0]!.results.map(format);
11+
12+
describe("adaptive local-linear tabling", () => {
13+
it("completes a finite direct left-recursive relation", () => {
14+
const src = `
15+
(= (edge a b) b)
16+
(= (edge b c) c)
17+
(= (edge c d) d)
18+
(= (edge $x $y) (empty))
19+
(= (path $x $z) (chain (path $x $y) $mid (path $mid $z)))
20+
(= (path $x $y) (edge $x $y))
21+
!(collapse (path a $z))
22+
`;
23+
24+
expect(results(src)).toEqual(["(, b c d)"]);
25+
});
26+
27+
it("keeps non-cyclic calls as exact ordered bags", () => {
28+
const src = `
29+
(= (choice $x) A)
30+
(= (choice $x) A)
31+
(= (choice $x) B)
32+
!(collapse (choice $x))
33+
`;
34+
35+
expect(results(src)).toEqual(["(, A A B)"]);
36+
});
37+
});

packages/core/src/collapse-count-route.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import { parseAll, format } from "./parser";
1313
import { pettaStdlibAtoms } from "./petta-stdlib";
1414
import { preludeAtoms, standardTokenizer } from "./runner";
1515
import { stdlibAtoms } from "./stdlib";
16-
import { analyzePurity } from "./tabling";
16+
import { analyzePurity, analyzeTableWorth, MODED_IMPURE_OPS } from "./tabling";
17+
import { TableSpace } from "./table-space";
1718
import { importsForBaseDir } from "./oracle-corpus";
1819

1920
const ROUTE_ENV = "METTA_COLLAPSE_ROUTE";
@@ -36,8 +37,12 @@ afterEach(() => {
3637
function buildDefaultTestEnv(imports: Map<string, Atom[]>) {
3738
const env = buildEnv([...preludeAtoms(), ...stdlibAtoms(), ...pettaStdlibAtoms()], stdTable());
3839
env.imports = withBuiltinModules(imports);
39-
env.table = new Map();
40+
env.tableSpace = new TableSpace();
4041
env.pureFunctors = analyzePurity(env);
42+
env.modedPureFunctors = analyzePurity(env, MODED_IMPURE_OPS);
43+
env.tableWorth = analyzeTableWorth(env, env.pureFunctors);
44+
env.modedTableWorth = analyzeTableWorth(env, env.modedPureFunctors);
45+
env.tablingDirty = false;
4146
env.compiled = new Map();
4247
env.compileDirty = true;
4348
return env;

0 commit comments

Comments
 (0)