numbl 0.1.2 → 0.1.3

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.
@@ -5,6 +5,16 @@
5
5
  * Concrete implementations (e.g. NodeFileIOAdapter) are injected
6
6
  * from the CLI or other host environments via ExecOptions.fileIO.
7
7
  */
8
+ /** Options extracted from a weboptions struct for websave/webread. */
9
+ export interface WebOptions {
10
+ timeout?: number;
11
+ requestMethod?: string;
12
+ headerFields?: [string, string][];
13
+ username?: string;
14
+ password?: string;
15
+ keyName?: string;
16
+ keyValue?: string;
17
+ }
8
18
  export interface FileIOAdapter {
9
19
  /** Open a file, returns an integer file identifier (fid). */
10
20
  fopen(filename: string, permission: string): number;
@@ -39,9 +49,9 @@ export interface FileIOAdapter {
39
49
  /** Create a directory (and parents). Returns true on success. Optional. */
40
50
  mkdir?(dirPath: string): boolean;
41
51
  /** Download a URL to a file. Optional. */
42
- websave?(url: string, filename: string): void;
52
+ websave?(url: string, filename: string, options?: WebOptions): void;
43
53
  /** Fetch content from a URL and return as a string. Optional. */
44
- webread?(url: string): string;
54
+ webread?(url: string, options?: WebOptions): string;
45
55
  /** Delete files matching a pattern (supports globs). Optional. */
46
56
  deleteFile?(pattern: string): void;
47
57
  /** Remove a directory. If recursive is true, remove contents first. Returns true on success. Optional. */
@@ -2,12 +2,15 @@
2
2
  * Seedable PRNG (xoshiro128**) for random number generation
3
3
  */
4
4
  import { RuntimeValue } from "../runtime/index.js";
5
+ import { type FloatXArrayType } from "../runtime/types.js";
5
6
  export declare function setRngShuffle(): void;
6
7
  export declare function setRngSeed(seed: number): void;
7
8
  export declare function seedRng(seed: number): void;
8
9
  /** Return a random float in [0, 1) using seeded or unseeded PRNG */
9
10
  export declare function rngRandom(): number;
10
11
  export declare function boxMullerRandom(): number;
12
+ /** Fill a typed array with normal random values (bulk polar method) */
13
+ export declare function fillRandn(data: FloatXArrayType): void;
11
14
  /** Return the current RNG state as a struct {Type, Seed, State} */
12
15
  export declare function getRngStateStruct(): RuntimeValue;
13
16
  /** Restore RNG state from a struct previously returned by rng() */
@@ -2,4 +2,4 @@
2
2
  * Time and system builtins: tic, toc, clock, etime, warning, computer, version,
3
3
  * ismac, ispc, isunix.
4
4
  */
5
- export {};
5
+ export declare function getTicTime(): number;
@@ -43,7 +43,6 @@ export declare function mkc(re: number, im: number): number | RuntimeComplexNumb
43
43
  export declare function makeTensor(data: InstanceType<typeof FloatXArray>, imag: InstanceType<typeof FloatXArray> | undefined, shape: number[]): RuntimeTensor;
44
44
  /** Type rule requiring two scalar numbers */
45
45
  export declare function binaryNumberOnly(argTypes: JitType[]): JitType[] | null;
46
- /** Apply a unary element-wise function with complex support */
47
46
  export declare function applyUnaryElemwise(v: RuntimeValue, realFn: (x: number) => number, complexFn: (re: number, im: number) => {
48
47
  re: number;
49
48
  im: number;
@@ -375,8 +375,26 @@ export interface LapackBridge {
375
375
  WRe?: Float64Array;
376
376
  WIm?: Float64Array;
377
377
  };
378
+ /**
379
+ * Bulk fill normal random values using Marsaglia polar + xoshiro128**.
380
+ * State is mutated in-place. Spare is passed in/out to preserve pair caching.
381
+ */
382
+ fillRandn?(state: Uint32Array, n: number, spare: number, hasSpare: boolean): {
383
+ data: Float64Array;
384
+ spare: number;
385
+ hasSpare: boolean;
386
+ };
387
+ /**
388
+ * Unary element-wise math on a real Float64Array.
389
+ * op: 0=exp, 1=log, 2=log2, 3=log10, 4=sqrt, 5=abs, 6=floor, 7=ceil,
390
+ * 8=round, 9=trunc, 10=sin, 11=cos, 12=tan, 13=asin, 14=acos, 15=atan,
391
+ * 16=sinh, 17=cosh, 18=tanh, 19=sign
392
+ */
393
+ unaryElemwise?(arr: Float64Array, op: number): Float64Array;
378
394
  /** Element-wise binary op on real Float64Arrays. op: 0=add, 1=sub, 2=mul, 3=div */
379
395
  elemwise?(a: Float64Array, b: Float64Array, op: number): Float64Array;
396
+ /** Scalar-tensor element-wise op. scalarOnLeft: true → scalar op arr[i], false → arr[i] op scalar */
397
+ elemwiseScalar?(scalar: number, arr: Float64Array, op: number, scalarOnLeft: boolean): Float64Array;
380
398
  /** Element-wise binary op on complex Float64Arrays. op: 0=add, 1=sub, 2=mul, 3=div */
381
399
  elemwiseComplex?(aRe: Float64Array, aIm: Float64Array | null, bRe: Float64Array, bIm: Float64Array | null, op: number): {
382
400
  re: Float64Array;
@@ -1,2 +1,2 @@
1
1
  /** Numbl version, used for JIT disk cache invalidation. */
2
- export declare const NUMBL_VERSION = "0.1.2";
2
+ export declare const NUMBL_VERSION = "0.1.3";
@@ -5,6 +5,11 @@
5
5
  * elemwise(a: Float64Array, b: Float64Array, op: number): Float64Array
6
6
  * op: 0=add, 1=sub, 2=mul, 3=div
7
7
  *
8
+ * elemwiseScalar(scalar: number, arr: Float64Array, op: number, scalarOnLeft: boolean): Float64Array
9
+ * op: 0=add, 1=sub, 2=mul, 3=div
10
+ * scalarOnLeft=true: result[i] = scalar op arr[i]
11
+ * scalarOnLeft=false: result[i] = arr[i] op scalar
12
+ *
8
13
  * Complex:
9
14
  * elemwiseComplex(aRe: Float64Array, aIm: Float64Array,
10
15
  * bRe: Float64Array, bIm: Float64Array,
@@ -166,3 +171,56 @@ Napi::Value ElemwiseComplex(const Napi::CallbackInfo& info) {
166
171
  }
167
172
  return result;
168
173
  }
174
+
175
+ // ── elemwiseScalar() — scalar-tensor element-wise binary op ────────────────
176
+
177
+ Napi::Value ElemwiseScalar(const Napi::CallbackInfo& info) {
178
+ Napi::Env env = info.Env();
179
+
180
+ if (info.Length() < 4
181
+ || !info[0].IsNumber()
182
+ || !info[1].IsTypedArray()
183
+ || !info[2].IsNumber()
184
+ || !info[3].IsBoolean()) {
185
+ Napi::TypeError::New(env,
186
+ "elemwiseScalar: expected (number scalar, Float64Array arr, number op, boolean scalarOnLeft)")
187
+ .ThrowAsJavaScriptException();
188
+ return env.Null();
189
+ }
190
+
191
+ double scalar = info[0].As<Napi::Number>().DoubleValue();
192
+ auto arr = info[1].As<Napi::Float64Array>();
193
+ int op = info[2].As<Napi::Number>().Int32Value();
194
+ bool scalarOnLeft = info[3].As<Napi::Boolean>().Value();
195
+
196
+ size_t n = arr.ElementLength();
197
+ auto result = Napi::Float64Array::New(env, n);
198
+ const double* a = arr.Data();
199
+ double* out = result.Data();
200
+
201
+ if (scalarOnLeft) {
202
+ switch (op) {
203
+ case 0: for (size_t i = 0; i < n; i++) out[i] = scalar + a[i]; break;
204
+ case 1: for (size_t i = 0; i < n; i++) out[i] = scalar - a[i]; break;
205
+ case 2: for (size_t i = 0; i < n; i++) out[i] = scalar * a[i]; break;
206
+ case 3: for (size_t i = 0; i < n; i++) out[i] = scalar / a[i]; break;
207
+ default:
208
+ Napi::RangeError::New(env, "elemwiseScalar: op must be 0-3")
209
+ .ThrowAsJavaScriptException();
210
+ return env.Null();
211
+ }
212
+ } else {
213
+ switch (op) {
214
+ case 0: for (size_t i = 0; i < n; i++) out[i] = a[i] + scalar; break;
215
+ case 1: for (size_t i = 0; i < n; i++) out[i] = a[i] - scalar; break;
216
+ case 2: for (size_t i = 0; i < n; i++) out[i] = a[i] * scalar; break;
217
+ case 3: for (size_t i = 0; i < n; i++) out[i] = a[i] / scalar; break;
218
+ default:
219
+ Napi::RangeError::New(env, "elemwiseScalar: op must be 0-3")
220
+ .ThrowAsJavaScriptException();
221
+ return env.Null();
222
+ }
223
+ }
224
+
225
+ return result;
226
+ }
@@ -93,8 +93,14 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
93
93
  Napi::Function::New(env, FftAlongDim));
94
94
  exports.Set(Napi::String::New(env, "elemwise"),
95
95
  Napi::Function::New(env, Elemwise));
96
+ exports.Set(Napi::String::New(env, "elemwiseScalar"),
97
+ Napi::Function::New(env, ElemwiseScalar));
96
98
  exports.Set(Napi::String::New(env, "elemwiseComplex"),
97
99
  Napi::Function::New(env, ElemwiseComplex));
100
+ exports.Set(Napi::String::New(env, "fillRandn"),
101
+ Napi::Function::New(env, FillRandn));
102
+ exports.Set(Napi::String::New(env, "unaryElemwise"),
103
+ Napi::Function::New(env, UnaryElemwise));
98
104
  return exports;
99
105
  }
100
106
 
@@ -282,4 +282,7 @@ Napi::Value Fft1d(const Napi::CallbackInfo& info);
282
282
  Napi::Value Fft1dComplex(const Napi::CallbackInfo& info);
283
283
  Napi::Value FftAlongDim(const Napi::CallbackInfo& info);
284
284
  Napi::Value Elemwise(const Napi::CallbackInfo& info);
285
+ Napi::Value ElemwiseScalar(const Napi::CallbackInfo& info);
285
286
  Napi::Value ElemwiseComplex(const Napi::CallbackInfo& info);
287
+ Napi::Value FillRandn(const Napi::CallbackInfo& info);
288
+ Napi::Value UnaryElemwise(const Napi::CallbackInfo& info);
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Native bulk fill for randn (Marsaglia polar method + xoshiro128**).
3
+ *
4
+ * fillRandn(state: Uint32Array(4), n: number, spare: number, hasSpare: boolean)
5
+ * → { data: Float64Array, spare: number, hasSpare: boolean }
6
+ *
7
+ * The xoshiro128** state is mutated in-place through the Uint32Array.
8
+ * The caller passes in any cached Box-Muller spare and gets the updated spare back.
9
+ */
10
+
11
+ #include "numbl_addon_common.h"
12
+ #include <cmath>
13
+
14
+ // Inline xoshiro128** — returns a uint32 and advances state in place.
15
+ static inline uint32_t xoshiro128ss(uint32_t* s) {
16
+ uint32_t tmp = s[1] * 5;
17
+ uint32_t result = ((tmp << 7) | (tmp >> 25)) * 9;
18
+ uint32_t t = s[1] << 9;
19
+ s[2] ^= s[0];
20
+ s[3] ^= s[1];
21
+ s[1] ^= s[2];
22
+ s[0] ^= s[3];
23
+ s[2] ^= t;
24
+ s[3] = (s[3] << 11) | (s[3] >> 21);
25
+ return result;
26
+ }
27
+
28
+ static inline double rngUniform(uint32_t* s) {
29
+ return static_cast<double>(xoshiro128ss(s)) / 4294967296.0;
30
+ }
31
+
32
+ Napi::Value FillRandn(const Napi::CallbackInfo& info) {
33
+ Napi::Env env = info.Env();
34
+
35
+ if (info.Length() < 4
36
+ || !info[0].IsTypedArray()
37
+ || !info[1].IsNumber()
38
+ || !info[2].IsNumber()
39
+ || !info[3].IsBoolean()) {
40
+ Napi::TypeError::New(env,
41
+ "fillRandn: expected (Uint32Array state, number n, number spare, boolean hasSpare)")
42
+ .ThrowAsJavaScriptException();
43
+ return env.Null();
44
+ }
45
+
46
+ auto stateArr = info[0].As<Napi::Uint32Array>();
47
+ int n = info[1].As<Napi::Number>().Int32Value();
48
+ double spare = info[2].As<Napi::Number>().DoubleValue();
49
+ bool hasSpare = info[3].As<Napi::Boolean>().Value();
50
+
51
+ if (stateArr.ElementLength() < 4) {
52
+ Napi::RangeError::New(env, "fillRandn: state must have 4 elements")
53
+ .ThrowAsJavaScriptException();
54
+ return env.Null();
55
+ }
56
+
57
+ uint32_t* state = stateArr.Data();
58
+ auto result = Napi::Float64Array::New(env, static_cast<size_t>(n));
59
+ double* out = result.Data();
60
+
61
+ int i = 0;
62
+
63
+ // Drain cached spare
64
+ if (hasSpare && i < n) {
65
+ out[i++] = spare;
66
+ hasSpare = false;
67
+ }
68
+
69
+ // Generate pairs via Marsaglia polar method
70
+ for (; i + 1 < n; i += 2) {
71
+ double u, v, s;
72
+ do {
73
+ u = 2.0 * rngUniform(state) - 1.0;
74
+ v = 2.0 * rngUniform(state) - 1.0;
75
+ s = u * u + v * v;
76
+ } while (s >= 1.0 || s == 0.0);
77
+ double mul = std::sqrt((-2.0 * std::log(s)) / s);
78
+ out[i] = u * mul;
79
+ out[i + 1] = v * mul;
80
+ }
81
+
82
+ // Handle odd trailing element
83
+ if (i < n) {
84
+ double u, v, s;
85
+ do {
86
+ u = 2.0 * rngUniform(state) - 1.0;
87
+ v = 2.0 * rngUniform(state) - 1.0;
88
+ s = u * u + v * v;
89
+ } while (s >= 1.0 || s == 0.0);
90
+ double mul = std::sqrt((-2.0 * std::log(s)) / s);
91
+ out[i] = u * mul;
92
+ spare = v * mul;
93
+ hasSpare = true;
94
+ }
95
+
96
+ auto obj = Napi::Object::New(env);
97
+ obj.Set("data", result);
98
+ obj.Set("spare", Napi::Number::New(env, spare));
99
+ obj.Set("hasSpare", Napi::Boolean::New(env, hasSpare));
100
+ return obj;
101
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Native unary element-wise math operations on Float64Arrays.
3
+ *
4
+ * unaryElemwise(arr: Float64Array, op: number): Float64Array
5
+ * op codes:
6
+ * 0=exp, 1=log, 2=log2, 3=log10,
7
+ * 4=sqrt, 5=abs, 6=floor, 7=ceil, 8=round, 9=trunc (fix),
8
+ * 10=sin, 11=cos, 12=tan, 13=asin, 14=acos, 15=atan,
9
+ * 16=sinh, 17=cosh, 18=tanh, 19=sign
10
+ */
11
+
12
+ #include "numbl_addon_common.h"
13
+ #include <cmath>
14
+
15
+ Napi::Value UnaryElemwise(const Napi::CallbackInfo& info) {
16
+ Napi::Env env = info.Env();
17
+
18
+ if (info.Length() < 2
19
+ || !info[0].IsTypedArray()
20
+ || !info[1].IsNumber()) {
21
+ Napi::TypeError::New(env,
22
+ "unaryElemwise: expected (Float64Array arr, number op)")
23
+ .ThrowAsJavaScriptException();
24
+ return env.Null();
25
+ }
26
+
27
+ auto arr = info[0].As<Napi::Float64Array>();
28
+ int op = info[1].As<Napi::Number>().Int32Value();
29
+
30
+ size_t n = arr.ElementLength();
31
+ auto result = Napi::Float64Array::New(env, n);
32
+ const double* a = arr.Data();
33
+ double* out = result.Data();
34
+
35
+ switch (op) {
36
+ case 0: for (size_t i = 0; i < n; i++) out[i] = std::exp(a[i]); break;
37
+ case 1: for (size_t i = 0; i < n; i++) out[i] = std::log(a[i]); break;
38
+ case 2: for (size_t i = 0; i < n; i++) out[i] = std::log2(a[i]); break;
39
+ case 3: for (size_t i = 0; i < n; i++) out[i] = std::log10(a[i]); break;
40
+ case 4: for (size_t i = 0; i < n; i++) out[i] = std::sqrt(a[i]); break;
41
+ case 5: for (size_t i = 0; i < n; i++) out[i] = std::abs(a[i]); break;
42
+ case 6: for (size_t i = 0; i < n; i++) out[i] = std::floor(a[i]); break;
43
+ case 7: for (size_t i = 0; i < n; i++) out[i] = std::ceil(a[i]); break;
44
+ case 8: for (size_t i = 0; i < n; i++) out[i] = std::round(a[i]); break;
45
+ case 9: for (size_t i = 0; i < n; i++) out[i] = std::trunc(a[i]); break;
46
+ case 10: for (size_t i = 0; i < n; i++) out[i] = std::sin(a[i]); break;
47
+ case 11: for (size_t i = 0; i < n; i++) out[i] = std::cos(a[i]); break;
48
+ case 12: for (size_t i = 0; i < n; i++) out[i] = std::tan(a[i]); break;
49
+ case 13: for (size_t i = 0; i < n; i++) out[i] = std::asin(a[i]); break;
50
+ case 14: for (size_t i = 0; i < n; i++) out[i] = std::acos(a[i]); break;
51
+ case 15: for (size_t i = 0; i < n; i++) out[i] = std::atan(a[i]); break;
52
+ case 16: for (size_t i = 0; i < n; i++) out[i] = std::sinh(a[i]); break;
53
+ case 17: for (size_t i = 0; i < n; i++) out[i] = std::cosh(a[i]); break;
54
+ case 18: for (size_t i = 0; i < n; i++) out[i] = std::tanh(a[i]); break;
55
+ case 19:
56
+ for (size_t i = 0; i < n; i++) {
57
+ out[i] = (a[i] > 0.0) ? 1.0 : (a[i] < 0.0) ? -1.0 : 0.0;
58
+ }
59
+ break;
60
+ default:
61
+ Napi::RangeError::New(env, "unaryElemwise: unknown op code")
62
+ .ThrowAsJavaScriptException();
63
+ return env.Null();
64
+ }
65
+
66
+ return result;
67
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "numbl",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Run .m source files in the browser and on the command line by compiling to JavaScript",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -71,7 +71,9 @@
71
71
  "fflate": "^0.8.2",
72
72
  "node-addon-api": "^8.3.0",
73
73
  "pako": "^2.1.0",
74
+ "react-markdown": "^10.1.0",
74
75
  "react-syntax-highlighter": "^16.1.1",
76
+ "remark-gfm": "^4.0.1",
75
77
  "three": "^0.183.2"
76
78
  },
77
79
  "devDependencies": {