tmmcore 0.2.0 → 0.3.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/build.ps1 +5 -1
- package/src/tmmWasm.js +247 -0
- package/src/tmm_kernel.c +330 -0
- package/src/tmm_kernel.wasm +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmmcore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Transfer-matrix method for multilayer thin-film optics, with exact analytic derivatives and phase dispersion. JavaScript, C and WebAssembly.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"LICENSE"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
-
"test": "node tests/equivalence.mjs",
|
|
23
|
+
"test": "node tests/equivalence.mjs && node tests/growing_equivalence.mjs && node tests/growing_eval_equivalence.mjs",
|
|
24
24
|
"compare": "node benchmarks/compare.mjs",
|
|
25
25
|
"examples": "node examples/01-single-layer.mjs && node examples/02-ar-coating.mjs && node examples/03-metal-mirror.mjs && node examples/04-refine.mjs && node examples/05-angle-map.mjs && node examples/06-needle.mjs && node examples/07-jacobian-map.mjs && node examples/08-group-delay.mjs",
|
|
26
|
-
"build:wasm": "emcc src/tmm_kernel.c -O3 --no-entry -sSTANDALONE_WASM=1 -sALLOW_MEMORY_GROWTH=1 -sEXPORTED_FUNCTIONS=_tmm_one,_tmm_spectrum,_tmm_jacobian,_tmm_needle_scan,_tmm_hessian,_tmm_phase_one,_tmm_phase_spectrum,_tmm_phase_jacobian,_malloc,_free -o src/tmm_kernel.wasm",
|
|
26
|
+
"build:wasm": "emcc src/tmm_kernel.c -O3 --no-entry -sSTANDALONE_WASM=1 -sALLOW_MEMORY_GROWTH=1 -sEXPORTED_FUNCTIONS=_tmm_one,_tmm_spectrum,_tmm_jacobian,_tmm_needle_scan,_tmm_hessian,_tmm_phase_one,_tmm_phase_spectrum,_tmm_phase_jacobian,_tmm_monitor_curve,_tmm_deposition_spectra,_tmm_growing_eval_create,_tmm_growing_eval_set_top,_tmm_growing_eval_sample,_tmm_growing_eval_free,_malloc,_free -o src/tmm_kernel.wasm",
|
|
27
27
|
"build:native": "cc -std=c99 -pedantic -Wall -Wextra -O2 -c src/tmm_kernel.c -o tmm_kernel.o"
|
|
28
28
|
},
|
|
29
29
|
"keywords": [
|
package/src/build.ps1
CHANGED
|
@@ -111,7 +111,7 @@ $emccArgs = @(
|
|
|
111
111
|
'--no-entry'
|
|
112
112
|
'-sSTANDALONE_WASM=1'
|
|
113
113
|
'-sALLOW_MEMORY_GROWTH=1'
|
|
114
|
-
'-sEXPORTED_FUNCTIONS=_tmm_one,_tmm_spectrum,_tmm_jacobian,_tmm_needle_scan,_tmm_hessian,_tmm_phase_one,_tmm_phase_spectrum,_tmm_phase_jacobian,_malloc,_free'
|
|
114
|
+
'-sEXPORTED_FUNCTIONS=_tmm_one,_tmm_spectrum,_tmm_jacobian,_tmm_needle_scan,_tmm_hessian,_tmm_phase_one,_tmm_phase_spectrum,_tmm_phase_jacobian,_tmm_monitor_curve,_tmm_deposition_spectra,_tmm_growing_eval_create,_tmm_growing_eval_set_top,_tmm_growing_eval_sample,_tmm_growing_eval_free,_malloc,_free'
|
|
115
115
|
'-o'
|
|
116
116
|
'src/tmm_kernel.wasm'
|
|
117
117
|
)
|
|
@@ -121,3 +121,7 @@ if ($LASTEXITCODE -ne 0) { Write-Error "emcc failed with exit code $LASTEXITCODE
|
|
|
121
121
|
Write-Host "Built src/tmm_kernel.wasm"
|
|
122
122
|
node tests/equivalence.mjs
|
|
123
123
|
if ($LASTEXITCODE -ne 0) { Write-Error "equivalence tests failed with exit code $LASTEXITCODE."; exit 1 }
|
|
124
|
+
node tests/growing_equivalence.mjs
|
|
125
|
+
if ($LASTEXITCODE -ne 0) { Write-Error "growing_equivalence tests failed with exit code $LASTEXITCODE."; exit 1 }
|
|
126
|
+
node tests/growing_eval_equivalence.mjs
|
|
127
|
+
if ($LASTEXITCODE -ne 0) { Write-Error "growing_eval_equivalence tests failed with exit code $LASTEXITCODE."; exit 1 }
|
package/src/tmmWasm.js
CHANGED
|
@@ -59,6 +59,15 @@ function wasmImports() {
|
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
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
|
+
|
|
62
71
|
export class TmmWasmInstance {
|
|
63
72
|
constructor(instance) {
|
|
64
73
|
const ex = instance.exports;
|
|
@@ -82,6 +91,16 @@ export class TmmWasmInstance {
|
|
|
82
91
|
this._tmm_phase_one = ex.tmm_phase_one || ex._tmm_phase_one || null;
|
|
83
92
|
this._tmm_phase_spectrum = ex.tmm_phase_spectrum || ex._tmm_phase_spectrum || null;
|
|
84
93
|
this._tmm_phase_jacobian = ex.tmm_phase_jacobian || ex._tmm_phase_jacobian || null;
|
|
94
|
+
// Optional, same reason: the growing-stack kernels (monitor curve and
|
|
95
|
+
// per-step deposition spectra) arrived after all of the above.
|
|
96
|
+
this._tmm_monitor_curve = ex.tmm_monitor_curve || ex._tmm_monitor_curve || null;
|
|
97
|
+
this._tmm_deposition_spectra = ex.tmm_deposition_spectra || ex._tmm_deposition_spectra || null;
|
|
98
|
+
// Optional, same reason: the persistent growing-layer evaluator
|
|
99
|
+
// (wavelength-grid-per-call) arrived after the two kernels above.
|
|
100
|
+
this._growing_eval_create = ex.tmm_growing_eval_create || ex._tmm_growing_eval_create || null;
|
|
101
|
+
this._growing_eval_set_top = ex.tmm_growing_eval_set_top || ex._tmm_growing_eval_set_top || null;
|
|
102
|
+
this._growing_eval_sample = ex.tmm_growing_eval_sample || ex._tmm_growing_eval_sample || null;
|
|
103
|
+
this._growing_eval_free = ex.tmm_growing_eval_free || ex._tmm_growing_eval_free || null;
|
|
85
104
|
const missingExports = !this.malloc || !this.free || !this._tmm_one ||
|
|
86
105
|
!this._tmm_spectrum || !this._tmm_jacobian || !this._tmm_needle_scan;
|
|
87
106
|
if (missingExports) {
|
|
@@ -192,6 +211,234 @@ export class TmmWasmInstance {
|
|
|
192
211
|
return res;
|
|
193
212
|
}
|
|
194
213
|
|
|
214
|
+
/** True if the loaded module carries the growing-stack kernels. */
|
|
215
|
+
hasGrowingKernels() {
|
|
216
|
+
return !!(this._tmm_monitor_curve && this._tmm_deposition_spectra);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** True if the loaded module carries the persistent growing-layer evaluator. */
|
|
220
|
+
hasGrowingEval() {
|
|
221
|
+
return !!(this._growing_eval_create && this._growing_eval_set_top
|
|
222
|
+
&& this._growing_eval_sample && this._growing_eval_free);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Monitor curve of one growing layer at one wavelength: the completed
|
|
227
|
+
* stack's matrix is built once, then every sample thickness costs one 2×2
|
|
228
|
+
* multiply. Returns the forward and substrate-side passes of the coated
|
|
229
|
+
* surface, both polarizations; the caller does the incoherent slab
|
|
230
|
+
* combination (or forms A = 1−R−T for a semi-infinite substrate).
|
|
231
|
+
* @param {[number,number]} n0 incident ñ
|
|
232
|
+
* @param {[number,number]} ns substrate ñ
|
|
233
|
+
* @param {{n:[number,number],d:number}[]} baseLayers completed stack,
|
|
234
|
+
* outermost first
|
|
235
|
+
* @param {[number,number]} ngNK growing layer ñ
|
|
236
|
+
* @param {ArrayLike<number>} dArr sample thicknesses (nm)
|
|
237
|
+
* @returns {{Rs,Ts,Rp,Tp,Rrs,Rrp}} each a Float64Array(dArr.length)
|
|
238
|
+
*/
|
|
239
|
+
monitorCurve(lambda_nm, theta_deg, n0, ns, baseLayers, ngNK, dArr) {
|
|
240
|
+
const NB = baseLayers.length;
|
|
241
|
+
const nD = dArr.length;
|
|
242
|
+
// arena: base[3NB] | d[nD] | Rs,Ts,Rp,Tp,Rrs,Rrp [6·nD]
|
|
243
|
+
const oBase = 0, oD = 3 * NB, oOut = oD + nD;
|
|
244
|
+
const need = oOut + 6 * nD;
|
|
245
|
+
const ptr = this._scratch(need);
|
|
246
|
+
const buf = this._view(ptr, need);
|
|
247
|
+
for (let i = 0; i < NB; i++) {
|
|
248
|
+
buf[3 * i + 0] = baseLayers[i].n[0];
|
|
249
|
+
buf[3 * i + 1] = baseLayers[i].n[1];
|
|
250
|
+
buf[3 * i + 2] = baseLayers[i].d;
|
|
251
|
+
}
|
|
252
|
+
for (let k = 0; k < nD; k++) buf[oD + k] = dArr[k];
|
|
253
|
+
const P = (off) => ptr + off * 8;
|
|
254
|
+
this._tmm_monitor_curve(lambda_nm, theta_deg,
|
|
255
|
+
n0[0], n0[1], ns[0], ns[1], P(oBase), NB, ngNK[0], ngNK[1],
|
|
256
|
+
P(oD), nD,
|
|
257
|
+
P(oOut), P(oOut + nD), P(oOut + 2 * nD), P(oOut + 3 * nD),
|
|
258
|
+
P(oOut + 4 * nD), P(oOut + 5 * nD));
|
|
259
|
+
// Fresh view after the call: memory growth detaches the old buffer.
|
|
260
|
+
const out = this._view(ptr, need);
|
|
261
|
+
return {
|
|
262
|
+
Rs: out.slice(oOut, oOut + nD),
|
|
263
|
+
Ts: out.slice(oOut + nD, oOut + 2 * nD),
|
|
264
|
+
Rp: out.slice(oOut + 2 * nD, oOut + 3 * nD),
|
|
265
|
+
Tp: out.slice(oOut + 3 * nD, oOut + 4 * nD),
|
|
266
|
+
Rrs: out.slice(oOut + 4 * nD, oOut + 5 * nD),
|
|
267
|
+
Rrp: out.slice(oOut + 5 * nD, oOut + 6 * nD),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Per-step spectra of a growing stack: one call returns the forward and
|
|
273
|
+
* substrate-side passes of the coated surface after every deposited layer,
|
|
274
|
+
* so all N step spectra cost about what the final one costs alone. Layers
|
|
275
|
+
* in DEPOSITION order (first deposited first).
|
|
276
|
+
* @param {number[]} lambdas
|
|
277
|
+
* @param {[number,number][]} n0List incident ñ per λ
|
|
278
|
+
* @param {[number,number][]} nsList substrate ñ per λ
|
|
279
|
+
* @param {[number,number][][]} layerNK [layer][λ] = ñ, deposition order
|
|
280
|
+
* @param {number[]} thick thicknesses (nm); 0 repeats the previous step
|
|
281
|
+
* @param {number} theta_deg
|
|
282
|
+
* @returns {{Rs,Ts,Rp,Tp,Rrs,Rrp}} each a Float64Array(N · nLam),
|
|
283
|
+
* step-major: value for step k at wavelength i is at [k·nLam + i]
|
|
284
|
+
*/
|
|
285
|
+
depositionSpectra(lambdas, n0List, nsList, layerNK, thick, theta_deg) {
|
|
286
|
+
const nLam = lambdas.length;
|
|
287
|
+
const N = thick.length;
|
|
288
|
+
const nOut = Math.max(1, N * nLam);
|
|
289
|
+
|
|
290
|
+
const lamPtr = this._alloc(nLam);
|
|
291
|
+
const n0Ptr = this._alloc(2 * nLam);
|
|
292
|
+
const nsPtr = this._alloc(2 * nLam);
|
|
293
|
+
const mPtr = this._alloc(Math.max(1, 2 * N * nLam));
|
|
294
|
+
const thPtr = this._alloc(Math.max(1, N));
|
|
295
|
+
const outPtrs = Array.from({ length: 6 }, () => this._alloc(nOut));
|
|
296
|
+
|
|
297
|
+
const lam = this._view(lamPtr, nLam);
|
|
298
|
+
const n0v = this._view(n0Ptr, 2 * nLam);
|
|
299
|
+
const nsv = this._view(nsPtr, 2 * nLam);
|
|
300
|
+
const mv = this._view(mPtr, Math.max(1, 2 * N * nLam));
|
|
301
|
+
const thv = this._view(thPtr, Math.max(1, N));
|
|
302
|
+
for (let i = 0; i < nLam; i++) {
|
|
303
|
+
lam[i] = lambdas[i];
|
|
304
|
+
n0v[2 * i] = n0List[i][0]; n0v[2 * i + 1] = n0List[i][1];
|
|
305
|
+
nsv[2 * i] = nsList[i][0]; nsv[2 * i + 1] = nsList[i][1];
|
|
306
|
+
}
|
|
307
|
+
for (let k = 0; k < N; k++) {
|
|
308
|
+
thv[k] = thick[k];
|
|
309
|
+
const row = layerNK[k];
|
|
310
|
+
const base = k * nLam * 2;
|
|
311
|
+
for (let i = 0; i < nLam; i++) {
|
|
312
|
+
mv[base + 2 * i] = row[i][0];
|
|
313
|
+
mv[base + 2 * i + 1] = row[i][1];
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const ok = this._tmm_deposition_spectra(lamPtr, nLam, n0Ptr, nsPtr, mPtr, thPtr, N,
|
|
318
|
+
theta_deg, ...outPtrs);
|
|
319
|
+
if (!ok) {
|
|
320
|
+
for (const p of [lamPtr, n0Ptr, nsPtr, mPtr, thPtr, ...outPtrs]) this.free(p);
|
|
321
|
+
// The outputs were never written; returning them would hand the
|
|
322
|
+
// caller uninitialized memory as spectra.
|
|
323
|
+
throw new Error('tmmWasm: deposition-spectra state allocation failed');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const cp = (p) => Float64Array.from(this._view(p, nOut));
|
|
327
|
+
const [Rs, Ts, Rp, Tp, Rrs, Rrp] = outPtrs.map(cp);
|
|
328
|
+
for (const p of [lamPtr, n0Ptr, nsPtr, mPtr, thPtr, ...outPtrs]) this.free(p);
|
|
329
|
+
return { Rs, Ts, Rp, Tp, Rrs, Rrp };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Persistent growing-layer evaluator: the completed stack's products for
|
|
334
|
+
* the whole wavelength grid are folded once and kept in kernel memory;
|
|
335
|
+
* each sample() then answers one thickness of the growing layer across
|
|
336
|
+
* the grid. This is the shape a broadband monitor scan needs (a spectrum
|
|
337
|
+
* per scan while the layers beneath stay fixed), where monitorCurve is
|
|
338
|
+
* the single-λ, many-thicknesses shape.
|
|
339
|
+
*
|
|
340
|
+
* The completed stack arrives OUTERMOST FIRST, layer-major like
|
|
341
|
+
* depositionSpectra's matNK; zero-thickness entries are skipped.
|
|
342
|
+
*
|
|
343
|
+
* The returned handle owns kernel memory: call free() when the layer is
|
|
344
|
+
* done. A dropped handle is reclaimed by a finalizer eventually, but
|
|
345
|
+
* deterministic free() is what keeps a long run's footprint flat.
|
|
346
|
+
*
|
|
347
|
+
* @param {number[]} lambdas
|
|
348
|
+
* @param {[number,number][]} n0List incident ñ per λ
|
|
349
|
+
* @param {[number,number][]} nsList substrate ñ per λ
|
|
350
|
+
* @param {[number,number][][]} layerNK [layer][λ] = ñ, outermost first
|
|
351
|
+
* @param {number[]} thick completed thicknesses (nm)
|
|
352
|
+
* @param {number} theta_deg
|
|
353
|
+
* @returns {{setTop, sample, free}} setTop(ngList) declares the growing
|
|
354
|
+
* layer's ñ per λ; sample(d, out?) returns {Rs,Ts,Rp,Tp,Rrs,Rrp}
|
|
355
|
+
* (each Float64Array(nLam), written into `out` when given, so a
|
|
356
|
+
* scan loop can reuse one set of buffers).
|
|
357
|
+
*/
|
|
358
|
+
growingEval(lambdas, n0List, nsList, layerNK, thick, theta_deg) {
|
|
359
|
+
if (!this.hasGrowingEval()) throw new Error('tmmWasm: growing evaluator kernel not in this build');
|
|
360
|
+
const nLam = lambdas.length;
|
|
361
|
+
const NB = thick.length;
|
|
362
|
+
|
|
363
|
+
const lamPtr = this._alloc(nLam);
|
|
364
|
+
const n0Ptr = this._alloc(2 * nLam);
|
|
365
|
+
const nsPtr = this._alloc(2 * nLam);
|
|
366
|
+
const mPtr = this._alloc(Math.max(1, 2 * NB * nLam));
|
|
367
|
+
const thPtr = this._alloc(Math.max(1, NB));
|
|
368
|
+
const lam = this._view(lamPtr, nLam);
|
|
369
|
+
const n0v = this._view(n0Ptr, 2 * nLam);
|
|
370
|
+
const nsv = this._view(nsPtr, 2 * nLam);
|
|
371
|
+
const mv = this._view(mPtr, Math.max(1, 2 * NB * nLam));
|
|
372
|
+
const thv = this._view(thPtr, Math.max(1, NB));
|
|
373
|
+
for (let i = 0; i < nLam; i++) {
|
|
374
|
+
lam[i] = lambdas[i];
|
|
375
|
+
n0v[2 * i] = n0List[i][0]; n0v[2 * i + 1] = n0List[i][1];
|
|
376
|
+
nsv[2 * i] = nsList[i][0]; nsv[2 * i + 1] = nsList[i][1];
|
|
377
|
+
}
|
|
378
|
+
for (let k = 0; k < NB; k++) {
|
|
379
|
+
thv[k] = thick[k];
|
|
380
|
+
const row = layerNK[k];
|
|
381
|
+
const base = k * nLam * 2;
|
|
382
|
+
for (let i = 0; i < nLam; i++) {
|
|
383
|
+
mv[base + 2 * i] = row[i][0];
|
|
384
|
+
mv[base + 2 * i + 1] = row[i][1];
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const handlePtr = this._growing_eval_create(lamPtr, nLam, theta_deg,
|
|
388
|
+
n0Ptr, nsPtr, mPtr, thPtr, NB);
|
|
389
|
+
for (const p of [lamPtr, n0Ptr, nsPtr, mPtr, thPtr]) this.free(p);
|
|
390
|
+
if (!handlePtr) throw new Error('tmmWasm: growing evaluator allocation failed');
|
|
391
|
+
|
|
392
|
+
const self = this;
|
|
393
|
+
const handle = {
|
|
394
|
+
ptr: handlePtr,
|
|
395
|
+
setTop(ngList) {
|
|
396
|
+
if (!this.ptr) throw new Error('tmmWasm: growing evaluator already freed');
|
|
397
|
+
const p = self._scratch(2 * nLam);
|
|
398
|
+
const buf = self._view(p, 2 * nLam);
|
|
399
|
+
for (let i = 0; i < nLam; i++) {
|
|
400
|
+
buf[2 * i] = ngList[i][0]; buf[2 * i + 1] = ngList[i][1];
|
|
401
|
+
}
|
|
402
|
+
self._growing_eval_set_top(this.ptr, p);
|
|
403
|
+
},
|
|
404
|
+
sample(d, out = null) {
|
|
405
|
+
if (!this.ptr) throw new Error('tmmWasm: growing evaluator already freed');
|
|
406
|
+
const p = self._scratch(6 * nLam);
|
|
407
|
+
const P = (block) => p + block * nLam * 8;
|
|
408
|
+
const ok = self._growing_eval_sample(this.ptr, d,
|
|
409
|
+
P(0), P(1), P(2), P(3), P(4), P(5));
|
|
410
|
+
// The kernel writes nothing without a declared growing layer;
|
|
411
|
+
// copying the arena anyway would hand back stale memory.
|
|
412
|
+
if (!ok) throw new Error('tmmWasm: growing evaluator sampled before setTop');
|
|
413
|
+
// Fresh view AFTER the call (memory may have grown → detach).
|
|
414
|
+
const buf = self._view(p, 6 * nLam);
|
|
415
|
+
if (!out) {
|
|
416
|
+
out = {
|
|
417
|
+
Rs: new Float64Array(nLam), Ts: new Float64Array(nLam),
|
|
418
|
+
Rp: new Float64Array(nLam), Tp: new Float64Array(nLam),
|
|
419
|
+
Rrs: new Float64Array(nLam), Rrp: new Float64Array(nLam),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
out.Rs.set(buf.subarray(0, nLam));
|
|
423
|
+
out.Ts.set(buf.subarray(nLam, 2 * nLam));
|
|
424
|
+
out.Rp.set(buf.subarray(2 * nLam, 3 * nLam));
|
|
425
|
+
out.Tp.set(buf.subarray(3 * nLam, 4 * nLam));
|
|
426
|
+
out.Rrs.set(buf.subarray(4 * nLam, 5 * nLam));
|
|
427
|
+
out.Rrp.set(buf.subarray(5 * nLam, 6 * nLam));
|
|
428
|
+
return out;
|
|
429
|
+
},
|
|
430
|
+
free() {
|
|
431
|
+
if (!this.ptr) return;
|
|
432
|
+
growingEvalFinalizer?.unregister(this);
|
|
433
|
+
self._growing_eval_free(this.ptr);
|
|
434
|
+
this.ptr = 0;
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
growingEvalFinalizer?.register(handle,
|
|
438
|
+
{ wasm: this, ptr: handlePtr }, handle);
|
|
439
|
+
return handle;
|
|
440
|
+
}
|
|
441
|
+
|
|
195
442
|
/**
|
|
196
443
|
* Analytic thickness Jacobian for one (λ, θ, pol) : mirrors
|
|
197
444
|
* tmmThicknessJacobian(). layers used AS-IS (index parity).
|
package/src/tmm_kernel.c
CHANGED
|
@@ -245,6 +245,336 @@ void tmm_spectrum(const double *lambdas, int nLam,
|
|
|
245
245
|
if (layers) free(layers);
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
/* ── Shared tails for the growing-stack kernels ──────────────────────────────
|
|
249
|
+
* Forward tail: reproduces the [B,C] → r,t → R,T block of tmm_core on an
|
|
250
|
+
* already-built characteristic matrix. A is not formed here; the incoherent
|
|
251
|
+
* slab combination that consumes these results computes it after the sum, and
|
|
252
|
+
* a semi-infinite caller forms 1−R−T itself.
|
|
253
|
+
*
|
|
254
|
+
* Reverse tail: reflectance of the same stack seen from the substrate side.
|
|
255
|
+
* The characteristic matrix of the reversed stack is the anti-transpose of the
|
|
256
|
+
* forward one (every layer matrix is invariant under anti-transposition), so
|
|
257
|
+
* the reverse pass costs no second product, and the common rescale factor
|
|
258
|
+
* cancels in the amplitude ratio. */
|
|
259
|
+
|
|
260
|
+
static inline void growingTailFwd(mat2 M, cx eta0, cx etaS, double logScale,
|
|
261
|
+
double *outR, double *outT) {
|
|
262
|
+
cx B = cadd(M.a, cmul(M.b, etaS));
|
|
263
|
+
cx C = cadd(M.c, cmul(M.d, etaS));
|
|
264
|
+
cx eta0B = cmul(eta0, B);
|
|
265
|
+
cx r = cdiv(csub(eta0B, C), cadd(eta0B, C));
|
|
266
|
+
cx t = cdiv(cmul(cmk(2.0, 0.0), eta0), cadd(eta0B, C));
|
|
267
|
+
double R = cabs2(r);
|
|
268
|
+
double T = etaS.re / eta0.re * cabs2(t) * exp(-2.0 * logScale);
|
|
269
|
+
if (T < 0.0) T = 0.0;
|
|
270
|
+
*outR = R; *outT = T;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
static inline double growingTailRev(mat2 M, cx eta0, cx etaS) {
|
|
274
|
+
cx B = cadd(M.d, cmul(M.b, eta0));
|
|
275
|
+
cx C = cadd(M.c, cmul(M.a, eta0));
|
|
276
|
+
cx etaSB = cmul(etaS, B);
|
|
277
|
+
cx r = cdiv(csub(etaSB, C), cadd(etaSB, C));
|
|
278
|
+
return cabs2(r);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* ── Exported: monitor curve of one growing layer ────────────────────────────
|
|
282
|
+
* The signal of one layer as it grows on a completed stack, at one wavelength,
|
|
283
|
+
* both polarizations: the incremental control algorithm of Tikhonravov &
|
|
284
|
+
* Trubetskov, Appl. Opt. 44, 6877 (2005). The completed stack's characteristic
|
|
285
|
+
* matrix is built once; each sample then costs one layer matrix, one 2×2
|
|
286
|
+
* multiply and the tails, so a sweep is linear in samples rather than in
|
|
287
|
+
* samples × stack depth.
|
|
288
|
+
*
|
|
289
|
+
* The growing layer faces the incident medium, so its matrix multiplies the
|
|
290
|
+
* completed product from the LEFT, exactly as createMonitorTmmEvaluator in the
|
|
291
|
+
* JS reference.
|
|
292
|
+
*
|
|
293
|
+
* base : NB triples [n_re, n_im, d_nm], the completed stack outermost first
|
|
294
|
+
* (zero-thickness entries skipped, as everywhere)
|
|
295
|
+
* ng : growing layer ñ at this wavelength
|
|
296
|
+
* dArr : nD sample thicknesses of the growing layer (nm); d ≤ 0 evaluates
|
|
297
|
+
* the bare completed stack
|
|
298
|
+
* Outputs (each nD):
|
|
299
|
+
* outRs/outTs, outRp/outTp : forward R and T of the coated surface
|
|
300
|
+
* outRrs/outRrp : its reflectance from the substrate side, for
|
|
301
|
+
* callers that model the witness as an incoherent
|
|
302
|
+
* slab with a bare back face */
|
|
303
|
+
|
|
304
|
+
TMM_EXPORT("tmm_monitor_curve")
|
|
305
|
+
void tmm_monitor_curve(double lambda_nm, double theta_deg,
|
|
306
|
+
double n0_re, double n0_im, double ns_re, double ns_im,
|
|
307
|
+
const double *base, int NB,
|
|
308
|
+
double ng_re, double ng_im,
|
|
309
|
+
const double *dArr, int nD,
|
|
310
|
+
double *outRs, double *outTs,
|
|
311
|
+
double *outRp, double *outTp,
|
|
312
|
+
double *outRrs, double *outRrp) {
|
|
313
|
+
cx n0 = cmk(n0_re, n0_im);
|
|
314
|
+
cx ns = cmk(ns_re, ns_im);
|
|
315
|
+
cx ng = cmk(ng_re, ng_im);
|
|
316
|
+
cx sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
|
|
317
|
+
cx cosTheta0 = csqrt_(csub(cmk(1.0, 0.0), cmul(sinTheta0, sinTheta0)));
|
|
318
|
+
|
|
319
|
+
for (int pol = 0; pol < 2; pol++) {
|
|
320
|
+
cx eta0 = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
321
|
+
cx cosThetaS = snellCosTheta(n0, sinTheta0, ns);
|
|
322
|
+
cx etaS = (pol == 0) ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
|
|
323
|
+
|
|
324
|
+
mat2 Mb;
|
|
325
|
+
Mb.a = cmk(1.0, 0.0); Mb.b = cmk(0.0, 0.0);
|
|
326
|
+
Mb.c = cmk(0.0, 0.0); Mb.d = cmk(1.0, 0.0);
|
|
327
|
+
double logScaleB = 0.0;
|
|
328
|
+
for (int i = 0; i < NB; i++) {
|
|
329
|
+
cx n = cmk(base[3 * i + 0], base[3 * i + 1]);
|
|
330
|
+
double d = base[3 * i + 2];
|
|
331
|
+
if (d <= 0.0) continue;
|
|
332
|
+
cx cosThetaJ = snellCosTheta(n0, sinTheta0, n);
|
|
333
|
+
Mb = matmul(Mb, layerMatrix(n, d, lambda_nm, cosThetaJ, pol));
|
|
334
|
+
logScaleB += rescaleMatrix(&Mb);
|
|
335
|
+
}
|
|
336
|
+
cx cosThetaG = snellCosTheta(n0, sinTheta0, ng);
|
|
337
|
+
|
|
338
|
+
double *oR = pol ? outRp : outRs;
|
|
339
|
+
double *oT = pol ? outTp : outTs;
|
|
340
|
+
double *oRr = pol ? outRrp : outRrs;
|
|
341
|
+
|
|
342
|
+
for (int k = 0; k < nD; k++) {
|
|
343
|
+
mat2 M = Mb;
|
|
344
|
+
double logScale = logScaleB;
|
|
345
|
+
double d = dArr[k];
|
|
346
|
+
if (d > 0.0) {
|
|
347
|
+
M = matmul(layerMatrix(ng, d, lambda_nm, cosThetaG, pol), Mb);
|
|
348
|
+
logScale += rescaleMatrix(&M);
|
|
349
|
+
}
|
|
350
|
+
growingTailFwd(M, eta0, etaS, logScale, &oR[k], &oT[k]);
|
|
351
|
+
oRr[k] = growingTailRev(M, eta0, etaS);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* ── Exported: spectra of a growing stack, one per deposited layer ───────────
|
|
357
|
+
* The front-surface passes of a deposition run in a single call: layers arrive
|
|
358
|
+
* in DEPOSITION order (first deposited, substrate-adjacent, first), each is
|
|
359
|
+
* folded into the running product from the left, since the newest layer faces
|
|
360
|
+
* the incident medium, and the spectrum after every fold is written out. The
|
|
361
|
+
* same incremental structure as tmm_monitor_curve over a wavelength grid, so
|
|
362
|
+
* all N step spectra together cost what the final one costs alone.
|
|
363
|
+
*
|
|
364
|
+
* Memory layout mirrors tmm_spectrum:
|
|
365
|
+
* matNK : N × nLam × 2, [layer][λ][re, im], deposition order
|
|
366
|
+
* thick : N thicknesses (nm); a step of zero thickness repeats the previous
|
|
367
|
+
* spectrum, matching the d ≤ 0 skip of the reference loop
|
|
368
|
+
* Outputs, each N × nLam, step-major ([step][λ]):
|
|
369
|
+
* outRs/outTs, outRp/outTp : forward R and T after each step
|
|
370
|
+
* outRrs/outRrp : reflectance from the substrate side after each
|
|
371
|
+
* step, for the incoherent slab combination */
|
|
372
|
+
|
|
373
|
+
/* Returns 1 on success; 0, with the outputs untouched, when the working
|
|
374
|
+
* state could not be allocated. */
|
|
375
|
+
TMM_EXPORT("tmm_deposition_spectra")
|
|
376
|
+
int tmm_deposition_spectra(const double *lambdas, int nLam,
|
|
377
|
+
const double *n0arr, const double *nsarr,
|
|
378
|
+
const double *matNK, const double *thick, int N,
|
|
379
|
+
double theta_deg,
|
|
380
|
+
double *outRs, double *outTs,
|
|
381
|
+
double *outRp, double *outTp,
|
|
382
|
+
double *outRrs, double *outRrp) {
|
|
383
|
+
/* Running product and admittances per (λ, pol); pol-major blocks. */
|
|
384
|
+
size_t cells = (size_t)nLam * 2;
|
|
385
|
+
mat2 *M = (mat2 *)malloc(sizeof(mat2) * cells);
|
|
386
|
+
double *logScale = (double *)malloc(sizeof(double) * cells);
|
|
387
|
+
cx *eta0v = (cx *)malloc(sizeof(cx) * cells);
|
|
388
|
+
cx *etaSv = (cx *)malloc(sizeof(cx) * cells);
|
|
389
|
+
if (!M || !logScale || !eta0v || !etaSv) {
|
|
390
|
+
free(M); free(logScale); free(eta0v); free(etaSv);
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
cx sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
|
|
395
|
+
cx cosTheta0 = csqrt_(csub(cmk(1.0, 0.0), cmul(sinTheta0, sinTheta0)));
|
|
396
|
+
|
|
397
|
+
for (int li = 0; li < nLam; li++) {
|
|
398
|
+
cx n0 = cmk(n0arr[2 * li + 0], n0arr[2 * li + 1]);
|
|
399
|
+
cx ns = cmk(nsarr[2 * li + 0], nsarr[2 * li + 1]);
|
|
400
|
+
for (int pol = 0; pol < 2; pol++) {
|
|
401
|
+
size_t at = (size_t)pol * nLam + li;
|
|
402
|
+
eta0v[at] = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
403
|
+
cx cosThetaS = snellCosTheta(n0, sinTheta0, ns);
|
|
404
|
+
etaSv[at] = (pol == 0) ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
|
|
405
|
+
M[at].a = cmk(1.0, 0.0); M[at].b = cmk(0.0, 0.0);
|
|
406
|
+
M[at].c = cmk(0.0, 0.0); M[at].d = cmk(1.0, 0.0);
|
|
407
|
+
logScale[at] = 0.0;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
for (int k = 0; k < N; k++) {
|
|
412
|
+
double d = thick[k];
|
|
413
|
+
for (int li = 0; li < nLam; li++) {
|
|
414
|
+
double lam = lambdas[li];
|
|
415
|
+
cx n0 = cmk(n0arr[2 * li + 0], n0arr[2 * li + 1]);
|
|
416
|
+
cx n = cmk(matNK[((size_t)k * nLam + li) * 2 + 0],
|
|
417
|
+
matNK[((size_t)k * nLam + li) * 2 + 1]);
|
|
418
|
+
for (int pol = 0; pol < 2; pol++) {
|
|
419
|
+
size_t at = (size_t)pol * nLam + li;
|
|
420
|
+
if (d > 0.0) {
|
|
421
|
+
cx cosThetaJ = snellCosTheta(n0, sinTheta0, n);
|
|
422
|
+
M[at] = matmul(layerMatrix(n, d, lam, cosThetaJ, pol), M[at]);
|
|
423
|
+
logScale[at] += rescaleMatrix(&M[at]);
|
|
424
|
+
}
|
|
425
|
+
size_t out = (size_t)k * nLam + li;
|
|
426
|
+
double *oR = pol ? outRp : outRs;
|
|
427
|
+
double *oT = pol ? outTp : outTs;
|
|
428
|
+
double *oRr = pol ? outRrp : outRrs;
|
|
429
|
+
growingTailFwd(M[at], eta0v[at], etaSv[at], logScale[at],
|
|
430
|
+
&oR[out], &oT[out]);
|
|
431
|
+
oRr[out] = growingTailRev(M[at], eta0v[at], etaSv[at]);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
free(M); free(logScale); free(eta0v); free(etaSv);
|
|
437
|
+
return 1;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/* ── Exported: persistent growing-layer evaluator over a wavelength grid ─────
|
|
441
|
+
* The stateful counterpart of tmm_monitor_curve, batched the other way: one
|
|
442
|
+
* thickness of the growing layer per call, the WHOLE wavelength grid at once.
|
|
443
|
+
* This is the shape a broadband monitor scan needs, where every scan reads a
|
|
444
|
+
* full spectrum of the growing stack and the completed layers beneath do not
|
|
445
|
+
* change until the layer is cut.
|
|
446
|
+
*
|
|
447
|
+
* Lifecycle: create() folds the completed stack's characteristic matrices for
|
|
448
|
+
* every (λ, pol) once and keeps them; set_top() declares the growing layer's
|
|
449
|
+
* ñ(λ); sample() then answers one thickness across the grid, costing one layer
|
|
450
|
+
* matrix, one 2×2 multiply and the tails per (λ, pol). free() releases the
|
|
451
|
+
* state. Handles are opaque pointers; the caller owns their lifetime.
|
|
452
|
+
*
|
|
453
|
+
* Layout matches tmm_deposition_spectra: matNK is NB × nLam × 2 layer-major
|
|
454
|
+
* with the completed stack OUTERMOST FIRST (the fold is M = M · M_k, exactly
|
|
455
|
+
* the JS reference createMonitorTmmEvaluator); zero-thickness entries are
|
|
456
|
+
* skipped. Outputs of sample(), each nLam: forward R and T plus the
|
|
457
|
+
* substrate-side reflectance, for the incoherent slab combination. */
|
|
458
|
+
|
|
459
|
+
typedef struct {
|
|
460
|
+
int nLam;
|
|
461
|
+
cx sinTheta0;
|
|
462
|
+
int topSet;
|
|
463
|
+
double *lam; /* nLam */
|
|
464
|
+
cx *n0; /* nLam */
|
|
465
|
+
cx *ng; /* nLam, set_top */
|
|
466
|
+
cx *cosThetaG; /* nLam, set_top (polarization-independent) */
|
|
467
|
+
cx *eta0; /* 2·nLam, pol-major */
|
|
468
|
+
cx *etaS; /* 2·nLam */
|
|
469
|
+
mat2 *Mb; /* 2·nLam completed-stack product */
|
|
470
|
+
double *logScaleB; /* 2·nLam */
|
|
471
|
+
} growing_eval;
|
|
472
|
+
|
|
473
|
+
TMM_EXPORT("tmm_growing_eval_free")
|
|
474
|
+
void tmm_growing_eval_free(growing_eval *h) {
|
|
475
|
+
if (!h) return;
|
|
476
|
+
free(h->lam); free(h->n0); free(h->ng); free(h->cosThetaG);
|
|
477
|
+
free(h->eta0); free(h->etaS); free(h->Mb); free(h->logScaleB);
|
|
478
|
+
free(h);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
TMM_EXPORT("tmm_growing_eval_create")
|
|
482
|
+
growing_eval *tmm_growing_eval_create(const double *lambdas, int nLam,
|
|
483
|
+
double theta_deg,
|
|
484
|
+
const double *n0arr, const double *nsarr,
|
|
485
|
+
const double *matNK, const double *thick,
|
|
486
|
+
int NB) {
|
|
487
|
+
growing_eval *h = (growing_eval *)malloc(sizeof(growing_eval));
|
|
488
|
+
if (!h) return 0;
|
|
489
|
+
h->nLam = nLam;
|
|
490
|
+
h->topSet = 0;
|
|
491
|
+
h->lam = (double *)malloc(sizeof(double) * nLam);
|
|
492
|
+
h->n0 = (cx *)malloc(sizeof(cx) * nLam);
|
|
493
|
+
h->ng = (cx *)malloc(sizeof(cx) * nLam);
|
|
494
|
+
h->cosThetaG = (cx *)malloc(sizeof(cx) * nLam);
|
|
495
|
+
h->eta0 = (cx *)malloc(sizeof(cx) * 2 * nLam);
|
|
496
|
+
h->etaS = (cx *)malloc(sizeof(cx) * 2 * nLam);
|
|
497
|
+
h->Mb = (mat2 *)malloc(sizeof(mat2) * 2 * nLam);
|
|
498
|
+
h->logScaleB = (double *)malloc(sizeof(double) * 2 * nLam);
|
|
499
|
+
if (!h->lam || !h->n0 || !h->ng || !h->cosThetaG
|
|
500
|
+
|| !h->eta0 || !h->etaS || !h->Mb || !h->logScaleB) {
|
|
501
|
+
tmm_growing_eval_free(h);
|
|
502
|
+
return 0;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
h->sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
|
|
506
|
+
cx cosTheta0 = csqrt_(csub(cmk(1.0, 0.0), cmul(h->sinTheta0, h->sinTheta0)));
|
|
507
|
+
|
|
508
|
+
for (int li = 0; li < nLam; li++) {
|
|
509
|
+
h->lam[li] = lambdas[li];
|
|
510
|
+
cx n0 = cmk(n0arr[2 * li + 0], n0arr[2 * li + 1]);
|
|
511
|
+
cx ns = cmk(nsarr[2 * li + 0], nsarr[2 * li + 1]);
|
|
512
|
+
h->n0[li] = n0;
|
|
513
|
+
for (int pol = 0; pol < 2; pol++) {
|
|
514
|
+
size_t at = (size_t)pol * nLam + li;
|
|
515
|
+
h->eta0[at] = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
516
|
+
cx cosThetaS = snellCosTheta(n0, h->sinTheta0, ns);
|
|
517
|
+
h->etaS[at] = (pol == 0) ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
|
|
518
|
+
mat2 M;
|
|
519
|
+
M.a = cmk(1.0, 0.0); M.b = cmk(0.0, 0.0);
|
|
520
|
+
M.c = cmk(0.0, 0.0); M.d = cmk(1.0, 0.0);
|
|
521
|
+
double logScale = 0.0;
|
|
522
|
+
for (int k = 0; k < NB; k++) {
|
|
523
|
+
double d = thick[k];
|
|
524
|
+
if (d <= 0.0) continue;
|
|
525
|
+
cx n = cmk(matNK[((size_t)k * nLam + li) * 2 + 0],
|
|
526
|
+
matNK[((size_t)k * nLam + li) * 2 + 1]);
|
|
527
|
+
cx cosThetaJ = snellCosTheta(n0, h->sinTheta0, n);
|
|
528
|
+
M = matmul(M, layerMatrix(n, d, lambdas[li], cosThetaJ, pol));
|
|
529
|
+
logScale += rescaleMatrix(&M);
|
|
530
|
+
}
|
|
531
|
+
h->Mb[at] = M;
|
|
532
|
+
h->logScaleB[at] = logScale;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return h;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
TMM_EXPORT("tmm_growing_eval_set_top")
|
|
539
|
+
void tmm_growing_eval_set_top(growing_eval *h, const double *ngNK) {
|
|
540
|
+
if (!h) return;
|
|
541
|
+
for (int li = 0; li < h->nLam; li++) {
|
|
542
|
+
cx ng = cmk(ngNK[2 * li + 0], ngNK[2 * li + 1]);
|
|
543
|
+
h->ng[li] = ng;
|
|
544
|
+
h->cosThetaG[li] = snellCosTheta(h->n0[li], h->sinTheta0, ng);
|
|
545
|
+
}
|
|
546
|
+
h->topSet = 1;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/* Returns 1 on success; 0, with the outputs untouched, for a null handle or
|
|
550
|
+
* a d > 0 sample before set_top declared the growing layer. The status is
|
|
551
|
+
* what keeps an unwritten buffer from being read back as data. */
|
|
552
|
+
TMM_EXPORT("tmm_growing_eval_sample")
|
|
553
|
+
int tmm_growing_eval_sample(growing_eval *h, double d,
|
|
554
|
+
double *outRs, double *outTs,
|
|
555
|
+
double *outRp, double *outTp,
|
|
556
|
+
double *outRrs, double *outRrp) {
|
|
557
|
+
if (!h || (d > 0.0 && !h->topSet)) return 0;
|
|
558
|
+
for (int li = 0; li < h->nLam; li++) {
|
|
559
|
+
for (int pol = 0; pol < 2; pol++) {
|
|
560
|
+
size_t at = (size_t)pol * h->nLam + li;
|
|
561
|
+
mat2 M = h->Mb[at];
|
|
562
|
+
double logScale = h->logScaleB[at];
|
|
563
|
+
if (d > 0.0) {
|
|
564
|
+
M = matmul(layerMatrix(h->ng[li], d, h->lam[li], h->cosThetaG[li], pol),
|
|
565
|
+
h->Mb[at]);
|
|
566
|
+
logScale += rescaleMatrix(&M);
|
|
567
|
+
}
|
|
568
|
+
double *oR = pol ? outRp : outRs;
|
|
569
|
+
double *oT = pol ? outTp : outTs;
|
|
570
|
+
double *oRr = pol ? outRrp : outRrs;
|
|
571
|
+
growingTailFwd(M, h->eta0[at], h->etaS[at], logScale, &oR[li], &oT[li]);
|
|
572
|
+
oRr[li] = growingTailRev(M, h->eta0[at], h->etaS[at]);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return 1;
|
|
576
|
+
}
|
|
577
|
+
|
|
248
578
|
/* ── Exported: analytic thickness Jacobian for one (λ, θ, pol) ────────────────
|
|
249
579
|
* Faithful port of tmmThicknessJacobian() in thinFilmMath.js. Returns the exact
|
|
250
580
|
* analytic dR/dd_k, dT/dd_k, dA/dd_k for every layer at one sample : the DLS
|
package/src/tmm_kernel.wasm
CHANGED
|
Binary file
|