tmmcore 0.2.0 → 0.3.1
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/index.js +1 -1
- package/src/phase.js +20 -9
- package/src/taylorJet.js +5 -0
- package/src/tmm.js +33 -8
- package/src/tmmWasm.js +247 -0
- package/src/tmm_kernel.c +374 -14
- 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.1",
|
|
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 && node tests/absorbing_incident.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/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export {
|
|
|
29
29
|
tmmNeedleScan,
|
|
30
30
|
// Low-level primitives, for building variants on the same conventions
|
|
31
31
|
cadd, csub, cmul, cdiv, cabs2, cconj, csqrt, ccos, csin, creal, cimag,
|
|
32
|
-
matmul, rescaleMatrix, snellCosTheta, layerMatrix, cmatvec,
|
|
32
|
+
matmul, rescaleMatrix, snellCosTheta, incidentCosTheta, layerMatrix, cmatvec,
|
|
33
33
|
} from './tmm.js';
|
|
34
34
|
|
|
35
35
|
export {
|
package/src/phase.js
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
jetDerivatives,
|
|
47
47
|
jetDivide,
|
|
48
48
|
jetMultiply,
|
|
49
|
+
jetRealPart,
|
|
49
50
|
jetScale,
|
|
50
51
|
jetSinCos,
|
|
51
52
|
jetSqrt,
|
|
@@ -124,11 +125,25 @@ function rescaleMatrix(matrix, threshold) {
|
|
|
124
125
|
return Math.log(scale);
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
// The transverse invariant is Re(n0) sinθ0, as in tmm.js: an absorbing incident
|
|
129
|
+
// medium carries a wave whose amplitude falls along the normal only.
|
|
127
130
|
function snellCosine(incidentIndex, incidentSine, layerIndex) {
|
|
128
|
-
const layerSine = jetDivide(jetMultiply(incidentIndex, incidentSine), layerIndex);
|
|
131
|
+
const layerSine = jetDivide(jetMultiply(jetRealPart(incidentIndex), incidentSine), layerIndex);
|
|
129
132
|
return jetSqrt(jetSubtract(jetConstant(1), jetMultiply(layerSine, layerSine)));
|
|
130
133
|
}
|
|
131
134
|
|
|
135
|
+
// cosθ0 of the incident medium: the plain cosine while the index is real at
|
|
136
|
+
// every order, otherwise from the same real invariant as the layers, so the
|
|
137
|
+
// incident admittance is sqrt(N0² − n0² sin²θ0) and matches tmm.js.
|
|
138
|
+
function incidentCosine(incidentIndex, incidentSine, incidentSineJet, thetaDeg) {
|
|
139
|
+
if (incidentIndex.some(coefficient => coefficient[1] !== 0)) {
|
|
140
|
+
return snellCosine(incidentIndex, incidentSine, incidentIndex);
|
|
141
|
+
}
|
|
142
|
+
return incidentSineJet
|
|
143
|
+
? jetSqrt(jetSubtract(jetConstant(1), jetMultiply(incidentSine, incidentSine)))
|
|
144
|
+
: jetConstant(Math.cos(thetaDeg * Math.PI / 180));
|
|
145
|
+
}
|
|
146
|
+
|
|
132
147
|
function admittance(index, cosine, polarization) {
|
|
133
148
|
return polarization === 's'
|
|
134
149
|
? jetMultiply(index, cosine)
|
|
@@ -249,10 +264,8 @@ export function tmmCoefficientJets({
|
|
|
249
264
|
layers,
|
|
250
265
|
}) {
|
|
251
266
|
const incidentSine = incidentSineJet || jetConstant(Math.sin(thetaDeg * Math.PI / 180));
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
: jetConstant(Math.cos(thetaDeg * Math.PI / 180));
|
|
255
|
-
const incidentEta = admittance(incidentIndexJet, incidentCosine, polarization);
|
|
267
|
+
const incidentEta = admittance(incidentIndexJet,
|
|
268
|
+
incidentCosine(incidentIndexJet, incidentSine, incidentSineJet, thetaDeg), polarization);
|
|
256
269
|
const substrateCosine = snellCosine(incidentIndexJet, incidentSine, substrateIndexJet);
|
|
257
270
|
const substrateEta = admittance(substrateIndexJet, substrateCosine, polarization);
|
|
258
271
|
|
|
@@ -301,10 +314,8 @@ export function tmmCoefficientThicknessJets(options) {
|
|
|
301
314
|
layers,
|
|
302
315
|
} = options;
|
|
303
316
|
const incidentSine = incidentSineJet || jetConstant(Math.sin(thetaDeg * Math.PI / 180));
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
: jetConstant(Math.cos(thetaDeg * Math.PI / 180));
|
|
307
|
-
const incidentEta = admittance(incidentIndexJet, incidentCosine, polarization);
|
|
317
|
+
const incidentEta = admittance(incidentIndexJet,
|
|
318
|
+
incidentCosine(incidentIndexJet, incidentSine, incidentSineJet, thetaDeg), polarization);
|
|
308
319
|
const substrateCosine = snellCosine(incidentIndexJet, incidentSine, substrateIndexJet);
|
|
309
320
|
const substrateEta = admittance(substrateIndexJet, substrateCosine, polarization);
|
|
310
321
|
const layerData = layers.map((layer) => {
|
package/src/taylorJet.js
CHANGED
|
@@ -233,6 +233,11 @@ export function jetWithImaginaryPart(realJet, imaginaryJet) {
|
|
|
233
233
|
return realJet.map((coefficient, index) => [coefficient[0], imaginaryJet[index][0]]);
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
/** The real part of a complex jet, order by order. */
|
|
237
|
+
export function jetRealPart(jet) {
|
|
238
|
+
return jet.map(coefficient => [coefficient[0], 0]);
|
|
239
|
+
}
|
|
240
|
+
|
|
236
241
|
/** Floor the value at `minimum`, flattening the jet to a constant when it bites. */
|
|
237
242
|
export function jetClampRealMinimum(jet, minimum) {
|
|
238
243
|
return jet[0][0] >= minimum ? jet : jetConstant(minimum);
|
package/src/tmm.js
CHANGED
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
* it to float64 round-off (see tests/).
|
|
21
21
|
*
|
|
22
22
|
* References:
|
|
23
|
-
* • Macleod, Thin-Film Optical Filters 5th ed., §2.4, Eqs. 2.111, 2.123–2.125
|
|
23
|
+
* • Macleod, Thin-Film Optical Filters 5th ed., §2.4, Eqs. 2.111, 2.123–2.125;
|
|
24
|
+
* Eq. 2.83 (transmittance out of an absorbing incident medium);
|
|
25
|
+
* §10.2, Eqs. 10.11–10.13 (tilted admittances from the real invariant n0 sinθ0)
|
|
24
26
|
* • Sullivan & Dobrowolski, Appl. Opt. 35, 5484 (1996), Eqs. (3)–(6)
|
|
25
27
|
* • Tikhonravov, Trubetskov & DeBell, Appl. Opt. 35, 5493 (1996)
|
|
26
28
|
*/
|
|
@@ -90,14 +92,37 @@ function rescaleMatrix(M) {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
// ── Snell's law ───────────────────────────────────────────────────────────────
|
|
95
|
+
//
|
|
96
|
+
// Every wave in the stack shares one transverse wavevector, n0 sinθ0 with n0
|
|
97
|
+
// the REAL part of the incident index (Macleod 5th ed., §10.2, Eqs. 10.11 to
|
|
98
|
+
// 10.13). An absorbing incident medium then carries an inhomogeneous wave whose
|
|
99
|
+
// amplitude falls along the normal only, and each medium's cosθ follows from
|
|
100
|
+
// that one real invariant. Putting the complex index into the invariant would
|
|
101
|
+
// make the amplitude vary along the interface, so energy would flow sideways
|
|
102
|
+
// inside lossless layers and R + T would be wrong by a term linear in the
|
|
103
|
+
// incident k at oblique incidence.
|
|
93
104
|
|
|
94
105
|
function snellCosTheta(n0, sinTheta0, nj) {
|
|
95
|
-
// sinThetaJ = n0 * sinTheta0 / nj (complex)
|
|
96
|
-
const sinThetaJ = cdiv(cmul(n0, sinTheta0), nj);
|
|
106
|
+
// sinThetaJ = Re(n0) * sinTheta0 / nj (complex)
|
|
107
|
+
const sinThetaJ = cdiv(cmul([n0[0], 0], sinTheta0), nj);
|
|
97
108
|
// cosTheta = sqrt(1 - sin²θ)
|
|
98
109
|
return csqrt(csub([1, 0], cmul(sinThetaJ, sinThetaJ)));
|
|
99
110
|
}
|
|
100
111
|
|
|
112
|
+
// cosθ0 of the incident medium. A transparent medium keeps the plain
|
|
113
|
+
// sqrt(1 − sin²θ0). An absorbing one takes its cosine from the same real
|
|
114
|
+
// invariant as the layers, so its tilted admittance is sqrt(N0² − n0² sin²θ0)
|
|
115
|
+
// and the incident wave belongs to the same boundary problem as the rest of
|
|
116
|
+
// the stack. R + T then departs from 1 for lossless layers only by the
|
|
117
|
+
// interference of the incident and reflected waves in the absorbing medium,
|
|
118
|
+
// the term Macleod's Eq. 2.83 carries, which is second order in k0 for a
|
|
119
|
+
// bare interface.
|
|
120
|
+
function incidentCosTheta(n0, sinTheta0) {
|
|
121
|
+
return n0[1] === 0
|
|
122
|
+
? csqrt(csub([1, 0], cmul(sinTheta0, sinTheta0)))
|
|
123
|
+
: snellCosTheta(n0, sinTheta0, n0);
|
|
124
|
+
}
|
|
125
|
+
|
|
101
126
|
// ── Layer characteristic matrix ───────────────────────────────────────────────
|
|
102
127
|
|
|
103
128
|
function layerMatrix(nj, dj_nm, lambda_nm, cosTheta_j, pol) {
|
|
@@ -150,7 +175,7 @@ function layerMatrix(nj, dj_nm, lambda_nm, cosTheta_j, pol) {
|
|
|
150
175
|
*/
|
|
151
176
|
export function tmm(lambda_nm, theta_deg, pol, n0, ns, layers) {
|
|
152
177
|
const sinTheta0 = [Math.sin(theta_deg * Math.PI / 180), 0];
|
|
153
|
-
const cosTheta0 =
|
|
178
|
+
const cosTheta0 = incidentCosTheta(n0, sinTheta0);
|
|
154
179
|
|
|
155
180
|
// Admittance of incident medium
|
|
156
181
|
const eta0 = pol === 's'
|
|
@@ -241,7 +266,7 @@ function cmatvec(M, v) {
|
|
|
241
266
|
export function tmmNeedleScan(lambda_nm, theta_deg, pol, n0, ns, layers,
|
|
242
267
|
candidateNs, intraFracs = []) {
|
|
243
268
|
const sinTheta0 = [Math.sin(theta_deg * Math.PI / 180), 0];
|
|
244
|
-
const cosTheta0 =
|
|
269
|
+
const cosTheta0 = incidentCosTheta(n0, sinTheta0);
|
|
245
270
|
const eta0 = pol === 's' ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
246
271
|
const cosThetaS = snellCosTheta(n0, sinTheta0, ns);
|
|
247
272
|
const etaS = pol === 's' ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
|
|
@@ -368,7 +393,7 @@ export function tmmNeedleScan(lambda_nm, theta_deg, pol, n0, ns, layers,
|
|
|
368
393
|
// `metrics()` in tmmNeedleScan.
|
|
369
394
|
export function tmmThicknessJacobian(lambda_nm, theta_deg, pol, n0, ns, layers) {
|
|
370
395
|
const sinTheta0 = [Math.sin(theta_deg * Math.PI / 180), 0];
|
|
371
|
-
const cosTheta0 =
|
|
396
|
+
const cosTheta0 = incidentCosTheta(n0, sinTheta0);
|
|
372
397
|
const eta0 = pol === 's' ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
373
398
|
const cosThetaS = snellCosTheta(n0, sinTheta0, ns);
|
|
374
399
|
const etaS = pol === 's' ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
|
|
@@ -464,7 +489,7 @@ export function tmmThicknessJacobian(lambda_nm, theta_deg, pol, n0, ns, layers)
|
|
|
464
489
|
// (tests/hessian_fd_validation.mjs).*
|
|
465
490
|
export function tmmThicknessHessian(lambda_nm, theta_deg, pol, n0, ns, layers) {
|
|
466
491
|
const sinTheta0 = [Math.sin(theta_deg * Math.PI / 180), 0];
|
|
467
|
-
const cosTheta0 =
|
|
492
|
+
const cosTheta0 = incidentCosTheta(n0, sinTheta0);
|
|
468
493
|
const eta0 = pol === 's' ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
469
494
|
const cosThetaS = snellCosTheta(n0, sinTheta0, ns);
|
|
470
495
|
const etaS = pol === 's' ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
|
|
@@ -598,5 +623,5 @@ export function tmmThicknessHessian(lambda_nm, theta_deg, pol, n0, ns, layers) {
|
|
|
598
623
|
|
|
599
624
|
export {
|
|
600
625
|
cadd, csub, cmul, cdiv, cabs2, cconj, csqrt, ccos, csin, creal, cimag,
|
|
601
|
-
matmul, rescaleMatrix, snellCosTheta, layerMatrix, cmatvec
|
|
626
|
+
matmul, rescaleMatrix, snellCosTheta, incidentCosTheta, layerMatrix, cmatvec
|
|
602
627
|
};
|
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
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* Builds with any C99 compiler; no dependencies beyond libm.
|
|
24
24
|
*
|
|
25
25
|
* References:
|
|
26
|
-
* • Macleod, Thin-Film Optical Filters 5th ed., §2.4, Eqs. 2.111, 2.123–2.125
|
|
26
|
+
* • Macleod, Thin-Film Optical Filters 5th ed., §2.4, Eqs. 2.111, 2.123–2.125;
|
|
27
|
+
* Eq. 2.83; §10.2, Eqs. 10.11–10.13 (tilted admittances, real invariant)
|
|
27
28
|
* • Sullivan & Dobrowolski, Appl. Opt. 35, 5484 (1996), Eqs. (3)–(6)
|
|
28
29
|
* • Tikhonravov, Trubetskov & DeBell, Appl. Opt. 35, 5493 (1996)
|
|
29
30
|
* • Birge & Kärtner, Appl. Opt. 45, 1478 (2006) [phase dispersion]
|
|
@@ -110,13 +111,24 @@ static inline vec2 cmatvec(mat2 M, vec2 v) {
|
|
|
110
111
|
return o;
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
/* ── Snell's law: cosθ_j from incident (n0, sinθ0) into medium nj ───────────
|
|
114
|
+
/* ── Snell's law: cosθ_j from incident (n0, sinθ0) into medium nj ───────────
|
|
115
|
+
* The transverse invariant is Re(n0) sinθ0 (Macleod 5th ed., §10.2, Eqs.
|
|
116
|
+
* 10.11–10.13): an absorbing incident medium carries a wave whose amplitude
|
|
117
|
+
* falls along the normal only, and every medium's cosθ follows from that one
|
|
118
|
+
* real invariant. Mirrors snellCosTheta / incidentCosTheta in tmm.js. */
|
|
114
119
|
|
|
115
120
|
static inline cx snellCosTheta(cx n0, cx sinTheta0, cx nj) {
|
|
116
|
-
cx sinThetaJ = cdiv(cmul(n0, sinTheta0), nj);
|
|
121
|
+
cx sinThetaJ = cdiv(cmul(cmk(n0.re, 0.0), sinTheta0), nj);
|
|
117
122
|
return csqrt_(csub(cmk(1.0, 0.0), cmul(sinThetaJ, sinThetaJ)));
|
|
118
123
|
}
|
|
119
124
|
|
|
125
|
+
/* cosθ0 of the incident medium: the plain cosine for a transparent medium,
|
|
126
|
+
* otherwise from the same real invariant as the layers. */
|
|
127
|
+
static inline cx incidentCosTheta(cx n0, cx sinTheta0) {
|
|
128
|
+
if (n0.im == 0.0) return csqrt_(csub(cmk(1.0, 0.0), cmul(sinTheta0, sinTheta0)));
|
|
129
|
+
return snellCosTheta(n0, sinTheta0, n0);
|
|
130
|
+
}
|
|
131
|
+
|
|
120
132
|
/* ── Layer characteristic matrix (pol: 0 = s, 1 = p) ─────────────────────── */
|
|
121
133
|
|
|
122
134
|
static inline mat2 layerMatrix(cx nj, double dj_nm, double lambda_nm, cx cosTheta_j, int pol) {
|
|
@@ -149,7 +161,7 @@ static void tmm_core(double lambda_nm, double theta_deg, int pol,
|
|
|
149
161
|
cx n0, cx ns, const double *layers, int N,
|
|
150
162
|
double *outR, double *outT, double *outA) {
|
|
151
163
|
cx sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
|
|
152
|
-
cx cosTheta0 =
|
|
164
|
+
cx cosTheta0 = incidentCosTheta(n0, sinTheta0);
|
|
153
165
|
|
|
154
166
|
cx eta0 = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
155
167
|
|
|
@@ -245,6 +257,336 @@ void tmm_spectrum(const double *lambdas, int nLam,
|
|
|
245
257
|
if (layers) free(layers);
|
|
246
258
|
}
|
|
247
259
|
|
|
260
|
+
/* ── Shared tails for the growing-stack kernels ──────────────────────────────
|
|
261
|
+
* Forward tail: reproduces the [B,C] → r,t → R,T block of tmm_core on an
|
|
262
|
+
* already-built characteristic matrix. A is not formed here; the incoherent
|
|
263
|
+
* slab combination that consumes these results computes it after the sum, and
|
|
264
|
+
* a semi-infinite caller forms 1−R−T itself.
|
|
265
|
+
*
|
|
266
|
+
* Reverse tail: reflectance of the same stack seen from the substrate side.
|
|
267
|
+
* The characteristic matrix of the reversed stack is the anti-transpose of the
|
|
268
|
+
* forward one (every layer matrix is invariant under anti-transposition), so
|
|
269
|
+
* the reverse pass costs no second product, and the common rescale factor
|
|
270
|
+
* cancels in the amplitude ratio. */
|
|
271
|
+
|
|
272
|
+
static inline void growingTailFwd(mat2 M, cx eta0, cx etaS, double logScale,
|
|
273
|
+
double *outR, double *outT) {
|
|
274
|
+
cx B = cadd(M.a, cmul(M.b, etaS));
|
|
275
|
+
cx C = cadd(M.c, cmul(M.d, etaS));
|
|
276
|
+
cx eta0B = cmul(eta0, B);
|
|
277
|
+
cx r = cdiv(csub(eta0B, C), cadd(eta0B, C));
|
|
278
|
+
cx t = cdiv(cmul(cmk(2.0, 0.0), eta0), cadd(eta0B, C));
|
|
279
|
+
double R = cabs2(r);
|
|
280
|
+
double T = etaS.re / eta0.re * cabs2(t) * exp(-2.0 * logScale);
|
|
281
|
+
if (T < 0.0) T = 0.0;
|
|
282
|
+
*outR = R; *outT = T;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
static inline double growingTailRev(mat2 M, cx eta0, cx etaS) {
|
|
286
|
+
cx B = cadd(M.d, cmul(M.b, eta0));
|
|
287
|
+
cx C = cadd(M.c, cmul(M.a, eta0));
|
|
288
|
+
cx etaSB = cmul(etaS, B);
|
|
289
|
+
cx r = cdiv(csub(etaSB, C), cadd(etaSB, C));
|
|
290
|
+
return cabs2(r);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* ── Exported: monitor curve of one growing layer ────────────────────────────
|
|
294
|
+
* The signal of one layer as it grows on a completed stack, at one wavelength,
|
|
295
|
+
* both polarizations: the incremental control algorithm of Tikhonravov &
|
|
296
|
+
* Trubetskov, Appl. Opt. 44, 6877 (2005). The completed stack's characteristic
|
|
297
|
+
* matrix is built once; each sample then costs one layer matrix, one 2×2
|
|
298
|
+
* multiply and the tails, so a sweep is linear in samples rather than in
|
|
299
|
+
* samples × stack depth.
|
|
300
|
+
*
|
|
301
|
+
* The growing layer faces the incident medium, so its matrix multiplies the
|
|
302
|
+
* completed product from the LEFT, exactly as createMonitorTmmEvaluator in the
|
|
303
|
+
* JS reference.
|
|
304
|
+
*
|
|
305
|
+
* base : NB triples [n_re, n_im, d_nm], the completed stack outermost first
|
|
306
|
+
* (zero-thickness entries skipped, as everywhere)
|
|
307
|
+
* ng : growing layer ñ at this wavelength
|
|
308
|
+
* dArr : nD sample thicknesses of the growing layer (nm); d ≤ 0 evaluates
|
|
309
|
+
* the bare completed stack
|
|
310
|
+
* Outputs (each nD):
|
|
311
|
+
* outRs/outTs, outRp/outTp : forward R and T of the coated surface
|
|
312
|
+
* outRrs/outRrp : its reflectance from the substrate side, for
|
|
313
|
+
* callers that model the witness as an incoherent
|
|
314
|
+
* slab with a bare back face */
|
|
315
|
+
|
|
316
|
+
TMM_EXPORT("tmm_monitor_curve")
|
|
317
|
+
void tmm_monitor_curve(double lambda_nm, double theta_deg,
|
|
318
|
+
double n0_re, double n0_im, double ns_re, double ns_im,
|
|
319
|
+
const double *base, int NB,
|
|
320
|
+
double ng_re, double ng_im,
|
|
321
|
+
const double *dArr, int nD,
|
|
322
|
+
double *outRs, double *outTs,
|
|
323
|
+
double *outRp, double *outTp,
|
|
324
|
+
double *outRrs, double *outRrp) {
|
|
325
|
+
cx n0 = cmk(n0_re, n0_im);
|
|
326
|
+
cx ns = cmk(ns_re, ns_im);
|
|
327
|
+
cx ng = cmk(ng_re, ng_im);
|
|
328
|
+
cx sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
|
|
329
|
+
cx cosTheta0 = incidentCosTheta(n0, sinTheta0);
|
|
330
|
+
|
|
331
|
+
for (int pol = 0; pol < 2; pol++) {
|
|
332
|
+
cx eta0 = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
333
|
+
cx cosThetaS = snellCosTheta(n0, sinTheta0, ns);
|
|
334
|
+
cx etaS = (pol == 0) ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
|
|
335
|
+
|
|
336
|
+
mat2 Mb;
|
|
337
|
+
Mb.a = cmk(1.0, 0.0); Mb.b = cmk(0.0, 0.0);
|
|
338
|
+
Mb.c = cmk(0.0, 0.0); Mb.d = cmk(1.0, 0.0);
|
|
339
|
+
double logScaleB = 0.0;
|
|
340
|
+
for (int i = 0; i < NB; i++) {
|
|
341
|
+
cx n = cmk(base[3 * i + 0], base[3 * i + 1]);
|
|
342
|
+
double d = base[3 * i + 2];
|
|
343
|
+
if (d <= 0.0) continue;
|
|
344
|
+
cx cosThetaJ = snellCosTheta(n0, sinTheta0, n);
|
|
345
|
+
Mb = matmul(Mb, layerMatrix(n, d, lambda_nm, cosThetaJ, pol));
|
|
346
|
+
logScaleB += rescaleMatrix(&Mb);
|
|
347
|
+
}
|
|
348
|
+
cx cosThetaG = snellCosTheta(n0, sinTheta0, ng);
|
|
349
|
+
|
|
350
|
+
double *oR = pol ? outRp : outRs;
|
|
351
|
+
double *oT = pol ? outTp : outTs;
|
|
352
|
+
double *oRr = pol ? outRrp : outRrs;
|
|
353
|
+
|
|
354
|
+
for (int k = 0; k < nD; k++) {
|
|
355
|
+
mat2 M = Mb;
|
|
356
|
+
double logScale = logScaleB;
|
|
357
|
+
double d = dArr[k];
|
|
358
|
+
if (d > 0.0) {
|
|
359
|
+
M = matmul(layerMatrix(ng, d, lambda_nm, cosThetaG, pol), Mb);
|
|
360
|
+
logScale += rescaleMatrix(&M);
|
|
361
|
+
}
|
|
362
|
+
growingTailFwd(M, eta0, etaS, logScale, &oR[k], &oT[k]);
|
|
363
|
+
oRr[k] = growingTailRev(M, eta0, etaS);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* ── Exported: spectra of a growing stack, one per deposited layer ───────────
|
|
369
|
+
* The front-surface passes of a deposition run in a single call: layers arrive
|
|
370
|
+
* in DEPOSITION order (first deposited, substrate-adjacent, first), each is
|
|
371
|
+
* folded into the running product from the left, since the newest layer faces
|
|
372
|
+
* the incident medium, and the spectrum after every fold is written out. The
|
|
373
|
+
* same incremental structure as tmm_monitor_curve over a wavelength grid, so
|
|
374
|
+
* all N step spectra together cost what the final one costs alone.
|
|
375
|
+
*
|
|
376
|
+
* Memory layout mirrors tmm_spectrum:
|
|
377
|
+
* matNK : N × nLam × 2, [layer][λ][re, im], deposition order
|
|
378
|
+
* thick : N thicknesses (nm); a step of zero thickness repeats the previous
|
|
379
|
+
* spectrum, matching the d ≤ 0 skip of the reference loop
|
|
380
|
+
* Outputs, each N × nLam, step-major ([step][λ]):
|
|
381
|
+
* outRs/outTs, outRp/outTp : forward R and T after each step
|
|
382
|
+
* outRrs/outRrp : reflectance from the substrate side after each
|
|
383
|
+
* step, for the incoherent slab combination */
|
|
384
|
+
|
|
385
|
+
/* Returns 1 on success; 0, with the outputs untouched, when the working
|
|
386
|
+
* state could not be allocated. */
|
|
387
|
+
TMM_EXPORT("tmm_deposition_spectra")
|
|
388
|
+
int tmm_deposition_spectra(const double *lambdas, int nLam,
|
|
389
|
+
const double *n0arr, const double *nsarr,
|
|
390
|
+
const double *matNK, const double *thick, int N,
|
|
391
|
+
double theta_deg,
|
|
392
|
+
double *outRs, double *outTs,
|
|
393
|
+
double *outRp, double *outTp,
|
|
394
|
+
double *outRrs, double *outRrp) {
|
|
395
|
+
/* Running product and admittances per (λ, pol); pol-major blocks. */
|
|
396
|
+
size_t cells = (size_t)nLam * 2;
|
|
397
|
+
mat2 *M = (mat2 *)malloc(sizeof(mat2) * cells);
|
|
398
|
+
double *logScale = (double *)malloc(sizeof(double) * cells);
|
|
399
|
+
cx *eta0v = (cx *)malloc(sizeof(cx) * cells);
|
|
400
|
+
cx *etaSv = (cx *)malloc(sizeof(cx) * cells);
|
|
401
|
+
if (!M || !logScale || !eta0v || !etaSv) {
|
|
402
|
+
free(M); free(logScale); free(eta0v); free(etaSv);
|
|
403
|
+
return 0;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
cx sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
|
|
407
|
+
|
|
408
|
+
for (int li = 0; li < nLam; li++) {
|
|
409
|
+
cx n0 = cmk(n0arr[2 * li + 0], n0arr[2 * li + 1]);
|
|
410
|
+
cx ns = cmk(nsarr[2 * li + 0], nsarr[2 * li + 1]);
|
|
411
|
+
cx cosTheta0 = incidentCosTheta(n0, sinTheta0);
|
|
412
|
+
for (int pol = 0; pol < 2; pol++) {
|
|
413
|
+
size_t at = (size_t)pol * nLam + li;
|
|
414
|
+
eta0v[at] = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
415
|
+
cx cosThetaS = snellCosTheta(n0, sinTheta0, ns);
|
|
416
|
+
etaSv[at] = (pol == 0) ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
|
|
417
|
+
M[at].a = cmk(1.0, 0.0); M[at].b = cmk(0.0, 0.0);
|
|
418
|
+
M[at].c = cmk(0.0, 0.0); M[at].d = cmk(1.0, 0.0);
|
|
419
|
+
logScale[at] = 0.0;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
for (int k = 0; k < N; k++) {
|
|
424
|
+
double d = thick[k];
|
|
425
|
+
for (int li = 0; li < nLam; li++) {
|
|
426
|
+
double lam = lambdas[li];
|
|
427
|
+
cx n0 = cmk(n0arr[2 * li + 0], n0arr[2 * li + 1]);
|
|
428
|
+
cx n = cmk(matNK[((size_t)k * nLam + li) * 2 + 0],
|
|
429
|
+
matNK[((size_t)k * nLam + li) * 2 + 1]);
|
|
430
|
+
for (int pol = 0; pol < 2; pol++) {
|
|
431
|
+
size_t at = (size_t)pol * nLam + li;
|
|
432
|
+
if (d > 0.0) {
|
|
433
|
+
cx cosThetaJ = snellCosTheta(n0, sinTheta0, n);
|
|
434
|
+
M[at] = matmul(layerMatrix(n, d, lam, cosThetaJ, pol), M[at]);
|
|
435
|
+
logScale[at] += rescaleMatrix(&M[at]);
|
|
436
|
+
}
|
|
437
|
+
size_t out = (size_t)k * nLam + li;
|
|
438
|
+
double *oR = pol ? outRp : outRs;
|
|
439
|
+
double *oT = pol ? outTp : outTs;
|
|
440
|
+
double *oRr = pol ? outRrp : outRrs;
|
|
441
|
+
growingTailFwd(M[at], eta0v[at], etaSv[at], logScale[at],
|
|
442
|
+
&oR[out], &oT[out]);
|
|
443
|
+
oRr[out] = growingTailRev(M[at], eta0v[at], etaSv[at]);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
free(M); free(logScale); free(eta0v); free(etaSv);
|
|
449
|
+
return 1;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/* ── Exported: persistent growing-layer evaluator over a wavelength grid ─────
|
|
453
|
+
* The stateful counterpart of tmm_monitor_curve, batched the other way: one
|
|
454
|
+
* thickness of the growing layer per call, the WHOLE wavelength grid at once.
|
|
455
|
+
* This is the shape a broadband monitor scan needs, where every scan reads a
|
|
456
|
+
* full spectrum of the growing stack and the completed layers beneath do not
|
|
457
|
+
* change until the layer is cut.
|
|
458
|
+
*
|
|
459
|
+
* Lifecycle: create() folds the completed stack's characteristic matrices for
|
|
460
|
+
* every (λ, pol) once and keeps them; set_top() declares the growing layer's
|
|
461
|
+
* ñ(λ); sample() then answers one thickness across the grid, costing one layer
|
|
462
|
+
* matrix, one 2×2 multiply and the tails per (λ, pol). free() releases the
|
|
463
|
+
* state. Handles are opaque pointers; the caller owns their lifetime.
|
|
464
|
+
*
|
|
465
|
+
* Layout matches tmm_deposition_spectra: matNK is NB × nLam × 2 layer-major
|
|
466
|
+
* with the completed stack OUTERMOST FIRST (the fold is M = M · M_k, exactly
|
|
467
|
+
* the JS reference createMonitorTmmEvaluator); zero-thickness entries are
|
|
468
|
+
* skipped. Outputs of sample(), each nLam: forward R and T plus the
|
|
469
|
+
* substrate-side reflectance, for the incoherent slab combination. */
|
|
470
|
+
|
|
471
|
+
typedef struct {
|
|
472
|
+
int nLam;
|
|
473
|
+
cx sinTheta0;
|
|
474
|
+
int topSet;
|
|
475
|
+
double *lam; /* nLam */
|
|
476
|
+
cx *n0; /* nLam */
|
|
477
|
+
cx *ng; /* nLam, set_top */
|
|
478
|
+
cx *cosThetaG; /* nLam, set_top (polarization-independent) */
|
|
479
|
+
cx *eta0; /* 2·nLam, pol-major */
|
|
480
|
+
cx *etaS; /* 2·nLam */
|
|
481
|
+
mat2 *Mb; /* 2·nLam completed-stack product */
|
|
482
|
+
double *logScaleB; /* 2·nLam */
|
|
483
|
+
} growing_eval;
|
|
484
|
+
|
|
485
|
+
TMM_EXPORT("tmm_growing_eval_free")
|
|
486
|
+
void tmm_growing_eval_free(growing_eval *h) {
|
|
487
|
+
if (!h) return;
|
|
488
|
+
free(h->lam); free(h->n0); free(h->ng); free(h->cosThetaG);
|
|
489
|
+
free(h->eta0); free(h->etaS); free(h->Mb); free(h->logScaleB);
|
|
490
|
+
free(h);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
TMM_EXPORT("tmm_growing_eval_create")
|
|
494
|
+
growing_eval *tmm_growing_eval_create(const double *lambdas, int nLam,
|
|
495
|
+
double theta_deg,
|
|
496
|
+
const double *n0arr, const double *nsarr,
|
|
497
|
+
const double *matNK, const double *thick,
|
|
498
|
+
int NB) {
|
|
499
|
+
growing_eval *h = (growing_eval *)malloc(sizeof(growing_eval));
|
|
500
|
+
if (!h) return 0;
|
|
501
|
+
h->nLam = nLam;
|
|
502
|
+
h->topSet = 0;
|
|
503
|
+
h->lam = (double *)malloc(sizeof(double) * nLam);
|
|
504
|
+
h->n0 = (cx *)malloc(sizeof(cx) * nLam);
|
|
505
|
+
h->ng = (cx *)malloc(sizeof(cx) * nLam);
|
|
506
|
+
h->cosThetaG = (cx *)malloc(sizeof(cx) * nLam);
|
|
507
|
+
h->eta0 = (cx *)malloc(sizeof(cx) * 2 * nLam);
|
|
508
|
+
h->etaS = (cx *)malloc(sizeof(cx) * 2 * nLam);
|
|
509
|
+
h->Mb = (mat2 *)malloc(sizeof(mat2) * 2 * nLam);
|
|
510
|
+
h->logScaleB = (double *)malloc(sizeof(double) * 2 * nLam);
|
|
511
|
+
if (!h->lam || !h->n0 || !h->ng || !h->cosThetaG
|
|
512
|
+
|| !h->eta0 || !h->etaS || !h->Mb || !h->logScaleB) {
|
|
513
|
+
tmm_growing_eval_free(h);
|
|
514
|
+
return 0;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
h->sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
|
|
518
|
+
|
|
519
|
+
for (int li = 0; li < nLam; li++) {
|
|
520
|
+
h->lam[li] = lambdas[li];
|
|
521
|
+
cx n0 = cmk(n0arr[2 * li + 0], n0arr[2 * li + 1]);
|
|
522
|
+
cx ns = cmk(nsarr[2 * li + 0], nsarr[2 * li + 1]);
|
|
523
|
+
h->n0[li] = n0;
|
|
524
|
+
cx cosTheta0 = incidentCosTheta(n0, h->sinTheta0);
|
|
525
|
+
for (int pol = 0; pol < 2; pol++) {
|
|
526
|
+
size_t at = (size_t)pol * nLam + li;
|
|
527
|
+
h->eta0[at] = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
528
|
+
cx cosThetaS = snellCosTheta(n0, h->sinTheta0, ns);
|
|
529
|
+
h->etaS[at] = (pol == 0) ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
|
|
530
|
+
mat2 M;
|
|
531
|
+
M.a = cmk(1.0, 0.0); M.b = cmk(0.0, 0.0);
|
|
532
|
+
M.c = cmk(0.0, 0.0); M.d = cmk(1.0, 0.0);
|
|
533
|
+
double logScale = 0.0;
|
|
534
|
+
for (int k = 0; k < NB; k++) {
|
|
535
|
+
double d = thick[k];
|
|
536
|
+
if (d <= 0.0) continue;
|
|
537
|
+
cx n = cmk(matNK[((size_t)k * nLam + li) * 2 + 0],
|
|
538
|
+
matNK[((size_t)k * nLam + li) * 2 + 1]);
|
|
539
|
+
cx cosThetaJ = snellCosTheta(n0, h->sinTheta0, n);
|
|
540
|
+
M = matmul(M, layerMatrix(n, d, lambdas[li], cosThetaJ, pol));
|
|
541
|
+
logScale += rescaleMatrix(&M);
|
|
542
|
+
}
|
|
543
|
+
h->Mb[at] = M;
|
|
544
|
+
h->logScaleB[at] = logScale;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return h;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
TMM_EXPORT("tmm_growing_eval_set_top")
|
|
551
|
+
void tmm_growing_eval_set_top(growing_eval *h, const double *ngNK) {
|
|
552
|
+
if (!h) return;
|
|
553
|
+
for (int li = 0; li < h->nLam; li++) {
|
|
554
|
+
cx ng = cmk(ngNK[2 * li + 0], ngNK[2 * li + 1]);
|
|
555
|
+
h->ng[li] = ng;
|
|
556
|
+
h->cosThetaG[li] = snellCosTheta(h->n0[li], h->sinTheta0, ng);
|
|
557
|
+
}
|
|
558
|
+
h->topSet = 1;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/* Returns 1 on success; 0, with the outputs untouched, for a null handle or
|
|
562
|
+
* a d > 0 sample before set_top declared the growing layer. The status is
|
|
563
|
+
* what keeps an unwritten buffer from being read back as data. */
|
|
564
|
+
TMM_EXPORT("tmm_growing_eval_sample")
|
|
565
|
+
int tmm_growing_eval_sample(growing_eval *h, double d,
|
|
566
|
+
double *outRs, double *outTs,
|
|
567
|
+
double *outRp, double *outTp,
|
|
568
|
+
double *outRrs, double *outRrp) {
|
|
569
|
+
if (!h || (d > 0.0 && !h->topSet)) return 0;
|
|
570
|
+
for (int li = 0; li < h->nLam; li++) {
|
|
571
|
+
for (int pol = 0; pol < 2; pol++) {
|
|
572
|
+
size_t at = (size_t)pol * h->nLam + li;
|
|
573
|
+
mat2 M = h->Mb[at];
|
|
574
|
+
double logScale = h->logScaleB[at];
|
|
575
|
+
if (d > 0.0) {
|
|
576
|
+
M = matmul(layerMatrix(h->ng[li], d, h->lam[li], h->cosThetaG[li], pol),
|
|
577
|
+
h->Mb[at]);
|
|
578
|
+
logScale += rescaleMatrix(&M);
|
|
579
|
+
}
|
|
580
|
+
double *oR = pol ? outRp : outRs;
|
|
581
|
+
double *oT = pol ? outTp : outTs;
|
|
582
|
+
double *oRr = pol ? outRrp : outRrs;
|
|
583
|
+
growingTailFwd(M, h->eta0[at], h->etaS[at], logScale, &oR[li], &oT[li]);
|
|
584
|
+
oRr[li] = growingTailRev(M, h->eta0[at], h->etaS[at]);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return 1;
|
|
588
|
+
}
|
|
589
|
+
|
|
248
590
|
/* ── Exported: analytic thickness Jacobian for one (λ, θ, pol) ────────────────
|
|
249
591
|
* Faithful port of tmmThicknessJacobian() in thinFilmMath.js. Returns the exact
|
|
250
592
|
* analytic dR/dd_k, dT/dd_k, dA/dd_k for every layer at one sample : the DLS
|
|
@@ -263,7 +605,7 @@ void tmm_jacobian(double lambda_nm, double theta_deg, int pol,
|
|
|
263
605
|
cx ns = cmk(ns_re, ns_im);
|
|
264
606
|
|
|
265
607
|
cx sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
|
|
266
|
-
cx cosTheta0 =
|
|
608
|
+
cx cosTheta0 = incidentCosTheta(n0, sinTheta0);
|
|
267
609
|
cx eta0 = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
268
610
|
cx cosThetaS = snellCosTheta(n0, sinTheta0, ns);
|
|
269
611
|
cx etaS = (pol == 0) ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
|
|
@@ -366,7 +708,7 @@ void tmm_needle_scan(double lambda_nm, double theta_deg, int pol,
|
|
|
366
708
|
double *base, double *gaps, double *intra) {
|
|
367
709
|
cx n0 = cmk(n0_re, n0_im), ns = cmk(ns_re, ns_im);
|
|
368
710
|
cx sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
|
|
369
|
-
cx cosTheta0 =
|
|
711
|
+
cx cosTheta0 = incidentCosTheta(n0, sinTheta0);
|
|
370
712
|
cx eta0 = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
371
713
|
cx cosThetaS = snellCosTheta(n0, sinTheta0, ns);
|
|
372
714
|
cx etaS = (pol == 0) ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
|
|
@@ -475,7 +817,7 @@ void tmm_hessian(double lambda_nm, double theta_deg, int pol,
|
|
|
475
817
|
cx ns = cmk(ns_re, ns_im);
|
|
476
818
|
|
|
477
819
|
cx sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
|
|
478
|
-
cx cosTheta0 =
|
|
820
|
+
cx cosTheta0 = incidentCosTheta(n0, sinTheta0);
|
|
479
821
|
cx eta0 = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
|
|
480
822
|
cx cosThetaS = snellCosTheta(n0, sinTheta0, ns);
|
|
481
823
|
cx etaS = (pol == 0) ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
|
|
@@ -753,10 +1095,32 @@ static double jmatmag(jmat2 M) {
|
|
|
753
1095
|
return magnitude;
|
|
754
1096
|
}
|
|
755
1097
|
|
|
1098
|
+
/* The real part of a jet, order by order. */
|
|
1099
|
+
static inline jet jreal(jet a) {
|
|
1100
|
+
jet o;
|
|
1101
|
+
for (int i = 0; i < JET_N; i++) o.c[i] = cmk(a.c[i].re, 0.0);
|
|
1102
|
+
return o;
|
|
1103
|
+
}
|
|
1104
|
+
static inline int jhas_imag(jet a) {
|
|
1105
|
+
for (int i = 0; i < JET_N; i++) if (a.c[i].im != 0.0) return 1;
|
|
1106
|
+
return 0;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/* Same real invariant Re(n0) sinθ0 as snellCosTheta, in jet arithmetic. */
|
|
756
1110
|
static inline jet jsnell_cos(jet n0, jet sin0, jet nj) {
|
|
757
|
-
jet s = jdiv(jmul(n0, sin0), nj);
|
|
1111
|
+
jet s = jdiv(jmul(jreal(n0), sin0), nj);
|
|
758
1112
|
return jsqrt_j(jsub(jconst(1.0, 0.0), jmul(s, s)));
|
|
759
1113
|
}
|
|
1114
|
+
/* cosθ0 of the incident medium: from the same real invariant as the layers
|
|
1115
|
+
* when the medium absorbs at any order, otherwise the plain cosine, taken
|
|
1116
|
+
* from the sine jet when the caller supplied one. Mirrors incidentCosine in
|
|
1117
|
+
* phase.js. */
|
|
1118
|
+
static inline jet jincident_cos(jet n0, jet incidentSine, int fromSineJet, double theta_deg) {
|
|
1119
|
+
if (jhas_imag(n0)) return jsnell_cos(n0, incidentSine, n0);
|
|
1120
|
+
return fromSineJet
|
|
1121
|
+
? jsqrt_j(jsub(jconst(1.0, 0.0), jmul(incidentSine, incidentSine)))
|
|
1122
|
+
: jconst(cos(theta_deg * PI / 180.0), 0.0);
|
|
1123
|
+
}
|
|
760
1124
|
static inline jet jadmittance(jet n, jet cosv, int pol) {
|
|
761
1125
|
return (pol == 0) ? jmul(n, cosv) : jdiv(n, cosv);
|
|
762
1126
|
}
|
|
@@ -877,9 +1241,7 @@ static void jphase_core(double lambda, double omega, double theta_deg, int pol,
|
|
|
877
1241
|
const jet *sinJet, double *out10) {
|
|
878
1242
|
jet wavelength = jwavelength(lambda, omega);
|
|
879
1243
|
jet incidentSine = sinJet ? *sinJet : jconst(sin(theta_deg * PI / 180.0), 0.0);
|
|
880
|
-
jet incidentCosine = sinJet
|
|
881
|
-
? jsqrt_j(jsub(jconst(1.0, 0.0), jmul(incidentSine, incidentSine)))
|
|
882
|
-
: jconst(cos(theta_deg * PI / 180.0), 0.0);
|
|
1244
|
+
jet incidentCosine = jincident_cos(n0, incidentSine, sinJet != NULL, theta_deg);
|
|
883
1245
|
jet incidentEta = jadmittance(n0, incidentCosine, pol);
|
|
884
1246
|
jet substrateCosine = jsnell_cos(n0, incidentSine, ns);
|
|
885
1247
|
jet substrateEta = jadmittance(ns, substrateCosine, pol);
|
|
@@ -983,9 +1345,7 @@ void tmm_phase_jacobian(double lambda, double omega, double theta_deg, int pol,
|
|
|
983
1345
|
|
|
984
1346
|
jet wavelength = jwavelength(lambda, omega);
|
|
985
1347
|
jet incidentSine = sinePtr ? *sinePtr : jconst(sin(theta_deg * PI / 180.0), 0.0);
|
|
986
|
-
jet incidentCosine = sinePtr
|
|
987
|
-
? jsqrt_j(jsub(jconst(1.0, 0.0), jmul(incidentSine, incidentSine)))
|
|
988
|
-
: jconst(cos(theta_deg * PI / 180.0), 0.0);
|
|
1348
|
+
jet incidentCosine = jincident_cos(n0, incidentSine, sinePtr != NULL, theta_deg);
|
|
989
1349
|
jet incidentEta = jadmittance(n0, incidentCosine, pol);
|
|
990
1350
|
jet substrateCosine = jsnell_cos(n0, incidentSine, ns);
|
|
991
1351
|
jet substrateEta = jadmittance(ns, substrateCosine, pol);
|
package/src/tmm_kernel.wasm
CHANGED
|
Binary file
|