tmmcore 0.3.0 → 0.4.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 +3 -3
- package/src/index.js +1 -1
- package/src/phase.js +29 -12
- package/src/taylorJet.js +5 -0
- package/src/tmm.js +33 -8
- package/src/tmmWasm.js +931 -839
- package/src/tmm_kernel.c +144 -44
- package/src/tmm_kernel.wasm +0 -0
package/src/tmmWasm.js
CHANGED
|
@@ -1,839 +1,931 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* tmmWasm.js : loader and ergonomic wrappers for the WebAssembly TMM kernel.
|
|
3
|
-
*
|
|
4
|
-
* The kernel (`tmm_kernel.c`, built to `tmm_kernel.wasm`) is a line-by-line port
|
|
5
|
-
* of the JavaScript TMM in `tmm.js`. This module instantiates it : in a browser
|
|
6
|
-
* main thread, a Web Worker, or Node : and exposes wrappers whose signatures
|
|
7
|
-
* mirror the JS functions.
|
|
8
|
-
*
|
|
9
|
-
* Acceleration is opt-in and falls back to JavaScript: if the `.wasm` is
|
|
10
|
-
* unavailable, instantiation fails, or the feature flag is off, the wrappers
|
|
11
|
-
* return `null` and callers use the JS path. Results are identical either way
|
|
12
|
-
* to float64 round-off.
|
|
13
|
-
*
|
|
14
|
-
* Instances are not shared across threads : there is no shared memory, so each
|
|
15
|
-
* context instantiates its own from the same bytes. Use `instantiateTmmWasm()`
|
|
16
|
-
* where you already hold the bytes (a worker receives them in its init message)
|
|
17
|
-
* and `initTmmWasmFromUrl()` where the artifact is fetchable.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { omegaFromLambdaNm } from './phase.js';
|
|
21
|
-
|
|
22
|
-
let _instance = null; // TmmWasmInstance | null
|
|
23
|
-
let _enabled = false; // feature flag (default OFF)
|
|
24
|
-
let _initPromise = null; // de-dupe concurrent init
|
|
25
|
-
|
|
26
|
-
// ── Jet marshalling ──────────────────────────────────────────────────────────
|
|
27
|
-
// A jet crosses the boundary as 8 doubles, [re, im] per order.
|
|
28
|
-
|
|
29
|
-
function writeJet(buf, offset, jet) {
|
|
30
|
-
for (let i = 0; i < 4; i++) {
|
|
31
|
-
buf[offset + 2 * i] = jet[i][0];
|
|
32
|
-
buf[offset + 2 * i + 1] = jet[i][1];
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// The kernel writes NaN where the coefficient is exactly zero and the phase is
|
|
37
|
-
// undefined; the JS reference returns null there, so agree with it.
|
|
38
|
-
function readPhase(buf, offset) {
|
|
39
|
-
const magnitudeSquared = buf[offset + 4];
|
|
40
|
-
if (Number.isNaN(magnitudeSquared)) return null;
|
|
41
|
-
const phaseRad = buf[offset];
|
|
42
|
-
return {
|
|
43
|
-
phaseRad,
|
|
44
|
-
phaseDeg: phaseRad * 180 / Math.PI,
|
|
45
|
-
gd: buf[offset + 1],
|
|
46
|
-
gdd: buf[offset + 2],
|
|
47
|
-
tod: buf[offset + 3],
|
|
48
|
-
magnitudeSquared,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Permissive imports: a STANDALONE_WASM build of pure-math C usually needs no
|
|
53
|
-
// imports, but ALLOW_MEMORY_GROWTH may emit `emscripten_notify_memory_growth`,
|
|
54
|
-
// and some toolchains emit WASI stubs. Cover them so instantiation never throws.
|
|
55
|
-
function wasmImports() {
|
|
56
|
-
return {
|
|
57
|
-
env: { emscripten_notify_memory_growth: () => {} },
|
|
58
|
-
wasi_snapshot_preview1: new Proxy({}, { get: () => () => 0 }),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Backstop for growing-evaluator handles that are dropped without free():
|
|
63
|
-
// reclaims their kernel memory when the JS handle is collected. Deterministic
|
|
64
|
-
// free() remains the contract; this only keeps a leak from being permanent.
|
|
65
|
-
const growingEvalFinalizer = typeof FinalizationRegistry !== 'undefined'
|
|
66
|
-
? new FinalizationRegistry(({ wasm, ptr }) => {
|
|
67
|
-
try { wasm._growing_eval_free(ptr); } catch (_) { /* instance gone */ }
|
|
68
|
-
})
|
|
69
|
-
: null;
|
|
70
|
-
|
|
71
|
-
export class TmmWasmInstance {
|
|
72
|
-
constructor(instance) {
|
|
73
|
-
const ex = instance.exports;
|
|
74
|
-
this.exports = ex;
|
|
75
|
-
// STANDALONE_WASM reactor modules expose an initializer that must run
|
|
76
|
-
// before malloc (sets up the allocator + any static ctors). Call it once.
|
|
77
|
-
if (typeof ex._initialize === 'function') ex._initialize();
|
|
78
|
-
else if (typeof ex.__wasm_call_ctors === 'function') ex.__wasm_call_ctors();
|
|
79
|
-
this.memory = ex.memory;
|
|
80
|
-
this.malloc = ex.malloc || ex._malloc;
|
|
81
|
-
this.free = ex.free || ex._free;
|
|
82
|
-
this._tmm_one = ex.tmm_one || ex._tmm_one;
|
|
83
|
-
this._tmm_spectrum = ex.tmm_spectrum || ex._tmm_spectrum;
|
|
84
|
-
this._tmm_jacobian = ex.tmm_jacobian || ex._tmm_jacobian;
|
|
85
|
-
this._tmm_needle_scan = ex.tmm_needle_scan || ex._tmm_needle_scan;
|
|
86
|
-
// Optional (added later for SQP/Newton accel): a .wasm built before the
|
|
87
|
-
// Hessian kernel existed simply lacks it → callers fall back to JS.
|
|
88
|
-
this._tmm_hessian = ex.tmm_hessian || ex._tmm_hessian || null;
|
|
89
|
-
// Optional, same reason: the phase-dispersion kernel arrived after the
|
|
90
|
-
// spectral one, so an older artifact lacks these three.
|
|
91
|
-
this._tmm_phase_one = ex.tmm_phase_one || ex._tmm_phase_one || null;
|
|
92
|
-
this._tmm_phase_spectrum = ex.tmm_phase_spectrum || ex._tmm_phase_spectrum || null;
|
|
93
|
-
this._tmm_phase_jacobian = ex.tmm_phase_jacobian || ex._tmm_phase_jacobian || null;
|
|
94
|
-
// Optional, same reason: the
|
|
95
|
-
//
|
|
96
|
-
this.
|
|
97
|
-
|
|
98
|
-
// Optional, same reason: the
|
|
99
|
-
//
|
|
100
|
-
this.
|
|
101
|
-
this.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* @param {number[]}
|
|
166
|
-
* @param {number}
|
|
167
|
-
* @
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
* @param {[number,number]}
|
|
236
|
-
* @param {
|
|
237
|
-
* @
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* @param {number[]}
|
|
281
|
-
* @param {number}
|
|
282
|
-
* @
|
|
283
|
-
*
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
const
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
const
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
*
|
|
338
|
-
* the
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* @param {number[]}
|
|
352
|
-
* @param {number}
|
|
353
|
-
* @
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
const
|
|
370
|
-
const
|
|
371
|
-
const
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
self.
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
out.
|
|
427
|
-
out.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
*
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
* @
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
*
|
|
569
|
-
* @param {number[]}
|
|
570
|
-
* @param {
|
|
571
|
-
* @
|
|
572
|
-
*
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
const
|
|
586
|
-
const
|
|
587
|
-
const
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const
|
|
596
|
-
const
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
*
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const
|
|
704
|
-
const
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
this.
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
const
|
|
724
|
-
const
|
|
725
|
-
const
|
|
726
|
-
const
|
|
727
|
-
for (let
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
const
|
|
738
|
-
for (let
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
*
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1
|
+
/**
|
|
2
|
+
* tmmWasm.js : loader and ergonomic wrappers for the WebAssembly TMM kernel.
|
|
3
|
+
*
|
|
4
|
+
* The kernel (`tmm_kernel.c`, built to `tmm_kernel.wasm`) is a line-by-line port
|
|
5
|
+
* of the JavaScript TMM in `tmm.js`. This module instantiates it : in a browser
|
|
6
|
+
* main thread, a Web Worker, or Node : and exposes wrappers whose signatures
|
|
7
|
+
* mirror the JS functions.
|
|
8
|
+
*
|
|
9
|
+
* Acceleration is opt-in and falls back to JavaScript: if the `.wasm` is
|
|
10
|
+
* unavailable, instantiation fails, or the feature flag is off, the wrappers
|
|
11
|
+
* return `null` and callers use the JS path. Results are identical either way
|
|
12
|
+
* to float64 round-off.
|
|
13
|
+
*
|
|
14
|
+
* Instances are not shared across threads : there is no shared memory, so each
|
|
15
|
+
* context instantiates its own from the same bytes. Use `instantiateTmmWasm()`
|
|
16
|
+
* where you already hold the bytes (a worker receives them in its init message)
|
|
17
|
+
* and `initTmmWasmFromUrl()` where the artifact is fetchable.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { omegaFromLambdaNm } from './phase.js';
|
|
21
|
+
|
|
22
|
+
let _instance = null; // TmmWasmInstance | null
|
|
23
|
+
let _enabled = false; // feature flag (default OFF)
|
|
24
|
+
let _initPromise = null; // de-dupe concurrent init
|
|
25
|
+
|
|
26
|
+
// ── Jet marshalling ──────────────────────────────────────────────────────────
|
|
27
|
+
// A jet crosses the boundary as 8 doubles, [re, im] per order.
|
|
28
|
+
|
|
29
|
+
function writeJet(buf, offset, jet) {
|
|
30
|
+
for (let i = 0; i < 4; i++) {
|
|
31
|
+
buf[offset + 2 * i] = jet[i][0];
|
|
32
|
+
buf[offset + 2 * i + 1] = jet[i][1];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The kernel writes NaN where the coefficient is exactly zero and the phase is
|
|
37
|
+
// undefined; the JS reference returns null there, so agree with it.
|
|
38
|
+
function readPhase(buf, offset) {
|
|
39
|
+
const magnitudeSquared = buf[offset + 4];
|
|
40
|
+
if (Number.isNaN(magnitudeSquared)) return null;
|
|
41
|
+
const phaseRad = buf[offset];
|
|
42
|
+
return {
|
|
43
|
+
phaseRad,
|
|
44
|
+
phaseDeg: phaseRad * 180 / Math.PI,
|
|
45
|
+
gd: buf[offset + 1],
|
|
46
|
+
gdd: buf[offset + 2],
|
|
47
|
+
tod: buf[offset + 3],
|
|
48
|
+
magnitudeSquared,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Permissive imports: a STANDALONE_WASM build of pure-math C usually needs no
|
|
53
|
+
// imports, but ALLOW_MEMORY_GROWTH may emit `emscripten_notify_memory_growth`,
|
|
54
|
+
// and some toolchains emit WASI stubs. Cover them so instantiation never throws.
|
|
55
|
+
function wasmImports() {
|
|
56
|
+
return {
|
|
57
|
+
env: { emscripten_notify_memory_growth: () => {} },
|
|
58
|
+
wasi_snapshot_preview1: new Proxy({}, { get: () => () => 0 }),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Backstop for growing-evaluator handles that are dropped without free():
|
|
63
|
+
// reclaims their kernel memory when the JS handle is collected. Deterministic
|
|
64
|
+
// free() remains the contract; this only keeps a leak from being permanent.
|
|
65
|
+
const growingEvalFinalizer = typeof FinalizationRegistry !== 'undefined'
|
|
66
|
+
? new FinalizationRegistry(({ wasm, ptr }) => {
|
|
67
|
+
try { wasm._growing_eval_free(ptr); } catch (_) { /* instance gone */ }
|
|
68
|
+
})
|
|
69
|
+
: null;
|
|
70
|
+
|
|
71
|
+
export class TmmWasmInstance {
|
|
72
|
+
constructor(instance) {
|
|
73
|
+
const ex = instance.exports;
|
|
74
|
+
this.exports = ex;
|
|
75
|
+
// STANDALONE_WASM reactor modules expose an initializer that must run
|
|
76
|
+
// before malloc (sets up the allocator + any static ctors). Call it once.
|
|
77
|
+
if (typeof ex._initialize === 'function') ex._initialize();
|
|
78
|
+
else if (typeof ex.__wasm_call_ctors === 'function') ex.__wasm_call_ctors();
|
|
79
|
+
this.memory = ex.memory;
|
|
80
|
+
this.malloc = ex.malloc || ex._malloc;
|
|
81
|
+
this.free = ex.free || ex._free;
|
|
82
|
+
this._tmm_one = ex.tmm_one || ex._tmm_one;
|
|
83
|
+
this._tmm_spectrum = ex.tmm_spectrum || ex._tmm_spectrum;
|
|
84
|
+
this._tmm_jacobian = ex.tmm_jacobian || ex._tmm_jacobian;
|
|
85
|
+
this._tmm_needle_scan = ex.tmm_needle_scan || ex._tmm_needle_scan;
|
|
86
|
+
// Optional (added later for SQP/Newton accel): a .wasm built before the
|
|
87
|
+
// Hessian kernel existed simply lacks it → callers fall back to JS.
|
|
88
|
+
this._tmm_hessian = ex.tmm_hessian || ex._tmm_hessian || null;
|
|
89
|
+
// Optional, same reason: the phase-dispersion kernel arrived after the
|
|
90
|
+
// spectral one, so an older artifact lacks these three.
|
|
91
|
+
this._tmm_phase_one = ex.tmm_phase_one || ex._tmm_phase_one || null;
|
|
92
|
+
this._tmm_phase_spectrum = ex.tmm_phase_spectrum || ex._tmm_phase_spectrum || null;
|
|
93
|
+
this._tmm_phase_jacobian = ex.tmm_phase_jacobian || ex._tmm_phase_jacobian || null;
|
|
94
|
+
// Optional, same reason: the batched phase Jacobian arrived after the
|
|
95
|
+
// three above, for fitting a whole measured Ψ/Δ or group-delay spectrum.
|
|
96
|
+
this._tmm_phase_jacobian_spectrum = ex.tmm_phase_jacobian_spectrum
|
|
97
|
+
|| ex._tmm_phase_jacobian_spectrum || null;
|
|
98
|
+
// Optional, same reason: the growing-stack kernels (monitor curve and
|
|
99
|
+
// per-step deposition spectra) arrived after all of the above.
|
|
100
|
+
this._tmm_monitor_curve = ex.tmm_monitor_curve || ex._tmm_monitor_curve || null;
|
|
101
|
+
this._tmm_deposition_spectra = ex.tmm_deposition_spectra || ex._tmm_deposition_spectra || null;
|
|
102
|
+
// Optional, same reason: the persistent growing-layer evaluator
|
|
103
|
+
// (wavelength-grid-per-call) arrived after the two kernels above.
|
|
104
|
+
this._growing_eval_create = ex.tmm_growing_eval_create || ex._tmm_growing_eval_create || null;
|
|
105
|
+
this._growing_eval_set_top = ex.tmm_growing_eval_set_top || ex._tmm_growing_eval_set_top || null;
|
|
106
|
+
this._growing_eval_sample = ex.tmm_growing_eval_sample || ex._tmm_growing_eval_sample || null;
|
|
107
|
+
this._growing_eval_free = ex.tmm_growing_eval_free || ex._tmm_growing_eval_free || null;
|
|
108
|
+
const missingExports = !this.malloc || !this.free || !this._tmm_one ||
|
|
109
|
+
!this._tmm_spectrum || !this._tmm_jacobian || !this._tmm_needle_scan;
|
|
110
|
+
if (missingExports) {
|
|
111
|
+
throw new Error('tmmWasm: required exports missing from module');
|
|
112
|
+
}
|
|
113
|
+
this._scratchPtr = 0; // persistent per-call scratch arena (lazy)
|
|
114
|
+
this._scratchN = 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_alloc(nDoubles) {
|
|
118
|
+
const ptr = this.malloc(nDoubles * 8);
|
|
119
|
+
if (!ptr) throw new Error('tmmWasm: malloc failed');
|
|
120
|
+
return ptr;
|
|
121
|
+
}
|
|
122
|
+
// Fresh view : memory.buffer is detached after any growth, so re-create
|
|
123
|
+
// views AFTER all mallocs for a call are done.
|
|
124
|
+
_view(ptr, nDoubles) {
|
|
125
|
+
return new Float64Array(this.memory.buffer, ptr, nDoubles);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Persistent scratch arena for the per-call hot paths (tmmOne/tmmJacobian).
|
|
129
|
+
// These are invoked thousands of times per optimization run; malloc/free +
|
|
130
|
+
// typed-array churn per call would dominate the JS↔WASM boundary cost and
|
|
131
|
+
// can make per-call WASM SLOWER than JS. Reusing one buffer (grown on demand,
|
|
132
|
+
// never freed between calls) makes each call just write-args / read-result.
|
|
133
|
+
_scratch(nDoubles) {
|
|
134
|
+
if (this._scratchN < nDoubles) {
|
|
135
|
+
if (this._scratchPtr) this.free(this._scratchPtr);
|
|
136
|
+
this._scratchPtr = this._alloc(nDoubles);
|
|
137
|
+
this._scratchN = nDoubles;
|
|
138
|
+
}
|
|
139
|
+
return this._scratchPtr;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Single (λ, θ, pol) : mirrors tmm() in thinFilmMath.js.
|
|
144
|
+
* @returns {{R:number,T:number,A:number}}
|
|
145
|
+
*/
|
|
146
|
+
tmmOne(lambda_nm, theta_deg, polCode /* 0=s,1=p */, n0, ns, layers) {
|
|
147
|
+
const N = layers.length;
|
|
148
|
+
const need = 3 * N + 3; // layers [0..3N) + out [3N..3N+3)
|
|
149
|
+
const ptr = this._scratch(need); // may grow→detach; view created AFTER
|
|
150
|
+
const buf = this._view(ptr, need);
|
|
151
|
+
for (let i = 0; i < N; i++) {
|
|
152
|
+
buf[3 * i + 0] = layers[i].n[0];
|
|
153
|
+
buf[3 * i + 1] = layers[i].n[1];
|
|
154
|
+
buf[3 * i + 2] = layers[i].d;
|
|
155
|
+
}
|
|
156
|
+
const outPtr = ptr + 3 * N * 8;
|
|
157
|
+
this._tmm_one(lambda_nm, theta_deg, polCode | 0,
|
|
158
|
+
n0[0], n0[1], ns[0], ns[1], ptr, N, outPtr);
|
|
159
|
+
return { R: buf[3 * N], T: buf[3 * N + 1], A: buf[3 * N + 2] };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Batched spectrum over a λ grid for BOTH polarizations : the boundary-
|
|
164
|
+
* amortizing path behind evaluateSpectrum().
|
|
165
|
+
* @param {number[]} lambdas
|
|
166
|
+
* @param {[number,number][]} n0List incident ñ per λ
|
|
167
|
+
* @param {[number,number][]} nsList substrate ñ per λ
|
|
168
|
+
* @param {[number,number][][]} layerNK [layer][λ] = ñ
|
|
169
|
+
* @param {number[]} thick layer thicknesses (nm), length N
|
|
170
|
+
* @param {number} theta_deg
|
|
171
|
+
* @returns {{Rs,Ts,As,Rp,Tp,Ap}} each a Float64Array(nLam)
|
|
172
|
+
*/
|
|
173
|
+
tmmSpectrum(lambdas, n0List, nsList, layerNK, thick, theta_deg) {
|
|
174
|
+
const nLam = lambdas.length;
|
|
175
|
+
const N = thick.length;
|
|
176
|
+
|
|
177
|
+
const lamPtr = this._alloc(nLam);
|
|
178
|
+
const n0Ptr = this._alloc(2 * nLam);
|
|
179
|
+
const nsPtr = this._alloc(2 * nLam);
|
|
180
|
+
const mPtr = this._alloc(Math.max(1, 2 * N * nLam));
|
|
181
|
+
const thPtr = this._alloc(Math.max(1, N));
|
|
182
|
+
const rsPtr = this._alloc(nLam), tsPtr = this._alloc(nLam), asPtr = this._alloc(nLam);
|
|
183
|
+
const rpPtr = this._alloc(nLam), tpPtr = this._alloc(nLam), apPtr = this._alloc(nLam);
|
|
184
|
+
|
|
185
|
+
// Views created after all mallocs (buffer may have grown/detached).
|
|
186
|
+
const lam = this._view(lamPtr, nLam);
|
|
187
|
+
const n0v = this._view(n0Ptr, 2 * nLam);
|
|
188
|
+
const nsv = this._view(nsPtr, 2 * nLam);
|
|
189
|
+
const mv = this._view(mPtr, Math.max(1, 2 * N * nLam));
|
|
190
|
+
const thv = this._view(thPtr, Math.max(1, N));
|
|
191
|
+
for (let i = 0; i < nLam; i++) {
|
|
192
|
+
lam[i] = lambdas[i];
|
|
193
|
+
n0v[2 * i] = n0List[i][0]; n0v[2 * i + 1] = n0List[i][1];
|
|
194
|
+
nsv[2 * i] = nsList[i][0]; nsv[2 * i + 1] = nsList[i][1];
|
|
195
|
+
}
|
|
196
|
+
for (let k = 0; k < N; k++) {
|
|
197
|
+
thv[k] = thick[k];
|
|
198
|
+
const row = layerNK[k];
|
|
199
|
+
const base = k * nLam * 2;
|
|
200
|
+
for (let i = 0; i < nLam; i++) {
|
|
201
|
+
mv[base + 2 * i] = row[i][0];
|
|
202
|
+
mv[base + 2 * i + 1] = row[i][1];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this._tmm_spectrum(lamPtr, nLam, n0Ptr, nsPtr, mPtr, thPtr, N, theta_deg,
|
|
207
|
+
rsPtr, tsPtr, asPtr, rpPtr, tpPtr, apPtr);
|
|
208
|
+
|
|
209
|
+
// Copy outputs out of wasm memory before freeing.
|
|
210
|
+
const cp = (p) => Float64Array.from(this._view(p, nLam));
|
|
211
|
+
const res = { Rs: cp(rsPtr), Ts: cp(tsPtr), As: cp(asPtr),
|
|
212
|
+
Rp: cp(rpPtr), Tp: cp(tpPtr), Ap: cp(apPtr) };
|
|
213
|
+
for (const p of [lamPtr, n0Ptr, nsPtr, mPtr, thPtr,
|
|
214
|
+
rsPtr, tsPtr, asPtr, rpPtr, tpPtr, apPtr]) this.free(p);
|
|
215
|
+
return res;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** True if the loaded module carries the growing-stack kernels. */
|
|
219
|
+
hasGrowingKernels() {
|
|
220
|
+
return !!(this._tmm_monitor_curve && this._tmm_deposition_spectra);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** True if the loaded module carries the persistent growing-layer evaluator. */
|
|
224
|
+
hasGrowingEval() {
|
|
225
|
+
return !!(this._growing_eval_create && this._growing_eval_set_top
|
|
226
|
+
&& this._growing_eval_sample && this._growing_eval_free);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Monitor curve of one growing layer at one wavelength: the completed
|
|
231
|
+
* stack's matrix is built once, then every sample thickness costs one 2×2
|
|
232
|
+
* multiply. Returns the forward and substrate-side passes of the coated
|
|
233
|
+
* surface, both polarizations; the caller does the incoherent slab
|
|
234
|
+
* combination (or forms A = 1−R−T for a semi-infinite substrate).
|
|
235
|
+
* @param {[number,number]} n0 incident ñ
|
|
236
|
+
* @param {[number,number]} ns substrate ñ
|
|
237
|
+
* @param {{n:[number,number],d:number}[]} baseLayers completed stack,
|
|
238
|
+
* outermost first
|
|
239
|
+
* @param {[number,number]} ngNK growing layer ñ
|
|
240
|
+
* @param {ArrayLike<number>} dArr sample thicknesses (nm)
|
|
241
|
+
* @returns {{Rs,Ts,Rp,Tp,Rrs,Rrp}} each a Float64Array(dArr.length)
|
|
242
|
+
*/
|
|
243
|
+
monitorCurve(lambda_nm, theta_deg, n0, ns, baseLayers, ngNK, dArr) {
|
|
244
|
+
const NB = baseLayers.length;
|
|
245
|
+
const nD = dArr.length;
|
|
246
|
+
// arena: base[3NB] | d[nD] | Rs,Ts,Rp,Tp,Rrs,Rrp [6·nD]
|
|
247
|
+
const oBase = 0, oD = 3 * NB, oOut = oD + nD;
|
|
248
|
+
const need = oOut + 6 * nD;
|
|
249
|
+
const ptr = this._scratch(need);
|
|
250
|
+
const buf = this._view(ptr, need);
|
|
251
|
+
for (let i = 0; i < NB; i++) {
|
|
252
|
+
buf[3 * i + 0] = baseLayers[i].n[0];
|
|
253
|
+
buf[3 * i + 1] = baseLayers[i].n[1];
|
|
254
|
+
buf[3 * i + 2] = baseLayers[i].d;
|
|
255
|
+
}
|
|
256
|
+
for (let k = 0; k < nD; k++) buf[oD + k] = dArr[k];
|
|
257
|
+
const P = (off) => ptr + off * 8;
|
|
258
|
+
this._tmm_monitor_curve(lambda_nm, theta_deg,
|
|
259
|
+
n0[0], n0[1], ns[0], ns[1], P(oBase), NB, ngNK[0], ngNK[1],
|
|
260
|
+
P(oD), nD,
|
|
261
|
+
P(oOut), P(oOut + nD), P(oOut + 2 * nD), P(oOut + 3 * nD),
|
|
262
|
+
P(oOut + 4 * nD), P(oOut + 5 * nD));
|
|
263
|
+
// Fresh view after the call: memory growth detaches the old buffer.
|
|
264
|
+
const out = this._view(ptr, need);
|
|
265
|
+
return {
|
|
266
|
+
Rs: out.slice(oOut, oOut + nD),
|
|
267
|
+
Ts: out.slice(oOut + nD, oOut + 2 * nD),
|
|
268
|
+
Rp: out.slice(oOut + 2 * nD, oOut + 3 * nD),
|
|
269
|
+
Tp: out.slice(oOut + 3 * nD, oOut + 4 * nD),
|
|
270
|
+
Rrs: out.slice(oOut + 4 * nD, oOut + 5 * nD),
|
|
271
|
+
Rrp: out.slice(oOut + 5 * nD, oOut + 6 * nD),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Per-step spectra of a growing stack: one call returns the forward and
|
|
277
|
+
* substrate-side passes of the coated surface after every deposited layer,
|
|
278
|
+
* so all N step spectra cost about what the final one costs alone. Layers
|
|
279
|
+
* in DEPOSITION order (first deposited first).
|
|
280
|
+
* @param {number[]} lambdas
|
|
281
|
+
* @param {[number,number][]} n0List incident ñ per λ
|
|
282
|
+
* @param {[number,number][]} nsList substrate ñ per λ
|
|
283
|
+
* @param {[number,number][][]} layerNK [layer][λ] = ñ, deposition order
|
|
284
|
+
* @param {number[]} thick thicknesses (nm); 0 repeats the previous step
|
|
285
|
+
* @param {number} theta_deg
|
|
286
|
+
* @returns {{Rs,Ts,Rp,Tp,Rrs,Rrp}} each a Float64Array(N · nLam),
|
|
287
|
+
* step-major: value for step k at wavelength i is at [k·nLam + i]
|
|
288
|
+
*/
|
|
289
|
+
depositionSpectra(lambdas, n0List, nsList, layerNK, thick, theta_deg) {
|
|
290
|
+
const nLam = lambdas.length;
|
|
291
|
+
const N = thick.length;
|
|
292
|
+
const nOut = Math.max(1, N * nLam);
|
|
293
|
+
|
|
294
|
+
const lamPtr = this._alloc(nLam);
|
|
295
|
+
const n0Ptr = this._alloc(2 * nLam);
|
|
296
|
+
const nsPtr = this._alloc(2 * nLam);
|
|
297
|
+
const mPtr = this._alloc(Math.max(1, 2 * N * nLam));
|
|
298
|
+
const thPtr = this._alloc(Math.max(1, N));
|
|
299
|
+
const outPtrs = Array.from({ length: 6 }, () => this._alloc(nOut));
|
|
300
|
+
|
|
301
|
+
const lam = this._view(lamPtr, nLam);
|
|
302
|
+
const n0v = this._view(n0Ptr, 2 * nLam);
|
|
303
|
+
const nsv = this._view(nsPtr, 2 * nLam);
|
|
304
|
+
const mv = this._view(mPtr, Math.max(1, 2 * N * nLam));
|
|
305
|
+
const thv = this._view(thPtr, Math.max(1, N));
|
|
306
|
+
for (let i = 0; i < nLam; i++) {
|
|
307
|
+
lam[i] = lambdas[i];
|
|
308
|
+
n0v[2 * i] = n0List[i][0]; n0v[2 * i + 1] = n0List[i][1];
|
|
309
|
+
nsv[2 * i] = nsList[i][0]; nsv[2 * i + 1] = nsList[i][1];
|
|
310
|
+
}
|
|
311
|
+
for (let k = 0; k < N; k++) {
|
|
312
|
+
thv[k] = thick[k];
|
|
313
|
+
const row = layerNK[k];
|
|
314
|
+
const base = k * nLam * 2;
|
|
315
|
+
for (let i = 0; i < nLam; i++) {
|
|
316
|
+
mv[base + 2 * i] = row[i][0];
|
|
317
|
+
mv[base + 2 * i + 1] = row[i][1];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const ok = this._tmm_deposition_spectra(lamPtr, nLam, n0Ptr, nsPtr, mPtr, thPtr, N,
|
|
322
|
+
theta_deg, ...outPtrs);
|
|
323
|
+
if (!ok) {
|
|
324
|
+
for (const p of [lamPtr, n0Ptr, nsPtr, mPtr, thPtr, ...outPtrs]) this.free(p);
|
|
325
|
+
// The outputs were never written; returning them would hand the
|
|
326
|
+
// caller uninitialized memory as spectra.
|
|
327
|
+
throw new Error('tmmWasm: deposition-spectra state allocation failed');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const cp = (p) => Float64Array.from(this._view(p, nOut));
|
|
331
|
+
const [Rs, Ts, Rp, Tp, Rrs, Rrp] = outPtrs.map(cp);
|
|
332
|
+
for (const p of [lamPtr, n0Ptr, nsPtr, mPtr, thPtr, ...outPtrs]) this.free(p);
|
|
333
|
+
return { Rs, Ts, Rp, Tp, Rrs, Rrp };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Persistent growing-layer evaluator: the completed stack's products for
|
|
338
|
+
* the whole wavelength grid are folded once and kept in kernel memory;
|
|
339
|
+
* each sample() then answers one thickness of the growing layer across
|
|
340
|
+
* the grid. This is the shape a broadband monitor scan needs (a spectrum
|
|
341
|
+
* per scan while the layers beneath stay fixed), where monitorCurve is
|
|
342
|
+
* the single-λ, many-thicknesses shape.
|
|
343
|
+
*
|
|
344
|
+
* The completed stack arrives OUTERMOST FIRST, layer-major like
|
|
345
|
+
* depositionSpectra's matNK; zero-thickness entries are skipped.
|
|
346
|
+
*
|
|
347
|
+
* The returned handle owns kernel memory: call free() when the layer is
|
|
348
|
+
* done. A dropped handle is reclaimed by a finalizer eventually, but
|
|
349
|
+
* deterministic free() is what keeps a long run's footprint flat.
|
|
350
|
+
*
|
|
351
|
+
* @param {number[]} lambdas
|
|
352
|
+
* @param {[number,number][]} n0List incident ñ per λ
|
|
353
|
+
* @param {[number,number][]} nsList substrate ñ per λ
|
|
354
|
+
* @param {[number,number][][]} layerNK [layer][λ] = ñ, outermost first
|
|
355
|
+
* @param {number[]} thick completed thicknesses (nm)
|
|
356
|
+
* @param {number} theta_deg
|
|
357
|
+
* @returns {{setTop, sample, free}} setTop(ngList) declares the growing
|
|
358
|
+
* layer's ñ per λ; sample(d, out?) returns {Rs,Ts,Rp,Tp,Rrs,Rrp}
|
|
359
|
+
* (each Float64Array(nLam), written into `out` when given, so a
|
|
360
|
+
* scan loop can reuse one set of buffers).
|
|
361
|
+
*/
|
|
362
|
+
growingEval(lambdas, n0List, nsList, layerNK, thick, theta_deg) {
|
|
363
|
+
if (!this.hasGrowingEval()) throw new Error('tmmWasm: growing evaluator kernel not in this build');
|
|
364
|
+
const nLam = lambdas.length;
|
|
365
|
+
const NB = thick.length;
|
|
366
|
+
|
|
367
|
+
const lamPtr = this._alloc(nLam);
|
|
368
|
+
const n0Ptr = this._alloc(2 * nLam);
|
|
369
|
+
const nsPtr = this._alloc(2 * nLam);
|
|
370
|
+
const mPtr = this._alloc(Math.max(1, 2 * NB * nLam));
|
|
371
|
+
const thPtr = this._alloc(Math.max(1, NB));
|
|
372
|
+
const lam = this._view(lamPtr, nLam);
|
|
373
|
+
const n0v = this._view(n0Ptr, 2 * nLam);
|
|
374
|
+
const nsv = this._view(nsPtr, 2 * nLam);
|
|
375
|
+
const mv = this._view(mPtr, Math.max(1, 2 * NB * nLam));
|
|
376
|
+
const thv = this._view(thPtr, Math.max(1, NB));
|
|
377
|
+
for (let i = 0; i < nLam; i++) {
|
|
378
|
+
lam[i] = lambdas[i];
|
|
379
|
+
n0v[2 * i] = n0List[i][0]; n0v[2 * i + 1] = n0List[i][1];
|
|
380
|
+
nsv[2 * i] = nsList[i][0]; nsv[2 * i + 1] = nsList[i][1];
|
|
381
|
+
}
|
|
382
|
+
for (let k = 0; k < NB; k++) {
|
|
383
|
+
thv[k] = thick[k];
|
|
384
|
+
const row = layerNK[k];
|
|
385
|
+
const base = k * nLam * 2;
|
|
386
|
+
for (let i = 0; i < nLam; i++) {
|
|
387
|
+
mv[base + 2 * i] = row[i][0];
|
|
388
|
+
mv[base + 2 * i + 1] = row[i][1];
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
const handlePtr = this._growing_eval_create(lamPtr, nLam, theta_deg,
|
|
392
|
+
n0Ptr, nsPtr, mPtr, thPtr, NB);
|
|
393
|
+
for (const p of [lamPtr, n0Ptr, nsPtr, mPtr, thPtr]) this.free(p);
|
|
394
|
+
if (!handlePtr) throw new Error('tmmWasm: growing evaluator allocation failed');
|
|
395
|
+
|
|
396
|
+
const self = this;
|
|
397
|
+
const handle = {
|
|
398
|
+
ptr: handlePtr,
|
|
399
|
+
setTop(ngList) {
|
|
400
|
+
if (!this.ptr) throw new Error('tmmWasm: growing evaluator already freed');
|
|
401
|
+
const p = self._scratch(2 * nLam);
|
|
402
|
+
const buf = self._view(p, 2 * nLam);
|
|
403
|
+
for (let i = 0; i < nLam; i++) {
|
|
404
|
+
buf[2 * i] = ngList[i][0]; buf[2 * i + 1] = ngList[i][1];
|
|
405
|
+
}
|
|
406
|
+
self._growing_eval_set_top(this.ptr, p);
|
|
407
|
+
},
|
|
408
|
+
sample(d, out = null) {
|
|
409
|
+
if (!this.ptr) throw new Error('tmmWasm: growing evaluator already freed');
|
|
410
|
+
const p = self._scratch(6 * nLam);
|
|
411
|
+
const P = (block) => p + block * nLam * 8;
|
|
412
|
+
const ok = self._growing_eval_sample(this.ptr, d,
|
|
413
|
+
P(0), P(1), P(2), P(3), P(4), P(5));
|
|
414
|
+
// The kernel writes nothing without a declared growing layer;
|
|
415
|
+
// copying the arena anyway would hand back stale memory.
|
|
416
|
+
if (!ok) throw new Error('tmmWasm: growing evaluator sampled before setTop');
|
|
417
|
+
// Fresh view AFTER the call (memory may have grown → detach).
|
|
418
|
+
const buf = self._view(p, 6 * nLam);
|
|
419
|
+
if (!out) {
|
|
420
|
+
out = {
|
|
421
|
+
Rs: new Float64Array(nLam), Ts: new Float64Array(nLam),
|
|
422
|
+
Rp: new Float64Array(nLam), Tp: new Float64Array(nLam),
|
|
423
|
+
Rrs: new Float64Array(nLam), Rrp: new Float64Array(nLam),
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
out.Rs.set(buf.subarray(0, nLam));
|
|
427
|
+
out.Ts.set(buf.subarray(nLam, 2 * nLam));
|
|
428
|
+
out.Rp.set(buf.subarray(2 * nLam, 3 * nLam));
|
|
429
|
+
out.Tp.set(buf.subarray(3 * nLam, 4 * nLam));
|
|
430
|
+
out.Rrs.set(buf.subarray(4 * nLam, 5 * nLam));
|
|
431
|
+
out.Rrp.set(buf.subarray(5 * nLam, 6 * nLam));
|
|
432
|
+
return out;
|
|
433
|
+
},
|
|
434
|
+
free() {
|
|
435
|
+
if (!this.ptr) return;
|
|
436
|
+
growingEvalFinalizer?.unregister(this);
|
|
437
|
+
self._growing_eval_free(this.ptr);
|
|
438
|
+
this.ptr = 0;
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
growingEvalFinalizer?.register(handle,
|
|
442
|
+
{ wasm: this, ptr: handlePtr }, handle);
|
|
443
|
+
return handle;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Analytic thickness Jacobian for one (λ, θ, pol) : mirrors
|
|
448
|
+
* tmmThicknessJacobian(). layers used AS-IS (index parity).
|
|
449
|
+
* @returns {{R,T,A, dRdd:Float64Array, dTdd, dAdd, N}}
|
|
450
|
+
*/
|
|
451
|
+
tmmJacobian(lambda_nm, theta_deg, polCode, n0, ns, layers) {
|
|
452
|
+
const N = layers.length;
|
|
453
|
+
const M = Math.max(1, N);
|
|
454
|
+
// arena layout: layers[3N] | dRdd[M] | dTdd[M] | dAdd[M] | base[3]
|
|
455
|
+
const oLay = 0, oDR = 3 * N, oDT = oDR + M, oDA = oDT + M, oBase = oDA + M;
|
|
456
|
+
const need = oBase + 3;
|
|
457
|
+
const ptr = this._scratch(need);
|
|
458
|
+
const buf = this._view(ptr, need);
|
|
459
|
+
for (let i = 0; i < N; i++) {
|
|
460
|
+
buf[3 * i + 0] = layers[i].n[0];
|
|
461
|
+
buf[3 * i + 1] = layers[i].n[1];
|
|
462
|
+
buf[3 * i + 2] = layers[i].d;
|
|
463
|
+
}
|
|
464
|
+
const P = (off) => ptr + off * 8;
|
|
465
|
+
this._tmm_jacobian(lambda_nm, theta_deg, polCode | 0,
|
|
466
|
+
n0[0], n0[1], ns[0], ns[1], P(oLay), N, P(oDR), P(oDT), P(oDA), P(oBase));
|
|
467
|
+
// Re-create the view AFTER the kernel call (like tmmSpectrum): under
|
|
468
|
+
// ALLOW_MEMORY_GROWTH the kernel may grow wasm memory, which detaches the
|
|
469
|
+
// ArrayBuffer `buf` was created over : reading the stale `buf` then yields
|
|
470
|
+
// garbage / throws. `out` is a fresh view over the current buffer.
|
|
471
|
+
const out = this._view(ptr, need);
|
|
472
|
+
return {
|
|
473
|
+
R: out[oBase], T: out[oBase + 1], A: out[oBase + 2], N,
|
|
474
|
+
dRdd: out.slice(oDR, oDR + N),
|
|
475
|
+
dTdd: out.slice(oDT, oDT + N),
|
|
476
|
+
dAdd: out.slice(oDA, oDA + N),
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** True if the loaded module carries the Hessian kernel (newer build). */
|
|
481
|
+
hasHessian() { return !!this._tmm_hessian; }
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Analytic thickness Hessian for one (λ, θ, pol) : mirrors
|
|
485
|
+
* tmmThicknessHessian(). Returns first AND second derivatives; the N×N
|
|
486
|
+
* second-derivative blocks are reshaped into nested arrays (one Float64Array
|
|
487
|
+
* row per layer, FULL symmetric) so the shape matches the JS oracle exactly.
|
|
488
|
+
* @returns {{R,T,A, dRdd, dTdd, dAdd, d2Rdd, d2Tdd, d2Add, N}}
|
|
489
|
+
*/
|
|
490
|
+
tmmHessian(lambda_nm, theta_deg, polCode, n0, ns, layers) {
|
|
491
|
+
const N = layers.length;
|
|
492
|
+
const M = Math.max(1, N);
|
|
493
|
+
const NN = Math.max(1, N * N);
|
|
494
|
+
// arena: layers[3N] | dRdd[M] | dTdd[M] | dAdd[M] | d2R[NN] | d2T[NN] | d2A[NN] | base[3]
|
|
495
|
+
const oLay = 0, oDR = 3 * N, oDT = oDR + M, oDA = oDT + M,
|
|
496
|
+
oR2 = oDA + M, oT2 = oR2 + NN, oA2 = oT2 + NN, oBase = oA2 + NN;
|
|
497
|
+
const need = oBase + 3;
|
|
498
|
+
const ptr = this._scratch(need);
|
|
499
|
+
const buf = this._view(ptr, need);
|
|
500
|
+
for (let i = 0; i < N; i++) {
|
|
501
|
+
buf[3 * i + 0] = layers[i].n[0];
|
|
502
|
+
buf[3 * i + 1] = layers[i].n[1];
|
|
503
|
+
buf[3 * i + 2] = layers[i].d;
|
|
504
|
+
}
|
|
505
|
+
const P = (off) => ptr + off * 8;
|
|
506
|
+
this._tmm_hessian(lambda_nm, theta_deg, polCode | 0,
|
|
507
|
+
n0[0], n0[1], ns[0], ns[1], P(oLay), N,
|
|
508
|
+
P(oDR), P(oDT), P(oDA), P(oR2), P(oT2), P(oA2), P(oBase));
|
|
509
|
+
// Fresh view after the call (memory may have grown → buf detached).
|
|
510
|
+
const out = this._view(ptr, need);
|
|
511
|
+
const reshape = (off) => {
|
|
512
|
+
const rows = new Array(N);
|
|
513
|
+
for (let i = 0; i < N; i++) rows[i] = out.slice(off + i * N, off + i * N + N);
|
|
514
|
+
return rows;
|
|
515
|
+
};
|
|
516
|
+
return {
|
|
517
|
+
R: out[oBase], T: out[oBase + 1], A: out[oBase + 2], N,
|
|
518
|
+
dRdd: out.slice(oDR, oDR + N),
|
|
519
|
+
dTdd: out.slice(oDT, oDT + N),
|
|
520
|
+
dAdd: out.slice(oDA, oDA + N),
|
|
521
|
+
d2Rdd: reshape(oR2), d2Tdd: reshape(oT2), d2Add: reshape(oA2),
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** True if the loaded module carries the phase-dispersion kernel. */
|
|
526
|
+
hasPhase() { return !!this._tmm_phase_one; }
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Phase, group delay, GDD and TOD at one wavelength : mirrors
|
|
530
|
+
* tmmPhaseDispersion() in phase.js.
|
|
531
|
+
*
|
|
532
|
+
* @param {number[][]} n0Jet incident-medium index jet, 4 × [re, im]
|
|
533
|
+
* @param {number[][]} nsJet substrate index jet
|
|
534
|
+
* @param {{nJet:number[][], d:number}[]} layers
|
|
535
|
+
* @param {{omega?:number, sinTheta0Jet?:number[][]}} [options]
|
|
536
|
+
* @returns {{r: object|null, t: object|null}}
|
|
537
|
+
*/
|
|
538
|
+
tmmPhaseOne(lambda_nm, theta_deg, polCode, n0Jet, nsJet, layers, options = {}) {
|
|
539
|
+
const N = layers.length;
|
|
540
|
+
const omega = options.omega ?? omegaFromLambdaNm(lambda_nm);
|
|
541
|
+
const sinJet = options.sinTheta0Jet || null;
|
|
542
|
+
// arena: layerJets[8N] | thick[N] | n0[8] | ns[8] | sin[8] | out[10]
|
|
543
|
+
const oLay = 0, oThick = 8 * N, oN0 = oThick + N, oNs = oN0 + 8,
|
|
544
|
+
oSin = oNs + 8, oOut = oSin + 8;
|
|
545
|
+
const need = oOut + 10;
|
|
546
|
+
const ptr = this._scratch(need);
|
|
547
|
+
const buf = this._view(ptr, need);
|
|
548
|
+
for (let i = 0; i < N; i++) {
|
|
549
|
+
writeJet(buf, oLay + 8 * i, layers[i].nJet);
|
|
550
|
+
buf[oThick + i] = layers[i].d;
|
|
551
|
+
}
|
|
552
|
+
writeJet(buf, oN0, n0Jet);
|
|
553
|
+
writeJet(buf, oNs, nsJet);
|
|
554
|
+
if (sinJet) writeJet(buf, oSin, sinJet);
|
|
555
|
+
const P = (off) => ptr + off * 8;
|
|
556
|
+
this._tmm_phase_one(lambda_nm, omega, theta_deg, polCode | 0,
|
|
557
|
+
P(oN0), P(oNs), P(oLay), P(oThick), N, sinJet ? P(oSin) : 0, P(oOut));
|
|
558
|
+
const out = this._view(ptr, need);
|
|
559
|
+
return { r: readPhase(out, oOut), t: readPhase(out, oOut + 5) };
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Batched phase dispersion over a λ grid : the boundary-amortizing path.
|
|
564
|
+
*
|
|
565
|
+
* Unlike `tmmSpectrum` this takes one polarization, because the kernel is an
|
|
566
|
+
* order of magnitude dearer per sample and callers at normal incidence would
|
|
567
|
+
* otherwise pay twice for the same numbers.
|
|
568
|
+
*
|
|
569
|
+
* @param {number[]} lambdas
|
|
570
|
+
* @param {number[][][]} n0Jets index jet per λ
|
|
571
|
+
* @param {number[][][]} nsJets index jet per λ
|
|
572
|
+
* @param {number[][][][]} layerJets [layer][λ] = index jet
|
|
573
|
+
* @param {number[]} thick layer thicknesses (nm), length N
|
|
574
|
+
* @param {{omegas?:number[], sinJets?:number[][][]}} [options]
|
|
575
|
+
* @returns {{r: object, t: object}} each `{phaseRad, gd, gdd, tod,
|
|
576
|
+
* magnitudeSquared}` of Float64Array(nLam). Failed samples hold NaN.
|
|
577
|
+
*/
|
|
578
|
+
tmmPhaseSpectrum(lambdas, n0Jets, nsJets, layerJets, thick, theta_deg, polCode, options = {}) {
|
|
579
|
+
const nLam = lambdas.length;
|
|
580
|
+
const N = thick.length;
|
|
581
|
+
const omegas = options.omegas
|
|
582
|
+
|| lambdas.map(lambda => omegaFromLambdaNm(lambda));
|
|
583
|
+
const sinJets = options.sinJets || null;
|
|
584
|
+
|
|
585
|
+
const lamPtr = this._alloc(nLam);
|
|
586
|
+
const omPtr = this._alloc(nLam);
|
|
587
|
+
const n0Ptr = this._alloc(8 * nLam);
|
|
588
|
+
const nsPtr = this._alloc(8 * nLam);
|
|
589
|
+
const matPtr = this._alloc(Math.max(1, 8 * N * nLam));
|
|
590
|
+
const thPtr = this._alloc(Math.max(1, N));
|
|
591
|
+
const sinPtr = sinJets ? this._alloc(8 * nLam) : 0;
|
|
592
|
+
const outPtr = this._alloc(10 * nLam);
|
|
593
|
+
|
|
594
|
+
// Views created after all mallocs (the buffer may have grown/detached).
|
|
595
|
+
const lam = this._view(lamPtr, nLam);
|
|
596
|
+
const om = this._view(omPtr, nLam);
|
|
597
|
+
const n0v = this._view(n0Ptr, 8 * nLam);
|
|
598
|
+
const nsv = this._view(nsPtr, 8 * nLam);
|
|
599
|
+
const matv = this._view(matPtr, Math.max(1, 8 * N * nLam));
|
|
600
|
+
const thv = this._view(thPtr, Math.max(1, N));
|
|
601
|
+
const sinv = sinJets ? this._view(sinPtr, 8 * nLam) : null;
|
|
602
|
+
for (let i = 0; i < nLam; i++) {
|
|
603
|
+
lam[i] = lambdas[i];
|
|
604
|
+
om[i] = omegas[i];
|
|
605
|
+
writeJet(n0v, 8 * i, n0Jets[i]);
|
|
606
|
+
writeJet(nsv, 8 * i, nsJets[i]);
|
|
607
|
+
if (sinv) writeJet(sinv, 8 * i, sinJets[i]);
|
|
608
|
+
}
|
|
609
|
+
for (let k = 0; k < N; k++) {
|
|
610
|
+
thv[k] = thick[k];
|
|
611
|
+
const row = layerJets[k];
|
|
612
|
+
const base = k * nLam * 8;
|
|
613
|
+
for (let i = 0; i < nLam; i++) writeJet(matv, base + 8 * i, row[i]);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
this._tmm_phase_spectrum(lamPtr, omPtr, nLam, n0Ptr, nsPtr, matPtr, thPtr, N,
|
|
617
|
+
theta_deg, polCode | 0, sinPtr, outPtr);
|
|
618
|
+
|
|
619
|
+
// De-interleave into one array per quantity before freeing.
|
|
620
|
+
const out = this._view(outPtr, 10 * nLam);
|
|
621
|
+
const side = (base) => {
|
|
622
|
+
const q = {
|
|
623
|
+
phaseRad: new Float64Array(nLam), gd: new Float64Array(nLam),
|
|
624
|
+
gdd: new Float64Array(nLam), tod: new Float64Array(nLam),
|
|
625
|
+
magnitudeSquared: new Float64Array(nLam),
|
|
626
|
+
};
|
|
627
|
+
const keys = ['phaseRad', 'gd', 'gdd', 'tod', 'magnitudeSquared'];
|
|
628
|
+
for (let i = 0; i < nLam; i++) {
|
|
629
|
+
for (let j = 0; j < 5; j++) q[keys[j]][i] = out[10 * i + base + j];
|
|
630
|
+
}
|
|
631
|
+
return q;
|
|
632
|
+
};
|
|
633
|
+
const result = { r: side(0), t: side(5) };
|
|
634
|
+
for (const p of [lamPtr, omPtr, n0Ptr, nsPtr, matPtr, thPtr, outPtr]) this.free(p);
|
|
635
|
+
if (sinPtr) this.free(sinPtr);
|
|
636
|
+
return result;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Phase dispersion plus exact thickness derivatives : mirrors
|
|
641
|
+
* tmmPhaseThicknessJacobian(). Layers used AS-IS (index parity).
|
|
642
|
+
*
|
|
643
|
+
* @returns {{r, t}} each the phase quantities plus `dPhaseDeg`, `dGd`,
|
|
644
|
+
* `dGdd`, `dTod` and `dLogMagnitudeSquared` as Float64Array(N), or `null`
|
|
645
|
+
* arrays on overflow.
|
|
646
|
+
*/
|
|
647
|
+
tmmPhaseJacobian(lambda_nm, theta_deg, polCode, n0Jet, nsJet, layers, options = {}) {
|
|
648
|
+
const N = layers.length;
|
|
649
|
+
const M = Math.max(1, N);
|
|
650
|
+
const omega = options.omega ?? omegaFromLambdaNm(lambda_nm);
|
|
651
|
+
const sinJet = options.sinTheta0Jet || null;
|
|
652
|
+
// arena: layerJets[8N] | thick[N] | n0[8] | ns[8] | sin[8] | out[10] | deriv[10M]
|
|
653
|
+
const oLay = 0, oThick = 8 * N, oN0 = oThick + N, oNs = oN0 + 8,
|
|
654
|
+
oSin = oNs + 8, oOut = oSin + 8, oDeriv = oOut + 10;
|
|
655
|
+
const need = oDeriv + 10 * M;
|
|
656
|
+
const ptr = this._scratch(need);
|
|
657
|
+
const buf = this._view(ptr, need);
|
|
658
|
+
for (let i = 0; i < N; i++) {
|
|
659
|
+
writeJet(buf, oLay + 8 * i, layers[i].nJet);
|
|
660
|
+
buf[oThick + i] = layers[i].d;
|
|
661
|
+
}
|
|
662
|
+
writeJet(buf, oN0, n0Jet);
|
|
663
|
+
writeJet(buf, oNs, nsJet);
|
|
664
|
+
if (sinJet) writeJet(buf, oSin, sinJet);
|
|
665
|
+
const P = (off) => ptr + off * 8;
|
|
666
|
+
this._tmm_phase_jacobian(lambda_nm, omega, theta_deg, polCode | 0,
|
|
667
|
+
P(oN0), P(oNs), P(oLay), P(oThick), N, sinJet ? P(oSin) : 0,
|
|
668
|
+
P(oOut), P(oDeriv));
|
|
669
|
+
const out = this._view(ptr, need);
|
|
670
|
+
const side = (phaseBase, derivBase) => {
|
|
671
|
+
const base = readPhase(out, phaseBase);
|
|
672
|
+
if (!base) return null;
|
|
673
|
+
// The kernel fills the whole block with NaN when the matrix product
|
|
674
|
+
// overflowed and the prefix/suffix decomposition had to be abandoned.
|
|
675
|
+
const overflowed = N > 0 && Number.isNaN(out[oDeriv + derivBase * N]);
|
|
676
|
+
const take = (q) => overflowed
|
|
677
|
+
? null
|
|
678
|
+
: out.slice(oDeriv + (derivBase + q) * N, oDeriv + (derivBase + q) * N + N);
|
|
679
|
+
return {
|
|
680
|
+
...base,
|
|
681
|
+
dPhaseDeg: take(0), dGd: take(1), dGdd: take(2), dTod: take(3),
|
|
682
|
+
dLogMagnitudeSquared: take(4),
|
|
683
|
+
};
|
|
684
|
+
};
|
|
685
|
+
return { r: side(oOut, 0), t: side(oOut + 5, 5) };
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/** True if the loaded module carries the batched phase Jacobian. */
|
|
689
|
+
hasPhaseJacobianSpectrum() { return !!this._tmm_phase_jacobian_spectrum; }
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Batched phase Jacobian over a λ grid : tmmPhaseJacobian at every
|
|
693
|
+
* wavelength in one call, for fitting a whole measured spectrum of phase
|
|
694
|
+
* quantities. Arguments as tmmPhaseSpectrum.
|
|
695
|
+
*
|
|
696
|
+
* @returns {{r: object, t: object}} each the five Float64Array(nLam) of
|
|
697
|
+
* tmmPhaseSpectrum plus `dPhaseDeg`, `dGd`, `dGdd`, `dTod` and
|
|
698
|
+
* `dLogMagnitudeSquared` as Float64Array(nLam × N), the derivative for
|
|
699
|
+
* wavelength i and layer k at `[i * N + k]`. A wavelength whose matrix
|
|
700
|
+
* product overflowed holds NaN across its derivative block.
|
|
701
|
+
*/
|
|
702
|
+
tmmPhaseJacobianSpectrum(lambdas, n0Jets, nsJets, layerJets, thick, theta_deg, polCode, options = {}) {
|
|
703
|
+
const nLam = lambdas.length;
|
|
704
|
+
const N = thick.length;
|
|
705
|
+
const omegas = options.omegas
|
|
706
|
+
|| lambdas.map(lambda => omegaFromLambdaNm(lambda));
|
|
707
|
+
const sinJets = options.sinJets || null;
|
|
708
|
+
const nDeriv = Math.max(1, 10 * N * nLam);
|
|
709
|
+
|
|
710
|
+
const lamPtr = this._alloc(nLam);
|
|
711
|
+
const omPtr = this._alloc(nLam);
|
|
712
|
+
const n0Ptr = this._alloc(8 * nLam);
|
|
713
|
+
const nsPtr = this._alloc(8 * nLam);
|
|
714
|
+
const matPtr = this._alloc(Math.max(1, 8 * N * nLam));
|
|
715
|
+
const thPtr = this._alloc(Math.max(1, N));
|
|
716
|
+
const sinPtr = sinJets ? this._alloc(8 * nLam) : 0;
|
|
717
|
+
const outPtr = this._alloc(10 * nLam);
|
|
718
|
+
const derivPtr = this._alloc(nDeriv);
|
|
719
|
+
|
|
720
|
+
const lam = this._view(lamPtr, nLam);
|
|
721
|
+
const om = this._view(omPtr, nLam);
|
|
722
|
+
const n0v = this._view(n0Ptr, 8 * nLam);
|
|
723
|
+
const nsv = this._view(nsPtr, 8 * nLam);
|
|
724
|
+
const matv = this._view(matPtr, Math.max(1, 8 * N * nLam));
|
|
725
|
+
const thv = this._view(thPtr, Math.max(1, N));
|
|
726
|
+
const sinv = sinJets ? this._view(sinPtr, 8 * nLam) : null;
|
|
727
|
+
for (let i = 0; i < nLam; i++) {
|
|
728
|
+
lam[i] = lambdas[i];
|
|
729
|
+
om[i] = omegas[i];
|
|
730
|
+
writeJet(n0v, 8 * i, n0Jets[i]);
|
|
731
|
+
writeJet(nsv, 8 * i, nsJets[i]);
|
|
732
|
+
if (sinv) writeJet(sinv, 8 * i, sinJets[i]);
|
|
733
|
+
}
|
|
734
|
+
for (let k = 0; k < N; k++) {
|
|
735
|
+
thv[k] = thick[k];
|
|
736
|
+
const row = layerJets[k];
|
|
737
|
+
const base = k * nLam * 8;
|
|
738
|
+
for (let i = 0; i < nLam; i++) writeJet(matv, base + 8 * i, row[i]);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
this._tmm_phase_jacobian_spectrum(lamPtr, omPtr, nLam, n0Ptr, nsPtr, matPtr, thPtr, N,
|
|
742
|
+
theta_deg, polCode | 0, sinPtr, outPtr, derivPtr);
|
|
743
|
+
|
|
744
|
+
// De-interleave: per λ the kernel writes 10 phase values and then a
|
|
745
|
+
// [side][quantity][layer] block of 10 × N derivatives.
|
|
746
|
+
const out = this._view(outPtr, 10 * nLam);
|
|
747
|
+
const deriv = this._view(derivPtr, nDeriv);
|
|
748
|
+
const side = (base, derivBase) => {
|
|
749
|
+
const q = {
|
|
750
|
+
phaseRad: new Float64Array(nLam), gd: new Float64Array(nLam),
|
|
751
|
+
gdd: new Float64Array(nLam), tod: new Float64Array(nLam),
|
|
752
|
+
magnitudeSquared: new Float64Array(nLam),
|
|
753
|
+
dPhaseDeg: new Float64Array(nLam * N), dGd: new Float64Array(nLam * N),
|
|
754
|
+
dGdd: new Float64Array(nLam * N), dTod: new Float64Array(nLam * N),
|
|
755
|
+
dLogMagnitudeSquared: new Float64Array(nLam * N),
|
|
756
|
+
};
|
|
757
|
+
const keys = ['phaseRad', 'gd', 'gdd', 'tod', 'magnitudeSquared'];
|
|
758
|
+
const derivKeys = ['dPhaseDeg', 'dGd', 'dGdd', 'dTod', 'dLogMagnitudeSquared'];
|
|
759
|
+
for (let i = 0; i < nLam; i++) {
|
|
760
|
+
for (let j = 0; j < 5; j++) q[keys[j]][i] = out[10 * i + base + j];
|
|
761
|
+
for (let j = 0; j < derivKeys.length; j++) {
|
|
762
|
+
const from = 10 * N * i + (derivBase + j) * N;
|
|
763
|
+
for (let k = 0; k < N; k++) q[derivKeys[j]][i * N + k] = deriv[from + k];
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return q;
|
|
767
|
+
};
|
|
768
|
+
const result = { r: side(0, 0), t: side(5, 5) };
|
|
769
|
+
for (const p of [lamPtr, omPtr, n0Ptr, nsPtr, matPtr, thPtr, outPtr, derivPtr]) this.free(p);
|
|
770
|
+
if (sinPtr) this.free(sinPtr);
|
|
771
|
+
return result;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Analytic needle P-function scan : mirrors tmmNeedleScan() in
|
|
776
|
+
* thinFilmMath.js, reshaping the flat WASM output into the SAME nested
|
|
777
|
+
* structure the synthesis scanners consume.
|
|
778
|
+
* @param {{n:[number,number],d:number}[]} layers used AS-IS (index parity)
|
|
779
|
+
* @param {[number,number][]} candidateNs candidate ñ
|
|
780
|
+
* @param {number[]} intraFracs intra-layer split fractions
|
|
781
|
+
* @returns {{R,T,A,N, gaps:Array, intra:Array}}
|
|
782
|
+
* gaps[pos][ci] = {dR,dT,dA} (pos = 0..N)
|
|
783
|
+
* intra[k][fi] = {frac, perCand:[{dR,dT,dA}]}
|
|
784
|
+
*/
|
|
785
|
+
tmmNeedleScan(lambda_nm, theta_deg, polCode, n0, ns, layers, candidateNs, intraFracs = []) {
|
|
786
|
+
const N = layers.length;
|
|
787
|
+
const nCand = candidateNs.length;
|
|
788
|
+
const nFrac = intraFracs.length;
|
|
789
|
+
const nGap = (N + 1) * nCand * 3;
|
|
790
|
+
const nIntra = Math.max(1, N * nFrac * nCand * 3);
|
|
791
|
+
|
|
792
|
+
const layPtr = this._alloc(Math.max(1, 3 * N));
|
|
793
|
+
const candPtr = this._alloc(Math.max(1, 2 * nCand));
|
|
794
|
+
const fracPtr = this._alloc(Math.max(1, nFrac));
|
|
795
|
+
const basePtr = this._alloc(3);
|
|
796
|
+
const gapPtr = this._alloc(Math.max(1, nGap));
|
|
797
|
+
const intraPtr = this._alloc(nIntra);
|
|
798
|
+
|
|
799
|
+
const lay = this._view(layPtr, Math.max(1, 3 * N));
|
|
800
|
+
for (let i = 0; i < N; i++) {
|
|
801
|
+
lay[3 * i + 0] = layers[i].n[0];
|
|
802
|
+
lay[3 * i + 1] = layers[i].n[1];
|
|
803
|
+
lay[3 * i + 2] = layers[i].d;
|
|
804
|
+
}
|
|
805
|
+
const cand = this._view(candPtr, Math.max(1, 2 * nCand));
|
|
806
|
+
for (let c = 0; c < nCand; c++) { cand[2 * c] = candidateNs[c][0]; cand[2 * c + 1] = candidateNs[c][1]; }
|
|
807
|
+
const frac = this._view(fracPtr, Math.max(1, nFrac));
|
|
808
|
+
for (let i = 0; i < nFrac; i++) frac[i] = intraFracs[i];
|
|
809
|
+
|
|
810
|
+
this._tmm_needle_scan(lambda_nm, theta_deg, polCode | 0,
|
|
811
|
+
n0[0], n0[1], ns[0], ns[1], layPtr, N, candPtr, nCand, fracPtr, nFrac,
|
|
812
|
+
basePtr, gapPtr, intraPtr);
|
|
813
|
+
|
|
814
|
+
// Copy outputs out before freeing, reshaping to the JS nested layout.
|
|
815
|
+
const base = this._view(basePtr, 3);
|
|
816
|
+
const R = base[0], T = base[1], A = base[2];
|
|
817
|
+
const gapV = this._view(gapPtr, Math.max(1, nGap));
|
|
818
|
+
const gaps = new Array(N + 1);
|
|
819
|
+
for (let pos = 0; pos <= N; pos++) {
|
|
820
|
+
const row = new Array(nCand);
|
|
821
|
+
for (let c = 0; c < nCand; c++) {
|
|
822
|
+
const o = (pos * nCand + c) * 3;
|
|
823
|
+
row[c] = { dR: gapV[o], dT: gapV[o + 1], dA: gapV[o + 2] };
|
|
824
|
+
}
|
|
825
|
+
gaps[pos] = row;
|
|
826
|
+
}
|
|
827
|
+
const intra = [];
|
|
828
|
+
if (nFrac > 0) {
|
|
829
|
+
const intraV = this._view(intraPtr, nIntra);
|
|
830
|
+
for (let k = 0; k < N; k++) {
|
|
831
|
+
const rowK = [];
|
|
832
|
+
for (let fi = 0; fi < nFrac; fi++) {
|
|
833
|
+
const perCand = new Array(nCand);
|
|
834
|
+
for (let c = 0; c < nCand; c++) {
|
|
835
|
+
const o = ((k * nFrac + fi) * nCand + c) * 3;
|
|
836
|
+
perCand[c] = { dR: intraV[o], dT: intraV[o + 1], dA: intraV[o + 2] };
|
|
837
|
+
}
|
|
838
|
+
rowK.push({ frac: intraFracs[fi], perCand });
|
|
839
|
+
}
|
|
840
|
+
intra.push(rowK);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
for (const p of [layPtr, candPtr, fracPtr, basePtr, gapPtr, intraPtr]) this.free(p);
|
|
845
|
+
return { R, T, A, gaps, intra, N };
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/** Instantiate from raw bytes (ArrayBuffer / Uint8Array). Sets the singleton. */
|
|
850
|
+
export async function instantiateTmmWasm(bytes) {
|
|
851
|
+
const { instance } = await WebAssembly.instantiate(
|
|
852
|
+
bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes), wasmImports());
|
|
853
|
+
_instance = new TmmWasmInstance(instance);
|
|
854
|
+
return _instance;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/** Renderer/Node helper: fetch the `.wasm` at `url` and instantiate it. */
|
|
858
|
+
export function initTmmWasmFromUrl(url) {
|
|
859
|
+
if (_initPromise) return _initPromise;
|
|
860
|
+
_initPromise = (async () => {
|
|
861
|
+
try {
|
|
862
|
+
const resp = await fetch(url);
|
|
863
|
+
if (!resp.ok) throw new Error(`fetch ${url} → ${resp.status}`);
|
|
864
|
+
const buf = await resp.arrayBuffer();
|
|
865
|
+
await instantiateTmmWasm(buf);
|
|
866
|
+
return true;
|
|
867
|
+
} catch (e) {
|
|
868
|
+
// Not built yet / not found : silent fallback to JS.
|
|
869
|
+
_instance = null;
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
})();
|
|
873
|
+
return _initPromise;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
export function setTmmWasmEnabled(on) { _enabled = !!on; }
|
|
877
|
+
/** TEST-ONLY: inject a TmmWasmInstance (or null) directly, bypassing the .wasm
|
|
878
|
+
* fetch/instantiate, so the integration seam can be exercised with a mock. */
|
|
879
|
+
export function __setTmmWasmInstanceForTest(inst) { _instance = inst; }
|
|
880
|
+
|
|
881
|
+
// ── Cross-thread plumbing ────────────────────────────────────────────────────
|
|
882
|
+
// The renderer (main thread) loads the .wasm bytes once via IPC, instantiates
|
|
883
|
+
// its own module, and BROADCASTS the same bytes to each pool/worker (workers
|
|
884
|
+
// can't fetch a file:// asset under contextIsolation). Each worker instantiates
|
|
885
|
+
// its OWN module (no shared memory) and enables the flag.
|
|
886
|
+
|
|
887
|
+
let _workerBytes = null; // main-side: raw bytes to hand to workers
|
|
888
|
+
let _workerInitPromise = null; // worker-side: in-flight instantiation
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* MAIN THREAD bootstrap. Store the bytes for worker broadcast and, if the user
|
|
892
|
+
* enabled the feature, instantiate the main-thread module and flip the flag.
|
|
893
|
+
* Safe to call once at startup; failures fall back to JS silently.
|
|
894
|
+
*/
|
|
895
|
+
export async function initTmmWasmMainThread(bytes, enabled) {
|
|
896
|
+
if (bytes) _workerBytes = bytes; // remember for workers + later toggles
|
|
897
|
+
if (!enabled) { _enabled = false; return false; } // toggle off → JS everywhere
|
|
898
|
+
if (!_workerBytes) return false; // artifact never loaded
|
|
899
|
+
if (!_instance) { // instantiate once; reuse on re-toggle
|
|
900
|
+
try { await instantiateTmmWasm(_workerBytes); }
|
|
901
|
+
catch (_) { _instance = null; _enabled = false; return false; }
|
|
902
|
+
}
|
|
903
|
+
_enabled = true;
|
|
904
|
+
return true;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/** MAIN THREAD: bytes to ship to a worker : only when the feature is active. */
|
|
908
|
+
export function getTmmWasmBytesForWorker() {
|
|
909
|
+
return (_enabled && _workerBytes) ? _workerBytes : null;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* WORKER side: kick off one-time instantiation from broadcast bytes and enable
|
|
914
|
+
* the flag in this worker. Idempotent; no-op without bytes or once instantiated.
|
|
915
|
+
*/
|
|
916
|
+
export function noteTmmWasmBytes(bytes) {
|
|
917
|
+
if (!bytes || _instance || _workerInitPromise) return;
|
|
918
|
+
_workerInitPromise = instantiateTmmWasm(bytes)
|
|
919
|
+
.then(() => { _enabled = true; return true; })
|
|
920
|
+
.catch(() => { _instance = null; _enabled = false; return false; });
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/** WORKER side: await any in-flight instantiation before processing a job. */
|
|
924
|
+
export function awaitTmmWasmReady() {
|
|
925
|
+
return _workerInitPromise || Promise.resolve(_instance !== null);
|
|
926
|
+
}
|
|
927
|
+
export function isTmmWasmEnabled() { return _enabled; }
|
|
928
|
+
export function isTmmWasmReady() { return _instance !== null; }
|
|
929
|
+
/** Active iff the feature flag is on AND a module is instantiated. */
|
|
930
|
+
export function tmmWasmActive() { return _enabled && _instance !== null; }
|
|
931
|
+
export function getTmmWasm() { return _instance; }
|