@statsim/compiler 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +23 -0
- package/src/index.js +83 -0
- package/src/runners/tfpjs.js +99 -0
- package/src/wasm.js +16 -0
- package/wasm/statsim.js +3169 -0
- package/wasm/statsim.wasm +0 -0
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@statsim/compiler",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "StatSim language compiler — parse .sm models and compile to stoch, PyMC, Stan, Z3",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src/",
|
|
12
|
+
"wasm/"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "node --experimental-vm-modules test/test.js"
|
|
19
|
+
},
|
|
20
|
+
"keywords": ["statsim", "probabilistic-programming", "bayesian", "compiler"],
|
|
21
|
+
"author": "Anton Zemlyansky",
|
|
22
|
+
"license": "MIT"
|
|
23
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { loadWasm, getWasm } from './wasm.js'
|
|
2
|
+
import { TfpjsRunner } from './runners/tfpjs.js'
|
|
3
|
+
|
|
4
|
+
// Ensure WASM is loaded before any operations
|
|
5
|
+
let initialized = false
|
|
6
|
+
|
|
7
|
+
async function ensureInit () {
|
|
8
|
+
if (!initialized) {
|
|
9
|
+
await loadWasm()
|
|
10
|
+
initialized = true
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse .sm source into an opaque block handle.
|
|
16
|
+
* @param {string} source - StatSim model source
|
|
17
|
+
* @returns {Promise<number>} block handle
|
|
18
|
+
*/
|
|
19
|
+
export async function parse (source) {
|
|
20
|
+
await ensureInit()
|
|
21
|
+
const wasm = getWasm()
|
|
22
|
+
const h1 = wasm.parse(source)
|
|
23
|
+
const h2 = wasm.expand(h1)
|
|
24
|
+
wasm.free(h1)
|
|
25
|
+
return h2
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Compile blocks to target code (script mode).
|
|
30
|
+
* @param {number} handle - block handle from parse()
|
|
31
|
+
* @param {object} opts - { target: 'tfpjs'|'pymc'|'stan'|'z3' }
|
|
32
|
+
* @returns {string} generated code
|
|
33
|
+
*/
|
|
34
|
+
export function compileScript (handle, opts) {
|
|
35
|
+
const wasm = getWasm()
|
|
36
|
+
return wasm.compileScript(handle, opts.target)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Compile blocks to target code (module mode) with metadata.
|
|
41
|
+
* @param {number} handle - block handle from parse()
|
|
42
|
+
* @param {object} opts - { target: 'tfpjs'|'pymc'|'stan'|'z3' }
|
|
43
|
+
* @returns {{ code: string, dataNames: string[], paramNames: string[], inferenceMode: string }}
|
|
44
|
+
*/
|
|
45
|
+
export function compile (handle, opts) {
|
|
46
|
+
const wasm = getWasm()
|
|
47
|
+
return wasm.compileModule(handle, opts.target)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Free a block handle when no longer needed.
|
|
52
|
+
* @param {number} handle
|
|
53
|
+
*/
|
|
54
|
+
export function free (handle) {
|
|
55
|
+
const wasm = getWasm()
|
|
56
|
+
wasm.free(handle)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* High-level API: parse + compile + return a runner.
|
|
61
|
+
* @param {string} source - StatSim model source
|
|
62
|
+
* @param {object} opts - { target: 'tfpjs'|'pymc'|'stan'|'z3' }
|
|
63
|
+
* @returns {Promise<TfpjsRunner>} runner with .run(), .build(), .code
|
|
64
|
+
*/
|
|
65
|
+
export async function model (source, opts = { target: 'tfpjs' }) {
|
|
66
|
+
const handle = await parse(source)
|
|
67
|
+
const result = compile(handle, opts)
|
|
68
|
+
free(handle)
|
|
69
|
+
|
|
70
|
+
if (opts.target === 'tfpjs') {
|
|
71
|
+
return new TfpjsRunner(result)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Generic runner for other targets — code + metadata only
|
|
75
|
+
return {
|
|
76
|
+
code: result.code,
|
|
77
|
+
dataNames: result.dataNames,
|
|
78
|
+
paramNames: result.paramNames,
|
|
79
|
+
inferenceMode: result.inferenceMode
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export { loadWasm }
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TFP.js runner — executes compiled module code in the host environment.
|
|
3
|
+
*
|
|
4
|
+
* Supports three modes:
|
|
5
|
+
* runner.run(data, options) — run inference, return results
|
|
6
|
+
* runner.build(data) — return model object for custom inference
|
|
7
|
+
* runner.code — raw code string
|
|
8
|
+
* runner.exports — lazy build() with no data
|
|
9
|
+
*
|
|
10
|
+
* Host functions are passed alongside data — they're auto-separated:
|
|
11
|
+
* runner.run({ images, labels, encode: myEncoder })
|
|
12
|
+
*/
|
|
13
|
+
export class TfpjsRunner {
|
|
14
|
+
/**
|
|
15
|
+
* @param {{ code: string, dataNames: string[], paramNames: string[], inferenceMode: string }} result
|
|
16
|
+
*/
|
|
17
|
+
constructor (result) {
|
|
18
|
+
this.code = result.code
|
|
19
|
+
this.dataNames = result.dataNames
|
|
20
|
+
this.paramNames = result.paramNames
|
|
21
|
+
this.inferenceMode = result.inferenceMode
|
|
22
|
+
this._module = null
|
|
23
|
+
this._exports = null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Lazily load the generated module code.
|
|
28
|
+
* @returns {Promise<object>} the evaluated module exports
|
|
29
|
+
*/
|
|
30
|
+
async _loadModule () {
|
|
31
|
+
if (this._module) return this._module
|
|
32
|
+
const blob = new Blob([this.code], { type: 'text/javascript' })
|
|
33
|
+
const url = URL.createObjectURL(blob)
|
|
34
|
+
try {
|
|
35
|
+
this._module = await import(url)
|
|
36
|
+
} finally {
|
|
37
|
+
URL.revokeObjectURL(url)
|
|
38
|
+
}
|
|
39
|
+
return this._module
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Separate data from host functions in a merged input object.
|
|
44
|
+
* Functions are extracted into ext, everything else stays as data.
|
|
45
|
+
*/
|
|
46
|
+
_separate (input) {
|
|
47
|
+
const data = {}
|
|
48
|
+
const ext = {}
|
|
49
|
+
for (const [k, v] of Object.entries(input || {})) {
|
|
50
|
+
if (typeof v === 'function') {
|
|
51
|
+
ext[k] = v
|
|
52
|
+
} else {
|
|
53
|
+
data[k] = v
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { data, ext }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Run inference.
|
|
61
|
+
* @param {object} [input={}] - data + host functions (auto-separated)
|
|
62
|
+
* @param {object} [options={}] - inference options (numResults, numBurninSteps, etc.)
|
|
63
|
+
* @returns {Promise<object>} inference results keyed by parameter name
|
|
64
|
+
*/
|
|
65
|
+
async run (input = {}, options = {}) {
|
|
66
|
+
const mod = await this._loadModule()
|
|
67
|
+
const { data, ext } = this._separate(input)
|
|
68
|
+
if (this.inferenceMode === 'optimize') {
|
|
69
|
+
return mod.runOptimization(data, ext, options)
|
|
70
|
+
}
|
|
71
|
+
return mod.runInference(data, ext, options)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Build model without running inference.
|
|
76
|
+
* Returns the model object for custom extension.
|
|
77
|
+
* @param {object} [input={}] - data + host functions (auto-separated)
|
|
78
|
+
* @returns {Promise<object>} model object with targetLogProb, bijectors, etc.
|
|
79
|
+
*/
|
|
80
|
+
async build (input = {}) {
|
|
81
|
+
const mod = await this._loadModule()
|
|
82
|
+
const { data, ext } = this._separate(input)
|
|
83
|
+
if (this.inferenceMode === 'optimize') {
|
|
84
|
+
return mod.createOptimizer(data, ext)
|
|
85
|
+
}
|
|
86
|
+
return mod.createModel(data, ext)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Lazy exports — calls build() with empty data.
|
|
91
|
+
* @returns {Promise<object>} model object
|
|
92
|
+
*/
|
|
93
|
+
get exports () {
|
|
94
|
+
if (!this._exports) {
|
|
95
|
+
this._exports = this.build()
|
|
96
|
+
}
|
|
97
|
+
return this._exports
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/wasm.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// WASM loader — loads the statsim compiler WASM module
|
|
2
|
+
let wasmModule = null
|
|
3
|
+
|
|
4
|
+
export async function loadWasm () {
|
|
5
|
+
if (wasmModule) return wasmModule
|
|
6
|
+
|
|
7
|
+
// Dynamic import of the emscripten-generated glue code
|
|
8
|
+
const { default: createStatsim } = await import('../wasm/statsim.js')
|
|
9
|
+
wasmModule = await createStatsim()
|
|
10
|
+
return wasmModule
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getWasm () {
|
|
14
|
+
if (!wasmModule) throw new Error('WASM not loaded — call loadWasm() first')
|
|
15
|
+
return wasmModule
|
|
16
|
+
}
|