@nirs4all/methods 1.0.6

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/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # @nirs4all/methods — WebAssembly binding
2
+
3
+ Browser + Node.js binding for **libn4m** (the `nirs4all-methods` portable
4
+ PLS/NIRS engine), compiled via Emscripten. It is a **non-idiomatic function
5
+ library**: raw typed arrays in, typed arrays out. Estimator ergonomics and the
6
+ multi-component aggregation live in the separate `nirs4all-core` repo — not
7
+ here. See [`INPUT_CONTRACT.md`](INPUT_CONTRACT.md) and
8
+ [`examples/consume.mjs`](examples/consume.mjs).
9
+
10
+ The package ships:
11
+
12
+ - `n4m.wasm` — the libn4m C ABI compiled to WebAssembly (full engine).
13
+ - `n4m.js` — Emscripten MODULARIZE/EXPORT_ES6 loader.
14
+ - `dist/` — TypeScript wrappers (`Context`, `Config`, `Model`, `MethodResult`)
15
+ emitted from `src/`.
16
+
17
+ ## Build
18
+
19
+ ```bash
20
+ # 1. Activate the Emscripten SDK.
21
+ source /path/to/emsdk/emsdk_env.sh
22
+ # 2. Configure + build the WASM preset (zero deps beyond Emscripten).
23
+ cmake --preset emscripten
24
+ cmake --build --preset emscripten --target n4m_wasm
25
+ # Artifacts land in build/emscripten/bindings/js/{n4m.js,n4m.wasm}.
26
+
27
+ # 3. Build the TypeScript wrapper for distribution:
28
+ cd bindings/js && npm run build && npm run stage:wasm
29
+ ```
30
+
31
+ ## Smoke test (Node)
32
+
33
+ ```bash
34
+ cd bindings/js
35
+ npm test # PLS parity + API/generic/AOM/new-pack smokes
36
+ node examples/consume.mjs # the downstream-consumption example
37
+ ```
38
+
39
+ The smoke suite fits a SIMPLS PLS regression through the raw-pointer entrypoint,
40
+ checks the public API and generic method path, exercises POP/AOM helpers, and
41
+ gates the broad-model-pack additions (`ECR`, `O2PLS`, AOM Ridge/Stack,
42
+ DataTwinning, SystematicCircular). The PLS smoke compares coefficients +
43
+ predictions to a frozen native fixture (`test/parity_fixture.json`) at a 1e-9
44
+ isolated band (achieved ~1e-16).
45
+
46
+ ## API surface
47
+
48
+ ```typescript
49
+ import * as n4m from "@nirs4all/methods";
50
+
51
+ await n4m.loadModule();
52
+ console.log(n4m.version()); // "1.0.3+abi.2.0.0"
53
+ console.log(n4m.abiVersion()); // [2, 0, 0]
54
+
55
+ const rows = 40, cols = 6;
56
+ const X = new Float64Array(rows * cols); // row-major
57
+ const y = new Float64Array(rows);
58
+ // ... fill X, y ...
59
+
60
+ const model = n4m.fitPls({ data: X, rows, cols },
61
+ { data: y, rows, cols: 1 }, 3);
62
+ const preds = n4m.predictPls(model, { data: X, rows, cols });
63
+
64
+ const split = n4m.computeSplitIndices("KennardStone", { data: X, rows, cols }, null);
65
+ // `computeSplit()` remains available when a compact train/test mask is enough.
66
+ ```
67
+
68
+ `Context` / `Config` / `MethodResult` are also exported for the lower-level
69
+ path. There is no idiomatic (sklearn-style) layer — that is intentional.
70
+
71
+ ## Build options
72
+
73
+ The CMake `emscripten` preset sets:
74
+
75
+ - `WASM_BIGINT=1` — int64 ABI params are exchanged as `BigInt`.
76
+ - `MODULARIZE=1`, `EXPORT_ES6=1` — the module factory is the default export.
77
+ - `ALLOW_MEMORY_GROWTH=1`, `INITIAL_MEMORY=64MB`, `MAXIMUM_MEMORY=2GB`.
78
+ - `EXPORTED_FUNCTIONS` — driven by `cpp/abi/expected_symbols_linux.txt`; every
79
+ `n4m_*` symbol that ships in `libn4m` is exported here as `_n4m_*` (the full
80
+ engine surface), plus `_malloc`/`_free` and the raw-pointer PLS shims.
81
+
82
+ ## Generic method path (enabled)
83
+
84
+ Two ways to reach the engine, both bit-exact vs native:
85
+
86
+ - **Raw-pointer shims** — `fitPls` / `predictPls` and the other shims in
87
+ `src/wasm_entry.c` (e.g. `n4m_wasm_pls_fit_legacy`): typed arrays in, typed
88
+ arrays out, no handle bookkeeping.
89
+ - **Generic `MethodResult.run`** — call **any** of the ~188 `method_result`
90
+ producers by symbol, passing `n4m_matrix_view_t*` arguments built from JS.
91
+
92
+ The generic path **works** and is regression-tested (`test/run_generic_method.mjs`
93
+ fits `n4m_estimators_sparse_simpls_fit` and the generic `n4m_model_fit` and matches the raw
94
+ `n4m_estimators_pls_fit` oracle byte-for-byte). The previous "Emscripten miscompiles
95
+ matrix-view parameters" diagnosis was **wrong**: the bug was the TS `ccall` layer
96
+ passing a JS `number` for the `int64_t` `rows`/`cols` fields. Marshalling those
97
+ dims as `BigInt` under `WASM_BIGINT` (`ffi.ts` / `makeMatrixView`) made the deep
98
+ view-pointer path byte-correct — see the note at the top of `src/wasm_entry.c`.
99
+
100
+ ```js
101
+ import { loadModule, Context, Config, MethodResult } from "@nirs4all/methods";
102
+ await loadModule();
103
+ const ctx = new Context(), cfg = new Config();
104
+ const X = makeMatrixView(rows, cols, dataF64); // row-major Float64Array
105
+ const res = MethodResult.run("n4m_estimators_sparse_simpls_fit", ctx, cfg, [X /* , Y */]);
106
+ const coef = res.matrix("coefficients"); // typed array + shape
107
+ res.destroy(); cfg.destroy(); ctx.destroy();
108
+ ```
109
+
110
+ ## Layout
111
+
112
+ ```
113
+ bindings/js/
114
+ ├── CMakeLists.txt # Emscripten target wired into the project preset
115
+ ├── package.json # @nirs4all/methods
116
+ ├── tsconfig.json
117
+ ├── INPUT_CONTRACT.md # the raw-array input contract (copied by nirs4all-lite)
118
+ ├── src/
119
+ │ ├── wasm_entry.c # ABI header pull-in + raw-pointer PLS shims
120
+ │ ├── ffi.ts # Module loader + matrix-view helpers
121
+ │ ├── types.ts # Mirrored C enums + error class
122
+ │ ├── context.ts # Context wrapper
123
+ │ ├── config.ts # Config wrapper
124
+ │ ├── model.ts # Model fit / predict (raw-pointer path)
125
+ │ ├── methodResult.ts # Universal n4m_method_result_t wrapper
126
+ │ └── index.ts # Public barrel
127
+ ├── examples/
128
+ │ └── consume.mjs # downstream-consumption example
129
+ └── test/
130
+ ├── run_smoke.mjs # Node PLS smoke + parity vs native Python
131
+ ├── run_api.mjs
132
+ ├── run_generic_method.mjs
133
+ ├── run_pop_aom.mjs
134
+ └── run_new_pack.mjs # broad-model-pack smoke
135
+ ```
@@ -0,0 +1,16 @@
1
+ import { Algorithm, Deflation, Solver } from "./types.js";
2
+ /** RAII wrapper around n4m_config_t. */
3
+ export declare class Config {
4
+ private _ptr;
5
+ private constructor();
6
+ static create(): Config;
7
+ get handle(): number;
8
+ destroy(): void;
9
+ setNComponents(k: number): void;
10
+ setAlgorithm(a: Algorithm): void;
11
+ setSolver(solver: Solver): void;
12
+ setDeflation(d: Deflation): void;
13
+ setCenterX(on: boolean): void;
14
+ setCenterY(on: boolean): void;
15
+ setStoreScores(on: boolean): void;
16
+ }
package/dist/config.js ADDED
@@ -0,0 +1,58 @@
1
+ // SPDX-License-Identifier: CECILL-2.1
2
+ import { checkStatus, getModule } from "./ffi.js";
3
+ /** RAII wrapper around n4m_config_t. */
4
+ export class Config {
5
+ _ptr;
6
+ constructor(ptr) {
7
+ this._ptr = ptr;
8
+ }
9
+ static create() {
10
+ const m = getModule();
11
+ const out = m._malloc(4);
12
+ try {
13
+ const status = m.ccall("n4m_config_create", "number", ["number"], [out]);
14
+ checkStatus(status);
15
+ return new Config(m.getValue(out, "i32"));
16
+ }
17
+ finally {
18
+ m._free(out);
19
+ }
20
+ }
21
+ get handle() {
22
+ return this._ptr;
23
+ }
24
+ destroy() {
25
+ if (this._ptr === 0)
26
+ return;
27
+ getModule().ccall("n4m_config_destroy", null, ["number"], [this._ptr]);
28
+ this._ptr = 0;
29
+ }
30
+ setNComponents(k) {
31
+ const s = getModule().ccall("n4m_config_set_n_components", "number", ["number", "number"], [this._ptr, k]);
32
+ checkStatus(s);
33
+ }
34
+ setAlgorithm(a) {
35
+ const s = getModule().ccall("n4m_config_set_algorithm", "number", ["number", "number"], [this._ptr, a]);
36
+ checkStatus(s);
37
+ }
38
+ setSolver(solver) {
39
+ const s = getModule().ccall("n4m_config_set_solver", "number", ["number", "number"], [this._ptr, solver]);
40
+ checkStatus(s);
41
+ }
42
+ setDeflation(d) {
43
+ const s = getModule().ccall("n4m_config_set_deflation", "number", ["number", "number"], [this._ptr, d]);
44
+ checkStatus(s);
45
+ }
46
+ setCenterX(on) {
47
+ const s = getModule().ccall("n4m_config_set_center_x", "number", ["number", "number"], [this._ptr, on ? 1 : 0]);
48
+ checkStatus(s);
49
+ }
50
+ setCenterY(on) {
51
+ const s = getModule().ccall("n4m_config_set_center_y", "number", ["number", "number"], [this._ptr, on ? 1 : 0]);
52
+ checkStatus(s);
53
+ }
54
+ setStoreScores(on) {
55
+ const s = getModule().ccall("n4m_config_set_store_scores", "number", ["number", "number"], [this._ptr, on ? 1 : 0]);
56
+ checkStatus(s);
57
+ }
58
+ }
@@ -0,0 +1,18 @@
1
+ /** RAII wrapper around n4m_context_t. */
2
+ export declare class Context {
3
+ private _ptr;
4
+ private constructor();
5
+ /** Create a new context. Throws N4mError on failure. */
6
+ static create(): Context;
7
+ /** Returns the raw `n4m_context_t*` pointer (handle). */
8
+ get handle(): number;
9
+ /** Free the context. Safe to call multiple times. */
10
+ destroy(): void;
11
+ /** Set the RNG seed used by stochastic algorithms.
12
+ *
13
+ * n4m_context_set_seed takes a uint64_t. Under WASM_BIGINT=1 the i64 slot
14
+ * must be marshalled as 'i64' with a BigInt — passing a JS number loses
15
+ * precision above 2^53 and throws "Cannot convert N to a BigInt" on emsdk
16
+ * >= 5.0.7 (same class of bug as the matrix-view dims; see ffi.ts). */
17
+ setSeed(seed: bigint | number): void;
18
+ }
@@ -0,0 +1,44 @@
1
+ // SPDX-License-Identifier: CECILL-2.1
2
+ import { checkStatus, getModule } from "./ffi.js";
3
+ /** RAII wrapper around n4m_context_t. */
4
+ export class Context {
5
+ _ptr;
6
+ constructor(ptr) {
7
+ this._ptr = ptr;
8
+ }
9
+ /** Create a new context. Throws N4mError on failure. */
10
+ static create() {
11
+ const m = getModule();
12
+ const out = m._malloc(4);
13
+ try {
14
+ const status = m.ccall("n4m_context_create", "number", ["number"], [out]);
15
+ checkStatus(status);
16
+ const ptr = m.getValue(out, "i32");
17
+ return new Context(ptr);
18
+ }
19
+ finally {
20
+ m._free(out);
21
+ }
22
+ }
23
+ /** Returns the raw `n4m_context_t*` pointer (handle). */
24
+ get handle() {
25
+ return this._ptr;
26
+ }
27
+ /** Free the context. Safe to call multiple times. */
28
+ destroy() {
29
+ if (this._ptr === 0)
30
+ return;
31
+ getModule().ccall("n4m_context_destroy", null, ["number"], [this._ptr]);
32
+ this._ptr = 0;
33
+ }
34
+ /** Set the RNG seed used by stochastic algorithms.
35
+ *
36
+ * n4m_context_set_seed takes a uint64_t. Under WASM_BIGINT=1 the i64 slot
37
+ * must be marshalled as 'i64' with a BigInt — passing a JS number loses
38
+ * precision above 2^53 and throws "Cannot convert N to a BigInt" on emsdk
39
+ * >= 5.0.7 (same class of bug as the matrix-view dims; see ffi.ts). */
40
+ setSeed(seed) {
41
+ const status = getModule().ccall("n4m_context_set_seed", "number", ["number", "i64"], [this._ptr, BigInt(seed)]);
42
+ checkStatus(status, this._ptr);
43
+ }
44
+ }
package/dist/ffi.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ /** Loose typing of the Emscripten module factory's runtime instance. */
2
+ export interface EmModule {
3
+ HEAPU8: Uint8Array;
4
+ HEAP32: Int32Array;
5
+ HEAPF64: Float64Array;
6
+ _malloc(size: number): number;
7
+ _free(ptr: number): void;
8
+ ccall<T>(name: string, returnType: string | null, argTypes: string[], args: unknown[]): T;
9
+ cwrap<T extends Function>(name: string, returnType: string | null, argTypes: string[]): T;
10
+ UTF8ToString(ptr: number): string;
11
+ stringToUTF8(s: string, ptr: number, max: number): void;
12
+ lengthBytesUTF8(s: string): number;
13
+ getValue(ptr: number, type: string): number;
14
+ setValue(ptr: number, value: number, type: string): void;
15
+ }
16
+ /** Load and cache the Emscripten module. Call once at app startup. */
17
+ export declare function loadModule(): Promise<EmModule>;
18
+ export declare function getModule(): EmModule;
19
+ export declare function checkStatus(status: number, ctxPtr?: number): void;
20
+ export declare const MATRIX_VIEW_SIZE = 48;
21
+ /** Allocate a matrix-view struct and copy `data` into the WASM heap.
22
+ * Returns the view pointer; caller must `free()` it.
23
+ *
24
+ * NOTE: n4m_matrix_view_init_rowmajor takes `int64_t rows, int64_t cols`.
25
+ * The WASM module is built with `-s WASM_BIGINT=1`, so ccall marshals i64
26
+ * args as BigInt — passing a plain JS number for these slots silently
27
+ * corrupts the struct fields (rows/cols/strides become garbage, producing
28
+ * ~1e32 numerics downstream) and on emsdk >= 5.0.7 throws
29
+ * "TypeError: Cannot convert N to a BigInt". We therefore declare the two
30
+ * dimension args as 'i64' and pass BigInt(...). The data/out/dtype slots
31
+ * stay 32-bit. dtype defaults to N4M_DTYPE_F64 (= 1; see types.ts Dtype). */
32
+ export declare function makeMatrixView(data: Float64Array, rows: number, cols: number, dtype?: number): {
33
+ viewPtr: number;
34
+ dataPtr: number;
35
+ free: () => void;
36
+ };
37
+ /** Read a core-owned `n4m_array_t*` (e.g. from n4m_model_get_array) into a
38
+ * JS-owned Float64Array. Does NOT free the array — the caller must call
39
+ * `n4m_array_free` afterwards. i64 view fields (rows @8, cols @16) are read
40
+ * as BigInt under WASM_BIGINT and narrowed with Number(). */
41
+ export declare function readArrayView(arrPtr: number): {
42
+ data: Float64Array;
43
+ rows: number;
44
+ cols: number;
45
+ };
package/dist/ffi.js ADDED
@@ -0,0 +1,115 @@
1
+ // SPDX-License-Identifier: CECILL-2.1
2
+ //
3
+ // Thin ccall/cwrap wrappers around the Emscripten module.
4
+ import { Status, N4mError } from "./types.js";
5
+ let _module = null;
6
+ /** Load and cache the Emscripten module. Call once at app startup. */
7
+ export async function loadModule() {
8
+ if (_module !== null)
9
+ return _module;
10
+ // n4m.js (the Emscripten loader) is staged into dist/ next to this module,
11
+ // so the package is self-contained (files: ["dist/"]). See package.json
12
+ // `stage:wasm`. The import path must stay relative to dist/ so the shipped
13
+ // package resolves the loader it bundles.
14
+ const factory = (await import("./n4m.js")).default;
15
+ _module = await factory({});
16
+ return _module;
17
+ }
18
+ export function getModule() {
19
+ if (_module === null) {
20
+ throw new Error("@nirs4all/methods not loaded — call await loadModule() first.");
21
+ }
22
+ return _module;
23
+ }
24
+ export function checkStatus(status, ctxPtr = 0) {
25
+ if (status === Status.OK)
26
+ return;
27
+ let msg = "";
28
+ if (ctxPtr !== 0) {
29
+ const m = getModule();
30
+ const cstr = m.ccall("n4m_context_last_error", "number", ["number"], [ctxPtr]);
31
+ if (cstr !== 0)
32
+ msg = m.UTF8ToString(cstr);
33
+ }
34
+ if (msg === "") {
35
+ const m = getModule();
36
+ const cstr = m.ccall("n4m_status_to_string", "number", ["number"], [status]);
37
+ if (cstr !== 0)
38
+ msg = m.UTF8ToString(cstr);
39
+ }
40
+ throw new N4mError(status, msg);
41
+ }
42
+ // ---- n4m_matrix_view_t layout (mirrors cpp/include/n4m/n4m.h) -----------
43
+ //
44
+ // struct {
45
+ // void* data; // 4 bytes (WASM is 32-bit; offset 0)
46
+ // int64_t rows; // 8 bytes (offset 8, aligned to 8)
47
+ // int64_t cols; // 8 bytes (offset 16)
48
+ // int64_t row_stride; // 8 bytes (offset 24)
49
+ // int64_t col_stride; // 8 bytes (offset 32)
50
+ // int32_t dtype; // 4 bytes (offset 40)
51
+ // int32_t reserved0; // 4 bytes (offset 44)
52
+ // }; total = 48 bytes on WASM32 with the 8-byte alignment of int64_t.
53
+ export const MATRIX_VIEW_SIZE = 48;
54
+ /** Allocate a matrix-view struct and copy `data` into the WASM heap.
55
+ * Returns the view pointer; caller must `free()` it.
56
+ *
57
+ * NOTE: n4m_matrix_view_init_rowmajor takes `int64_t rows, int64_t cols`.
58
+ * The WASM module is built with `-s WASM_BIGINT=1`, so ccall marshals i64
59
+ * args as BigInt — passing a plain JS number for these slots silently
60
+ * corrupts the struct fields (rows/cols/strides become garbage, producing
61
+ * ~1e32 numerics downstream) and on emsdk >= 5.0.7 throws
62
+ * "TypeError: Cannot convert N to a BigInt". We therefore declare the two
63
+ * dimension args as 'i64' and pass BigInt(...). The data/out/dtype slots
64
+ * stay 32-bit. dtype defaults to N4M_DTYPE_F64 (= 1; see types.ts Dtype). */
65
+ export function makeMatrixView(data, rows, cols, dtype = 1 /* N4M_DTYPE_F64 */) {
66
+ const m = getModule();
67
+ const expected = rows * cols;
68
+ if (data.length !== expected) {
69
+ throw new Error(`matrix data length ${data.length} != rows*cols (${expected})`);
70
+ }
71
+ const bytes = Math.max(1, data.length) * 8;
72
+ const dataPtr = m._malloc(bytes);
73
+ if (data.length > 0) {
74
+ m.HEAPF64.set(data, dataPtr / 8);
75
+ }
76
+ const viewPtr = m._malloc(MATRIX_VIEW_SIZE);
77
+ const status = m.ccall("n4m_matrix_view_init_rowmajor", "number", ["number", "number", "i64", "i64", "number"], [viewPtr, dataPtr, BigInt(rows), BigInt(cols), dtype]);
78
+ if (status !== Status.OK) {
79
+ m._free(dataPtr);
80
+ m._free(viewPtr);
81
+ checkStatus(status);
82
+ }
83
+ return {
84
+ viewPtr,
85
+ dataPtr,
86
+ free: () => {
87
+ m._free(viewPtr);
88
+ m._free(dataPtr);
89
+ },
90
+ };
91
+ }
92
+ /** Read a core-owned `n4m_array_t*` (e.g. from n4m_model_get_array) into a
93
+ * JS-owned Float64Array. Does NOT free the array — the caller must call
94
+ * `n4m_array_free` afterwards. i64 view fields (rows @8, cols @16) are read
95
+ * as BigInt under WASM_BIGINT and narrowed with Number(). */
96
+ export function readArrayView(arrPtr) {
97
+ const m = getModule();
98
+ const vp = m._malloc(MATRIX_VIEW_SIZE);
99
+ try {
100
+ const st = m.ccall("n4m_array_view", "number", ["number", "number"], [arrPtr, vp]);
101
+ checkStatus(st);
102
+ const dataPtr = m.getValue(vp, "i32");
103
+ const rows = Number(m.getValue(vp + 8, "i64"));
104
+ const cols = Number(m.getValue(vp + 16, "i64"));
105
+ const n = rows * cols;
106
+ const data = new Float64Array(n);
107
+ if (n > 0) {
108
+ data.set(m.HEAPF64.subarray(dataPtr / 8, dataPtr / 8 + n));
109
+ }
110
+ return { data, rows, cols };
111
+ }
112
+ finally {
113
+ m._free(vp);
114
+ }
115
+ }
@@ -0,0 +1,11 @@
1
+ export { loadModule, getModule, makeMatrixView, readArrayView } from "./ffi.js";
2
+ export { Context } from "./context.js";
3
+ export { Config } from "./config.js";
4
+ export { Model, fitPls, predictPls, fitModel, predictModel, fitAom, fitPop, fitAomRidge, fitAomStack, computeSplit, computeSplitIndices, type PlsModel, type FittedModel, type AomModel, type PopModel, type AomRidgeOptions, type AomStackOptions, type SplitKind, type SplitOptions, type SplitIndices } from "./model.js";
5
+ export { ppCreate, ppFit, ppTransform, ppGetState, ppSetState, ppDestroy, type PpOperator, } from "./preprocessing.js";
6
+ export { MethodResult } from "./methodResult.js";
7
+ export { Status, Dtype, Algorithm, Solver, Deflation, N4mError, type Matrix, } from "./types.js";
8
+ /** ABI / project version reported by the loaded WASM module. */
9
+ export declare function version(): string;
10
+ /** ABI MAJOR.MINOR.PATCH triple. */
11
+ export declare function abiVersion(): readonly [number, number, number];
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ // SPDX-License-Identifier: CECILL-2.1
2
+ //
3
+ // Public TypeScript API for the @nirs4all/methods binding — a
4
+ // non-idiomatic function library over libn4m (raw typed arrays in/out). See
5
+ // INPUT_CONTRACT.md and examples/consume.mjs.
6
+ //
7
+ // Example:
8
+ // import * as n4m from "@nirs4all/methods";
9
+ // await n4m.loadModule();
10
+ // const model = n4m.fitPls({ data: X, rows, cols }, { data: y, rows, cols: 1 }, 3);
11
+ // const preds = n4m.predictPls(model, { data: X, rows, cols });
12
+ import { getModule } from "./ffi.js";
13
+ export { loadModule, getModule, makeMatrixView, readArrayView } from "./ffi.js";
14
+ export { Context } from "./context.js";
15
+ export { Config } from "./config.js";
16
+ export { Model, fitPls, predictPls, fitModel, predictModel, fitAom, fitPop, fitAomRidge, fitAomStack, computeSplit, computeSplitIndices } from "./model.js";
17
+ export { ppCreate, ppFit, ppTransform, ppGetState, ppSetState, ppDestroy, } from "./preprocessing.js";
18
+ export { MethodResult } from "./methodResult.js";
19
+ export { Status, Dtype, Algorithm, Solver, Deflation, N4mError, } from "./types.js";
20
+ /** ABI / project version reported by the loaded WASM module. */
21
+ export function version() {
22
+ const m = getModule();
23
+ const ptr = m.ccall("n4m_get_version_string", "number", [], []);
24
+ return ptr === 0 ? "" : m.UTF8ToString(ptr);
25
+ }
26
+ /** ABI MAJOR.MINOR.PATCH triple. */
27
+ export function abiVersion() {
28
+ const m = getModule();
29
+ return [
30
+ m.ccall("n4m_get_abi_version_major", "number", [], []),
31
+ m.ccall("n4m_get_abi_version_minor", "number", [], []),
32
+ m.ccall("n4m_get_abi_version_patch", "number", [], []),
33
+ ];
34
+ }
@@ -0,0 +1,28 @@
1
+ import { Matrix } from "./types.js";
2
+ export declare class MethodResult {
3
+ private _ptr;
4
+ constructor(ptr: number);
5
+ /** Generic runner for the `(ctx, cfg, X[, Y, ...views], ...scalar-extras)
6
+ * -> n4m_method_result_t**` family — the bulk of the ~150 method_result
7
+ * producers. Matrix views are built BigInt-safe via makeMatrixView, so
8
+ * the deep entrypoints get correct dimensions under WASM_BIGINT=1.
9
+ * Returns an owning MethodResult; read outputs with matrix()/vector().
10
+ *
11
+ * `extra` are the positional scalar args after the views, matching the C
12
+ * signature: "int" -> int32_t, "double" -> double, "int64" -> int64_t.
13
+ * (Methods taking raw caller buffers — e.g. weighted_pls sample_weights —
14
+ * need a thin per-method wrapper that mallocs the buffer; not handled
15
+ * here.) */
16
+ static run(symbol: string, ctxHandle: number, cfgHandle: number, views: Matrix[], extra?: ReadonlyArray<{
17
+ kind: "int" | "double" | "int64";
18
+ value: number;
19
+ }>): MethodResult;
20
+ get handle(): number;
21
+ /** Read a named double matrix by name. Returns a copy in JS-owned memory. */
22
+ matrix(name: string): Matrix;
23
+ /** Read a named int32 vector. */
24
+ vectorInt(name: string): Int32Array;
25
+ /** Read a named scalar (returns NaN if not present). */
26
+ scalar(name: string): number;
27
+ destroy(): void;
28
+ }
@@ -0,0 +1,143 @@
1
+ // SPDX-License-Identifier: CECILL-2.1
2
+ //
3
+ // Owning wrapper around n4m_method_result_t — the universal output
4
+ // container used by every method shipped in Batches 1-12 of the C ABI.
5
+ import { checkStatus, getModule, makeMatrixView } from "./ffi.js";
6
+ export class MethodResult {
7
+ _ptr;
8
+ constructor(ptr) {
9
+ this._ptr = ptr;
10
+ }
11
+ /** Generic runner for the `(ctx, cfg, X[, Y, ...views], ...scalar-extras)
12
+ * -> n4m_method_result_t**` family — the bulk of the ~150 method_result
13
+ * producers. Matrix views are built BigInt-safe via makeMatrixView, so
14
+ * the deep entrypoints get correct dimensions under WASM_BIGINT=1.
15
+ * Returns an owning MethodResult; read outputs with matrix()/vector().
16
+ *
17
+ * `extra` are the positional scalar args after the views, matching the C
18
+ * signature: "int" -> int32_t, "double" -> double, "int64" -> int64_t.
19
+ * (Methods taking raw caller buffers — e.g. weighted_pls sample_weights —
20
+ * need a thin per-method wrapper that mallocs the buffer; not handled
21
+ * here.) */
22
+ static run(symbol, ctxHandle, cfgHandle, views, extra = []) {
23
+ const m = getModule();
24
+ const built = views.map((v) => makeMatrixView(v.data, v.rows, v.cols));
25
+ const resPP = m._malloc(4);
26
+ m.setValue(resPP, 0, "i32");
27
+ try {
28
+ const argTypes = ["number", "number"];
29
+ const args = [ctxHandle, cfgHandle];
30
+ for (const b of built) {
31
+ argTypes.push("number");
32
+ args.push(b.viewPtr);
33
+ }
34
+ for (const e of extra) {
35
+ if (e.kind === "double") {
36
+ argTypes.push("number");
37
+ args.push(e.value);
38
+ }
39
+ else if (e.kind === "int64") {
40
+ argTypes.push("i64");
41
+ args.push(BigInt(e.value));
42
+ }
43
+ else {
44
+ argTypes.push("number");
45
+ args.push(e.value | 0);
46
+ }
47
+ }
48
+ argTypes.push("number");
49
+ args.push(resPP); // n4m_method_result_t** out
50
+ const status = m.ccall(symbol, "number", argTypes, args);
51
+ checkStatus(status, ctxHandle);
52
+ const ptr = m.getValue(resPP, "i32");
53
+ return new MethodResult(ptr);
54
+ }
55
+ finally {
56
+ for (const b of built)
57
+ b.free();
58
+ m._free(resPP);
59
+ }
60
+ }
61
+ get handle() {
62
+ return this._ptr;
63
+ }
64
+ /** Read a named double matrix by name. Returns a copy in JS-owned memory. */
65
+ matrix(name) {
66
+ const m = getModule();
67
+ const nameBytes = m.lengthBytesUTF8(name);
68
+ const namePtr = m._malloc(nameBytes + 1);
69
+ const dataPtrPtr = m._malloc(4);
70
+ const rowsPtr = m._malloc(8);
71
+ const colsPtr = m._malloc(8);
72
+ try {
73
+ m.stringToUTF8(name, namePtr, nameBytes + 1);
74
+ const status = m.ccall("n4m_method_result_get_double_matrix", "number", ["number", "number", "number", "number", "number"], [this._ptr, namePtr, dataPtrPtr, rowsPtr, colsPtr]);
75
+ checkStatus(status);
76
+ const dataPtr = m.getValue(dataPtrPtr, "i32");
77
+ // i64 lo/hi pair — WASM_BIGINT=1 returns BigInt; use HEAP32 instead.
78
+ const rows = m.getValue(rowsPtr, "i64");
79
+ const cols = m.getValue(colsPtr, "i64");
80
+ const n = Number(rows) * Number(cols);
81
+ const data = new Float64Array(n);
82
+ if (n > 0) {
83
+ data.set(m.HEAPF64.subarray(dataPtr / 8, dataPtr / 8 + n));
84
+ }
85
+ return { data, rows: Number(rows), cols: Number(cols) };
86
+ }
87
+ finally {
88
+ m._free(namePtr);
89
+ m._free(dataPtrPtr);
90
+ m._free(rowsPtr);
91
+ m._free(colsPtr);
92
+ }
93
+ }
94
+ /** Read a named int32 vector. */
95
+ vectorInt(name) {
96
+ const m = getModule();
97
+ const nameBytes = m.lengthBytesUTF8(name);
98
+ const namePtr = m._malloc(nameBytes + 1);
99
+ const dataPtrPtr = m._malloc(4);
100
+ const sizePtr = m._malloc(4);
101
+ try {
102
+ m.stringToUTF8(name, namePtr, nameBytes + 1);
103
+ const status = m.ccall("n4m_method_result_get_int_vector", "number", ["number", "number", "number", "number"], [this._ptr, namePtr, dataPtrPtr, sizePtr]);
104
+ checkStatus(status);
105
+ const dataPtr = m.getValue(dataPtrPtr, "i32");
106
+ const size = m.getValue(sizePtr, "i32");
107
+ const out = new Int32Array(size);
108
+ if (size > 0) {
109
+ out.set(m.HEAP32.subarray(dataPtr / 4, dataPtr / 4 + size));
110
+ }
111
+ return out;
112
+ }
113
+ finally {
114
+ m._free(namePtr);
115
+ m._free(dataPtrPtr);
116
+ m._free(sizePtr);
117
+ }
118
+ }
119
+ /** Read a named scalar (returns NaN if not present). */
120
+ scalar(name) {
121
+ const m = getModule();
122
+ const nameBytes = m.lengthBytesUTF8(name);
123
+ const namePtr = m._malloc(nameBytes + 1);
124
+ const outPtr = m._malloc(8);
125
+ try {
126
+ m.stringToUTF8(name, namePtr, nameBytes + 1);
127
+ const status = m.ccall("n4m_method_result_get_scalar", "number", ["number", "number", "number"], [this._ptr, namePtr, outPtr]);
128
+ if (status !== 0)
129
+ return NaN;
130
+ return m.getValue(outPtr, "double");
131
+ }
132
+ finally {
133
+ m._free(namePtr);
134
+ m._free(outPtr);
135
+ }
136
+ }
137
+ destroy() {
138
+ if (this._ptr === 0)
139
+ return;
140
+ getModule().ccall("n4m_method_result_destroy", null, ["number"], [this._ptr]);
141
+ this._ptr = 0;
142
+ }
143
+ }