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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmmcore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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 && node tests/growing_equivalence.mjs && node tests/growing_eval_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,_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",
|
|
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_phase_jacobian_spectrum,_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/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) => {
|
|
@@ -419,14 +430,17 @@ export function coefficientPhaseDispersion(coefficientJet) {
|
|
|
419
430
|
*/
|
|
420
431
|
export function coefficientPhaseThicknessDerivatives(coefficientJet, thicknessJets) {
|
|
421
432
|
if (!thicknessJets) return null;
|
|
422
|
-
const result = { phaseDeg: [], gd: [], gdd: [], tod: [] };
|
|
433
|
+
const result = { phaseDeg: [], gd: [], gdd: [], tod: [], logMagnitudeSquared: [] };
|
|
423
434
|
for (const thicknessJet of thicknessJets) {
|
|
424
435
|
const logarithmicDerivative = jetDivide(thicknessJet, coefficientJet);
|
|
425
436
|
const derivatives = jetDerivatives(logarithmicDerivative);
|
|
437
|
+
// The imaginary part of d(ln c)/dd is the phase derivative; the real
|
|
438
|
+
// part is d(ln |c|)/dd, so twice it is the relative derivative of |c|².
|
|
426
439
|
result.phaseDeg.push(-derivatives[0][1] * 180 / Math.PI);
|
|
427
440
|
result.gd.push(derivatives[1][1]);
|
|
428
441
|
result.gdd.push(derivatives[2][1]);
|
|
429
442
|
result.tod.push(derivatives[3][1]);
|
|
443
|
+
result.logMagnitudeSquared.push(2 * derivatives[0][0]);
|
|
430
444
|
}
|
|
431
445
|
return result;
|
|
432
446
|
}
|
|
@@ -489,8 +503,10 @@ export function tmmPhaseDispersion(lambda_nm, theta_deg, pol, n0Jet, nsJet, laye
|
|
|
489
503
|
* thicknesses are skipped and receive zero derivative entries.
|
|
490
504
|
*
|
|
491
505
|
* @returns {{r, t}} where each side is the phase quantities plus
|
|
492
|
-
* `{dPhaseDeg, dGd, dGdd, dTod}`, arrays of length
|
|
493
|
-
*
|
|
506
|
+
* `{dPhaseDeg, dGd, dGdd, dTod, dLogMagnitudeSquared}`, arrays of length
|
|
507
|
+
* `layers.length`. `dLogMagnitudeSquared` is d(ln |coefficient|²)/dd, the
|
|
508
|
+
* relative intensity derivative. The derivative arrays are `null` if the
|
|
509
|
+
* matrix product overflowed.
|
|
494
510
|
*/
|
|
495
511
|
export function tmmPhaseThicknessJacobian(lambda_nm, theta_deg, pol, n0Jet, nsJet, layers, options = {}) {
|
|
496
512
|
const prepared = prepare(lambda_nm, layers, options);
|
|
@@ -513,6 +529,7 @@ export function tmmPhaseThicknessJacobian(lambda_nm, theta_deg, pol, n0Jet, nsJe
|
|
|
513
529
|
dGd: derivatives ? derivatives.gd : null,
|
|
514
530
|
dGdd: derivatives ? derivatives.gdd : null,
|
|
515
531
|
dTod: derivatives ? derivatives.tod : null,
|
|
532
|
+
dLogMagnitudeSquared: derivatives ? derivatives.logMagnitudeSquared : null,
|
|
516
533
|
};
|
|
517
534
|
};
|
|
518
535
|
return {
|
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
|
};
|