tmmcore 0.1.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/README.md +22 -33
- package/package.json +12 -6
- package/src/build.ps1 +8 -2
- package/src/index.js +19 -2
- package/src/phase.js +522 -0
- package/src/taylorJet.js +261 -0
- package/src/tmm.js +4 -4
- package/src/tmmWasm.js +454 -13
- package/src/tmm_kernel.c +798 -7
- package/src/tmm_kernel.wasm +0 -0
package/src/tmm_kernel.c
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* tmm_kernel.c
|
|
2
|
+
* tmm_kernel.c : transfer-matrix method for multilayer thin films.
|
|
3
3
|
*
|
|
4
4
|
* Computes reflectance, transmittance and absorptance for a stack of absorbing,
|
|
5
5
|
* dispersive layers at arbitrary angle of incidence, in s and p polarization,
|
|
6
6
|
* together with exact analytic derivatives: the thickness Jacobian, the
|
|
7
7
|
* thickness Hessian, and the needle-insertion P-function.
|
|
8
8
|
*
|
|
9
|
+
* Also computes phase, group delay, group delay dispersion and third-order
|
|
10
|
+
* dispersion, by carrying the same characteristic matrix in third-order Taylor
|
|
11
|
+
* arithmetic with angular frequency as the differentiation variable.
|
|
12
|
+
*
|
|
9
13
|
* Conventions:
|
|
10
14
|
* ñ = n + ik k > 0 for absorbing media
|
|
11
15
|
* time factor exp(−iωt) a wave exp(i(kz − ωt)) decays for k > 0
|
|
@@ -22,6 +26,7 @@
|
|
|
22
26
|
* • Macleod, Thin-Film Optical Filters 5th ed., §2.4, Eqs. 2.111, 2.123–2.125
|
|
23
27
|
* • Sullivan & Dobrowolski, Appl. Opt. 35, 5484 (1996), Eqs. (3)–(6)
|
|
24
28
|
* • Tikhonravov, Trubetskov & DeBell, Appl. Opt. 35, 5493 (1996)
|
|
29
|
+
* • Birge & Kärtner, Appl. Opt. 45, 1478 (2006) [phase dispersion]
|
|
25
30
|
*/
|
|
26
31
|
|
|
27
32
|
#include <math.h>
|
|
@@ -57,7 +62,7 @@ static inline cx cdiv(cx a, cx b) {
|
|
|
57
62
|
static inline double cabs2(cx a) { return a.re * a.re + a.im * a.im; }
|
|
58
63
|
static inline cx cconj(cx a) { return cmk(a.re, -a.im); }
|
|
59
64
|
static inline cx csqrt_(cx a) {
|
|
60
|
-
/* r = sqrt(sqrt(re²+im²)); theta = atan2(im,re)/2
|
|
65
|
+
/* r = sqrt(sqrt(re²+im²)); theta = atan2(im,re)/2 : JS csqrt verbatim */
|
|
61
66
|
double r = sqrt(sqrt(a.re * a.re + a.im * a.im));
|
|
62
67
|
double theta = atan2(a.im, a.re) / 2.0;
|
|
63
68
|
return cmk(r * cos(theta), r * sin(theta));
|
|
@@ -240,9 +245,339 @@ void tmm_spectrum(const double *lambdas, int nLam,
|
|
|
240
245
|
if (layers) free(layers);
|
|
241
246
|
}
|
|
242
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
|
+
|
|
243
578
|
/* ── Exported: analytic thickness Jacobian for one (λ, θ, pol) ────────────────
|
|
244
579
|
* Faithful port of tmmThicknessJacobian() in thinFilmMath.js. Returns the exact
|
|
245
|
-
* analytic dR/dd_k, dT/dd_k, dA/dd_k for every layer at one sample
|
|
580
|
+
* analytic dR/dd_k, dT/dd_k, dA/dd_k for every layer at one sample : the DLS
|
|
246
581
|
* refiner's per-step gradient (2·N fewer evals than central differences).
|
|
247
582
|
*
|
|
248
583
|
* `layers` is N triples [n_re, n_im, d]; layers used AS-IS (no d>0 filter) for
|
|
@@ -317,7 +652,7 @@ void tmm_jacobian(double lambda_nm, double theta_deg, int pol,
|
|
|
317
652
|
vec2 dV = cmatvec(Pre[k], cmatvec(dMk, Post[k + 1]));
|
|
318
653
|
cx dB = dV.x, dC = dV.y;
|
|
319
654
|
|
|
320
|
-
/* metrics(dB,dC)
|
|
655
|
+
/* metrics(dB,dC) : verbatim from validated tmmNeedleScan.metrics */
|
|
321
656
|
cx dr = cmul(f, csub(cmul(Cv, dB), cmul(Bv, dC)));
|
|
322
657
|
double dR = 2.0 * (cmul(cconj(r), dr)).re;
|
|
323
658
|
cx dt = cmul(neg1, cmul(f, cadd(cmul(eta0, dB), dC)));
|
|
@@ -329,7 +664,7 @@ void tmm_jacobian(double lambda_nm, double theta_deg, int pol,
|
|
|
329
664
|
}
|
|
330
665
|
|
|
331
666
|
/* ── Analytic needle P-function scan ─────────────────────────────────────────
|
|
332
|
-
* Faithful port of tmmNeedleScan() in thinFilmMath.js
|
|
667
|
+
* Faithful port of tmmNeedleScan() in thinFilmMath.js : the d→0 limit of
|
|
333
668
|
* Sullivan's pre/post method (Tikhonravov's analytic P-function). Returns the
|
|
334
669
|
* merit-gradient ingredients {dR,dT,dA} of inserting an infinitesimal needle of
|
|
335
670
|
* each candidate index at every gap position (0..N) and, optionally, at intra-
|
|
@@ -342,7 +677,7 @@ void tmm_jacobian(double lambda_nm, double theta_deg, int pol,
|
|
|
342
677
|
* intra : N*nFrac*nCand*3 layout [layer][frac][cand][dR,dT,dA] (nFrac>0)
|
|
343
678
|
*/
|
|
344
679
|
|
|
345
|
-
/* {dR,dT,dA} from d[B,C]/dd
|
|
680
|
+
/* {dR,dT,dA} from d[B,C]/dd : verbatim from tmm_jacobian.metrics. */
|
|
346
681
|
static void needle_metrics(cx Bv, cx Cv, cx eta0, cx f, cx r, cx t, double Tfac,
|
|
347
682
|
cx dB, cx dC, double *o) {
|
|
348
683
|
cx dr = cmul(f, csub(cmul(Cv, dB), cmul(Bv, dC)));
|
|
@@ -450,7 +785,7 @@ void tmm_needle_scan(double lambda_nm, double theta_deg, int pol,
|
|
|
450
785
|
}
|
|
451
786
|
|
|
452
787
|
/* ── Analytic thickness-Hessian kernel ───────────
|
|
453
|
-
* LINE-BY-LINE port of tmmThicknessHessian() in thinFilmMath.js
|
|
788
|
+
* LINE-BY-LINE port of tmmThicknessHessian() in thinFilmMath.js : the EXACT
|
|
454
789
|
* analytic second derivatives ∂²{R,T,A}/∂dᵢ∂dⱼ (full N×N symmetric) plus the
|
|
455
790
|
* first derivatives, at one (λ,θ,pol). Used by the bounded-SQP / Newton inner
|
|
456
791
|
* refiner; the JS remains the oracle (tests/wasm_hessian_equivalence.mjs).
|
|
@@ -587,3 +922,459 @@ void tmm_hessian(double lambda_nm, double theta_deg, int pol,
|
|
|
587
922
|
free(cosThJ); free(Ms); free(Pre); free(Post);
|
|
588
923
|
free(dM); free(d2M); free(v); free(dBa); free(dCa);
|
|
589
924
|
}
|
|
925
|
+
|
|
926
|
+
/* ── Third-order Taylor jets ─────────────────────────────────────────────────
|
|
927
|
+
* A jet holds [f, f', f''/2!, f'''/3!], each entry complex. Ordinary power-
|
|
928
|
+
* series algebra on these differentiates a function exactly, with no finite
|
|
929
|
+
* differences. Port of taylorJet.js; where the JS multiplies by a reciprocal
|
|
930
|
+
* rather than dividing, so does this, since the two are not bit-identical. */
|
|
931
|
+
|
|
932
|
+
#define JET_N 4
|
|
933
|
+
|
|
934
|
+
typedef struct { cx c[JET_N]; } jet;
|
|
935
|
+
|
|
936
|
+
static inline jet jconst(double re, double im) {
|
|
937
|
+
jet j;
|
|
938
|
+
j.c[0] = cmk(re, im);
|
|
939
|
+
j.c[1] = cmk(0.0, 0.0); j.c[2] = cmk(0.0, 0.0); j.c[3] = cmk(0.0, 0.0);
|
|
940
|
+
return j;
|
|
941
|
+
}
|
|
942
|
+
static inline jet jread(const double *p) {
|
|
943
|
+
jet j;
|
|
944
|
+
for (int i = 0; i < JET_N; i++) j.c[i] = cmk(p[2 * i], p[2 * i + 1]);
|
|
945
|
+
return j;
|
|
946
|
+
}
|
|
947
|
+
static inline jet jadd(jet a, jet b) {
|
|
948
|
+
jet o; for (int i = 0; i < JET_N; i++) o.c[i] = cadd(a.c[i], b.c[i]); return o;
|
|
949
|
+
}
|
|
950
|
+
static inline jet jsub(jet a, jet b) {
|
|
951
|
+
jet o; for (int i = 0; i < JET_N; i++) o.c[i] = csub(a.c[i], b.c[i]); return o;
|
|
952
|
+
}
|
|
953
|
+
static inline jet jscale(jet a, double s) {
|
|
954
|
+
jet o; for (int i = 0; i < JET_N; i++) o.c[i] = cmk(a.c[i].re * s, a.c[i].im * s); return o;
|
|
955
|
+
}
|
|
956
|
+
static inline jet jmul(jet a, jet b) {
|
|
957
|
+
jet o;
|
|
958
|
+
for (int order = 0; order < JET_N; order++) {
|
|
959
|
+
cx sum = cmk(0.0, 0.0);
|
|
960
|
+
for (int i = 0; i <= order; i++) sum = cadd(sum, cmul(a.c[i], b.c[order - i]));
|
|
961
|
+
o.c[order] = sum;
|
|
962
|
+
}
|
|
963
|
+
return o;
|
|
964
|
+
}
|
|
965
|
+
static inline jet jrecip(jet a) {
|
|
966
|
+
jet o;
|
|
967
|
+
o.c[0] = cdiv(cmk(1.0, 0.0), a.c[0]);
|
|
968
|
+
for (int order = 1; order < JET_N; order++) {
|
|
969
|
+
cx sum = cmk(0.0, 0.0);
|
|
970
|
+
for (int i = 1; i <= order; i++) sum = cadd(sum, cmul(a.c[i], o.c[order - i]));
|
|
971
|
+
cx q = cdiv(sum, a.c[0]);
|
|
972
|
+
o.c[order] = cmk(-q.re, -q.im);
|
|
973
|
+
}
|
|
974
|
+
return o;
|
|
975
|
+
}
|
|
976
|
+
static inline jet jdiv(jet a, jet b) { return jmul(a, jrecip(b)); }
|
|
977
|
+
|
|
978
|
+
static inline jet jsqrt_j(jet a) {
|
|
979
|
+
jet o;
|
|
980
|
+
o.c[0] = csqrt_(a.c[0]);
|
|
981
|
+
cx twiceRoot = cmk(o.c[0].re * 2.0, o.c[0].im * 2.0);
|
|
982
|
+
for (int order = 1; order < JET_N; order++) {
|
|
983
|
+
cx known = cmk(0.0, 0.0);
|
|
984
|
+
for (int i = 1; i < order; i++) known = cadd(known, cmul(o.c[i], o.c[order - i]));
|
|
985
|
+
o.c[order] = cdiv(csub(a.c[order], known), twiceRoot);
|
|
986
|
+
}
|
|
987
|
+
return o;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
static void jsincos(jet a, jet *sine, jet *cosine) {
|
|
991
|
+
double re = a.c[0].re, im = a.c[0].im;
|
|
992
|
+
sine->c[0] = cmk(sin(re) * cosh(im), cos(re) * sinh(im));
|
|
993
|
+
cosine->c[0] = cmk(cos(re) * cosh(im), -sin(re) * sinh(im));
|
|
994
|
+
for (int order = 1; order < JET_N; order++) {
|
|
995
|
+
cx sineSum = cmk(0.0, 0.0), cosineSum = cmk(0.0, 0.0);
|
|
996
|
+
for (int i = 1; i <= order; i++) {
|
|
997
|
+
cx ts = cmul(a.c[i], cosine->c[order - i]);
|
|
998
|
+
cx tc = cmul(a.c[i], sine->c[order - i]);
|
|
999
|
+
sineSum = cadd(sineSum, cmk(ts.re * i, ts.im * i));
|
|
1000
|
+
cosineSum = cadd(cosineSum, cmk(tc.re * i, tc.im * i));
|
|
1001
|
+
}
|
|
1002
|
+
double inv = 1.0 / (double)order;
|
|
1003
|
+
sine->c[order] = cmk( sineSum.re * inv, sineSum.im * inv);
|
|
1004
|
+
cosine->c[order] = cmk(-cosineSum.re * inv, -cosineSum.im * inv);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/* Past the limit the layer is opaque: the derivatives are zero to machine
|
|
1009
|
+
* precision and dropping them keeps cosh from overflowing the whole product. */
|
|
1010
|
+
static inline jet jclampim(jet a, double limit) {
|
|
1011
|
+
if (a.c[0].im > limit || a.c[0].im < -limit) {
|
|
1012
|
+
double held = (a.c[0].im > limit) ? limit : -limit;
|
|
1013
|
+
jet o;
|
|
1014
|
+
o.c[0] = cmk(a.c[0].re, held);
|
|
1015
|
+
for (int i = 1; i < JET_N; i++) o.c[i] = cmk(a.c[i].re, 0.0);
|
|
1016
|
+
return o;
|
|
1017
|
+
}
|
|
1018
|
+
return a;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/* [f, f', f'', f'''] from the stored [f, f', f''/2!, f'''/3!]. */
|
|
1022
|
+
static inline void jderivs(jet a, cx *out) {
|
|
1023
|
+
out[0] = a.c[0];
|
|
1024
|
+
out[1] = a.c[1];
|
|
1025
|
+
out[2] = cmk(a.c[2].re * 2.0, a.c[2].im * 2.0);
|
|
1026
|
+
out[3] = cmk(a.c[3].re * 6.0, a.c[3].im * 6.0);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/* λ(ω) = 2πc/ω. Needs no value for c: with λ and ω given, λ' = −λ/ω. */
|
|
1030
|
+
static inline jet jwavelength(double lambda, double omega) {
|
|
1031
|
+
jet o;
|
|
1032
|
+
o.c[0] = cmk(lambda, 0.0);
|
|
1033
|
+
o.c[1] = cmk(-lambda / omega, 0.0);
|
|
1034
|
+
o.c[2] = cmk(lambda / (omega * omega), 0.0);
|
|
1035
|
+
o.c[3] = cmk(-lambda / (omega * omega * omega), 0.0);
|
|
1036
|
+
return o;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/* ── Jet-valued 2×2 matrices ─────────────────────────────────────────────── */
|
|
1040
|
+
|
|
1041
|
+
typedef struct { jet a, b, c, d; } jmat2;
|
|
1042
|
+
|
|
1043
|
+
static jmat2 jmatmul(jmat2 A, jmat2 B) {
|
|
1044
|
+
jmat2 M;
|
|
1045
|
+
M.a = jadd(jmul(A.a, B.a), jmul(A.b, B.c));
|
|
1046
|
+
M.b = jadd(jmul(A.a, B.b), jmul(A.b, B.d));
|
|
1047
|
+
M.c = jadd(jmul(A.c, B.a), jmul(A.d, B.c));
|
|
1048
|
+
M.d = jadd(jmul(A.c, B.b), jmul(A.d, B.d));
|
|
1049
|
+
return M;
|
|
1050
|
+
}
|
|
1051
|
+
static jmat2 jidentity(void) {
|
|
1052
|
+
jmat2 M;
|
|
1053
|
+
M.a = jconst(1.0, 0.0); M.b = jconst(0.0, 0.0);
|
|
1054
|
+
M.c = jconst(0.0, 0.0); M.d = jconst(1.0, 0.0);
|
|
1055
|
+
return M;
|
|
1056
|
+
}
|
|
1057
|
+
static jmat2 jzero(void) {
|
|
1058
|
+
jmat2 M;
|
|
1059
|
+
M.a = jconst(0.0, 0.0); M.b = jconst(0.0, 0.0);
|
|
1060
|
+
M.c = jconst(0.0, 0.0); M.d = jconst(0.0, 0.0);
|
|
1061
|
+
return M;
|
|
1062
|
+
}
|
|
1063
|
+
/* The order-0 matrix controls overflow in the physical coefficient. Once
|
|
1064
|
+
* selected, one plain scalar rescales every jet order and cancels from r. */
|
|
1065
|
+
static double jrescale(jmat2 *M, double threshold) {
|
|
1066
|
+
jet *e[4] = { &M->a, &M->b, &M->c, &M->d };
|
|
1067
|
+
double scale = 0.0;
|
|
1068
|
+
for (int i = 0; i < 4; i++) {
|
|
1069
|
+
scale = fmax(scale, fabs(e[i]->c[0].re));
|
|
1070
|
+
scale = fmax(scale, fabs(e[i]->c[0].im));
|
|
1071
|
+
}
|
|
1072
|
+
if (scale <= threshold) return 0.0;
|
|
1073
|
+
double inverse = 1.0 / scale;
|
|
1074
|
+
for (int i = 0; i < 4; i++) *e[i] = jscale(*e[i], inverse);
|
|
1075
|
+
return log(scale);
|
|
1076
|
+
}
|
|
1077
|
+
static double jmatmag(jmat2 M) {
|
|
1078
|
+
jet *e[4] = { &M.a, &M.b, &M.c, &M.d };
|
|
1079
|
+
double magnitude = 0.0;
|
|
1080
|
+
for (int i = 0; i < 4; i++)
|
|
1081
|
+
for (int o = 0; o < JET_N; o++)
|
|
1082
|
+
magnitude = fmax(magnitude, fmax(fabs(e[i]->c[o].re), fabs(e[i]->c[o].im)));
|
|
1083
|
+
return magnitude;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
static inline jet jsnell_cos(jet n0, jet sin0, jet nj) {
|
|
1087
|
+
jet s = jdiv(jmul(n0, sin0), nj);
|
|
1088
|
+
return jsqrt_j(jsub(jconst(1.0, 0.0), jmul(s, s)));
|
|
1089
|
+
}
|
|
1090
|
+
static inline jet jadmittance(jet n, jet cosv, int pol) {
|
|
1091
|
+
return (pol == 0) ? jmul(n, cosv) : jdiv(n, cosv);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
static jmat2 jlayer_matrix(jet index, double thickness, jet wavelength, jet cosine, int pol) {
|
|
1095
|
+
jet phase = jclampim(jscale(jdiv(jmul(index, cosine), wavelength),
|
|
1096
|
+
2.0 * PI * thickness), MAX_IM_DELTA);
|
|
1097
|
+
jet sine, cosinePhase;
|
|
1098
|
+
jsincos(phase, &sine, &cosinePhase);
|
|
1099
|
+
jet eta = jadmittance(index, cosine, pol);
|
|
1100
|
+
jet minusI = jconst(0.0, -1.0);
|
|
1101
|
+
jmat2 M;
|
|
1102
|
+
M.a = cosinePhase;
|
|
1103
|
+
M.b = jmul(minusI, jdiv(sine, eta));
|
|
1104
|
+
M.c = jmul(minusI, jmul(eta, sine));
|
|
1105
|
+
M.d = cosinePhase;
|
|
1106
|
+
return M;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
static void jlayer_matrix_dd(jet index, double thickness, jet wavelength, jet cosine, int pol,
|
|
1110
|
+
jmat2 *M, jmat2 *dM) {
|
|
1111
|
+
jet phasePerUnit = jscale(jdiv(jmul(index, cosine), wavelength), 2.0 * PI);
|
|
1112
|
+
jet rawPhase = jscale(phasePerUnit, thickness);
|
|
1113
|
+
jet phase = jclampim(rawPhase, MAX_IM_DELTA);
|
|
1114
|
+
jet phaseDerivative;
|
|
1115
|
+
if (rawPhase.c[0].im == phase.c[0].im) {
|
|
1116
|
+
phaseDerivative = phasePerUnit;
|
|
1117
|
+
} else {
|
|
1118
|
+
for (int i = 0; i < JET_N; i++)
|
|
1119
|
+
phaseDerivative.c[i] = cmk(phasePerUnit.c[i].re, 0.0);
|
|
1120
|
+
}
|
|
1121
|
+
jet sine, cosinePhase;
|
|
1122
|
+
jsincos(phase, &sine, &cosinePhase);
|
|
1123
|
+
jet sineDerivative = jmul(cosinePhase, phaseDerivative);
|
|
1124
|
+
jet cosineDerivative = jscale(jmul(sine, phaseDerivative), -1.0);
|
|
1125
|
+
jet eta = jadmittance(index, cosine, pol);
|
|
1126
|
+
jet minusI = jconst(0.0, -1.0);
|
|
1127
|
+
M->a = cosinePhase;
|
|
1128
|
+
M->b = jmul(minusI, jdiv(sine, eta));
|
|
1129
|
+
M->c = jmul(minusI, jmul(eta, sine));
|
|
1130
|
+
M->d = cosinePhase;
|
|
1131
|
+
dM->a = cosineDerivative;
|
|
1132
|
+
dM->b = jmul(minusI, jdiv(sineDerivative, eta));
|
|
1133
|
+
dM->c = jmul(minusI, jmul(eta, sineDerivative));
|
|
1134
|
+
dM->d = cosineDerivative;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
typedef struct { jet reflection, transmission, denominator; } jcoef;
|
|
1138
|
+
|
|
1139
|
+
static jcoef jcoef_from_matrix(jmat2 M, jet incidentEta, jet substrateEta, double logScale) {
|
|
1140
|
+
jet boundaryB = jadd(M.a, jmul(M.b, substrateEta));
|
|
1141
|
+
jet boundaryC = jadd(M.c, jmul(M.d, substrateEta));
|
|
1142
|
+
jet incidentB = jmul(incidentEta, boundaryB);
|
|
1143
|
+
jcoef o;
|
|
1144
|
+
o.denominator = jadd(incidentB, boundaryC);
|
|
1145
|
+
o.reflection = jdiv(jsub(incidentB, boundaryC), o.denominator);
|
|
1146
|
+
o.transmission = jdiv(jscale(incidentEta, 2.0), o.denominator);
|
|
1147
|
+
if (logScale != 0.0) o.transmission = jscale(o.transmission, exp(-logScale));
|
|
1148
|
+
return o;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
static void jcoef_thickness(jmat2 dMat, jcoef base, jet incidentEta, jet substrateEta,
|
|
1152
|
+
jet *dReflection, jet *dTransmission) {
|
|
1153
|
+
jet dB = jadd(dMat.a, jmul(dMat.b, substrateEta));
|
|
1154
|
+
jet dC = jadd(dMat.c, jmul(dMat.d, substrateEta));
|
|
1155
|
+
jet dIncidentB = jmul(incidentEta, dB);
|
|
1156
|
+
jet dDenominator = jadd(dIncidentB, dC);
|
|
1157
|
+
jet dNumerator = jsub(dIncidentB, dC);
|
|
1158
|
+
*dReflection = jdiv(jsub(dNumerator, jmul(base.reflection, dDenominator)), base.denominator);
|
|
1159
|
+
*dTransmission = jscale(jdiv(jmul(base.transmission, dDenominator), base.denominator), -1.0);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/* ── Phase quantities from a coefficient jet ─────────────────────────────────
|
|
1163
|
+
* Writes [phaseRad, GD, GDD, TOD, |coefficient|²]; all NaN where the
|
|
1164
|
+
* coefficient is exactly zero and the phase is undefined.
|
|
1165
|
+
*
|
|
1166
|
+
* GD = Im(r'/r)
|
|
1167
|
+
* GDD = Im(r''/r − (r'/r)²)
|
|
1168
|
+
* TOD = Im(r'''/r − 3 r'r''/r² + 2 (r'/r)³) Birge & Kärtner
|
|
1169
|
+
*
|
|
1170
|
+
* GD comes out in the reciprocal of the caller's ω unit, GDD in its square and
|
|
1171
|
+
* TOD in its cube. */
|
|
1172
|
+
|
|
1173
|
+
static void jphase(jet coefficient, double *out5) {
|
|
1174
|
+
cx d[4];
|
|
1175
|
+
jderivs(coefficient, d);
|
|
1176
|
+
cx value = d[0];
|
|
1177
|
+
double magnitudeSquared = value.re * value.re + value.im * value.im;
|
|
1178
|
+
if (magnitudeSquared == 0.0 || !isfinite(magnitudeSquared)) {
|
|
1179
|
+
for (int i = 0; i < 5; i++) out5[i] = NAN;
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
cx inverse = cdiv(cmk(1.0, 0.0), value);
|
|
1183
|
+
cx firstRatio = cmul(d[1], inverse);
|
|
1184
|
+
cx secondRatio = cmul(d[2], inverse);
|
|
1185
|
+
cx thirdRatio = cmul(d[3], inverse);
|
|
1186
|
+
cx squareFirst = cmk(firstRatio.re * firstRatio.re - firstRatio.im * firstRatio.im,
|
|
1187
|
+
2.0 * firstRatio.re * firstRatio.im);
|
|
1188
|
+
cx firstTimesSecond = cmk(
|
|
1189
|
+
firstRatio.re * secondRatio.re - firstRatio.im * secondRatio.im,
|
|
1190
|
+
firstRatio.re * secondRatio.im + firstRatio.im * secondRatio.re);
|
|
1191
|
+
cx cubeFirst = cmk(squareFirst.re * firstRatio.re - squareFirst.im * firstRatio.im,
|
|
1192
|
+
squareFirst.re * firstRatio.im + squareFirst.im * firstRatio.re);
|
|
1193
|
+
out5[0] = -atan2(value.im, value.re);
|
|
1194
|
+
out5[1] = firstRatio.im;
|
|
1195
|
+
out5[2] = secondRatio.im - squareFirst.im;
|
|
1196
|
+
out5[3] = thirdRatio.im - 3.0 * firstTimesSecond.im + 2.0 * cubeFirst.im;
|
|
1197
|
+
out5[4] = magnitudeSquared;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/* ── Phase core: one wavelength, both coefficients ───────────────────────────
|
|
1201
|
+
* `sinJet` is the incident-side sine as a jet, for a stack embedded in a
|
|
1202
|
+
* dispersive medium at a fixed external angle; NULL uses the constant
|
|
1203
|
+
* sin(theta_deg). out10 = [r: phaseRad, GD, GDD, TOD, |r|²][t: same]. */
|
|
1204
|
+
|
|
1205
|
+
static void jphase_core(double lambda, double omega, double theta_deg, int pol,
|
|
1206
|
+
jet n0, jet ns, const jet *layerN, const double *thick, int N,
|
|
1207
|
+
const jet *sinJet, double *out10) {
|
|
1208
|
+
jet wavelength = jwavelength(lambda, omega);
|
|
1209
|
+
jet incidentSine = sinJet ? *sinJet : jconst(sin(theta_deg * PI / 180.0), 0.0);
|
|
1210
|
+
jet incidentCosine = sinJet
|
|
1211
|
+
? jsqrt_j(jsub(jconst(1.0, 0.0), jmul(incidentSine, incidentSine)))
|
|
1212
|
+
: jconst(cos(theta_deg * PI / 180.0), 0.0);
|
|
1213
|
+
jet incidentEta = jadmittance(n0, incidentCosine, pol);
|
|
1214
|
+
jet substrateCosine = jsnell_cos(n0, incidentSine, ns);
|
|
1215
|
+
jet substrateEta = jadmittance(ns, substrateCosine, pol);
|
|
1216
|
+
|
|
1217
|
+
jmat2 M = jidentity();
|
|
1218
|
+
double logScale = 0.0;
|
|
1219
|
+
for (int k = 0; k < N; k++) {
|
|
1220
|
+
if (!(thick[k] > 0.0)) continue;
|
|
1221
|
+
jet cosine = jsnell_cos(n0, incidentSine, layerN[k]);
|
|
1222
|
+
M = jmatmul(M, jlayer_matrix(layerN[k], thick[k], wavelength, cosine, pol));
|
|
1223
|
+
logScale += jrescale(&M, MATRIX_RESCALE_THRESHOLD);
|
|
1224
|
+
}
|
|
1225
|
+
jcoef coefficients = jcoef_from_matrix(M, incidentEta, substrateEta, logScale);
|
|
1226
|
+
jphase(coefficients.reflection, &out10[0]);
|
|
1227
|
+
jphase(coefficients.transmission, &out10[5]);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/* ── Exported: phase dispersion at one wavelength ─────────────────────────────
|
|
1231
|
+
* Mirrors tmmPhaseDispersion() in phase.js. Index jets are 8 doubles each,
|
|
1232
|
+
* [re,im] per order. `sinJet` may be NULL. */
|
|
1233
|
+
|
|
1234
|
+
TMM_EXPORT("tmm_phase_one")
|
|
1235
|
+
void tmm_phase_one(double lambda, double omega, double theta_deg, int pol,
|
|
1236
|
+
const double *n0jet, const double *nsjet,
|
|
1237
|
+
const double *layerJets, const double *thick, int N,
|
|
1238
|
+
const double *sinJet, double *out) {
|
|
1239
|
+
jet *layerN = (jet *)malloc(sizeof(jet) * (N > 0 ? N : 1));
|
|
1240
|
+
for (int k = 0; k < N; k++) layerN[k] = jread(&layerJets[8 * k]);
|
|
1241
|
+
jet sine;
|
|
1242
|
+
if (sinJet) sine = jread(sinJet);
|
|
1243
|
+
jphase_core(lambda, omega, theta_deg, pol,
|
|
1244
|
+
jread(n0jet), jread(nsjet), layerN, thick, N,
|
|
1245
|
+
sinJet ? &sine : NULL, out);
|
|
1246
|
+
free(layerN);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/* ── Exported: batched phase dispersion over a wavelength grid ────────────────
|
|
1250
|
+
* One call evaluates the whole grid, amortizing the JS↔WASM boundary the same
|
|
1251
|
+
* way tmm_spectrum does. Polarization is an argument rather than both-at-once,
|
|
1252
|
+
* because this kernel is an order of magnitude dearer per sample than the plain
|
|
1253
|
+
* spectrum and callers at normal incidence would pay twice for nothing.
|
|
1254
|
+
*
|
|
1255
|
+
* Memory layout (all f64, caller-owned):
|
|
1256
|
+
* lambdas : nLam
|
|
1257
|
+
* omegas : nLam angular frequency per λ; sets the unit
|
|
1258
|
+
* n0jets : nLam × 8 incident-medium index jet per λ
|
|
1259
|
+
* nsjets : nLam × 8 substrate index jet per λ
|
|
1260
|
+
* matJets : N × nLam × 8 per-layer index jet, layout [layer][λ]
|
|
1261
|
+
* thick : N
|
|
1262
|
+
* sinJets : nLam × 8, or NULL
|
|
1263
|
+
* out : nLam × 10 [r: phaseRad,GD,GDD,TOD,|r|²][t: same] */
|
|
1264
|
+
|
|
1265
|
+
TMM_EXPORT("tmm_phase_spectrum")
|
|
1266
|
+
void tmm_phase_spectrum(const double *lambdas, const double *omegas, int nLam,
|
|
1267
|
+
const double *n0jets, const double *nsjets,
|
|
1268
|
+
const double *matJets, const double *thick, int N,
|
|
1269
|
+
double theta_deg, int pol,
|
|
1270
|
+
const double *sinJets, double *out) {
|
|
1271
|
+
jet *layerN = (jet *)malloc(sizeof(jet) * (N > 0 ? N : 1));
|
|
1272
|
+
for (int li = 0; li < nLam; li++) {
|
|
1273
|
+
for (int k = 0; k < N; k++) {
|
|
1274
|
+
long base = ((long)k * nLam + li) * 8;
|
|
1275
|
+
layerN[k] = jread(&matJets[base]);
|
|
1276
|
+
}
|
|
1277
|
+
jet sine;
|
|
1278
|
+
if (sinJets) sine = jread(&sinJets[8 * (long)li]);
|
|
1279
|
+
jphase_core(lambdas[li], omegas[li], theta_deg, pol,
|
|
1280
|
+
jread(&n0jets[8 * (long)li]), jread(&nsjets[8 * (long)li]),
|
|
1281
|
+
layerN, thick, N,
|
|
1282
|
+
sinJets ? &sine : NULL, &out[10 * (long)li]);
|
|
1283
|
+
}
|
|
1284
|
+
free(layerN);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/* ── Exported: phase dispersion plus exact thickness derivatives ──────────────
|
|
1288
|
+
* Mirrors tmmPhaseThicknessJacobian() in phase.js. Frequency stays the Taylor
|
|
1289
|
+
* variable, so each thickness derivative is itself a third-order frequency jet.
|
|
1290
|
+
* Zero-thickness layers are retained so derivative indices line up with the
|
|
1291
|
+
* caller's design array. Negative and NaN thicknesses are skipped with a zero
|
|
1292
|
+
* derivative, matching the point evaluator's base result.
|
|
1293
|
+
*
|
|
1294
|
+
* out : 10 as tmm_phase_one
|
|
1295
|
+
* deriv : 8 × N [side][quantity][layer], side 0 = r, 1 = t,
|
|
1296
|
+
* quantity 0 = dPhaseDeg, 1 = dGD, 2 = dGDD, 3 = dTOD
|
|
1297
|
+
*
|
|
1298
|
+
* The prefix/suffix decomposition cannot carry a rescaling, so if the matrix
|
|
1299
|
+
* product overflows, `out` is still filled from the plain path and every entry
|
|
1300
|
+
* of `deriv` is set to NaN. */
|
|
1301
|
+
|
|
1302
|
+
TMM_EXPORT("tmm_phase_jacobian")
|
|
1303
|
+
void tmm_phase_jacobian(double lambda, double omega, double theta_deg, int pol,
|
|
1304
|
+
const double *n0jet, const double *nsjet,
|
|
1305
|
+
const double *layerJets, const double *thick, int N,
|
|
1306
|
+
const double *sinJet, double *out, double *deriv) {
|
|
1307
|
+
jet n0 = jread(n0jet), ns = jread(nsjet);
|
|
1308
|
+
jet *layerN = (jet *)malloc(sizeof(jet) * (N > 0 ? N : 1));
|
|
1309
|
+
for (int k = 0; k < N; k++) layerN[k] = jread(&layerJets[8 * k]);
|
|
1310
|
+
jet sine;
|
|
1311
|
+
if (sinJet) sine = jread(sinJet);
|
|
1312
|
+
const jet *sinePtr = sinJet ? &sine : NULL;
|
|
1313
|
+
|
|
1314
|
+
jet wavelength = jwavelength(lambda, omega);
|
|
1315
|
+
jet incidentSine = sinePtr ? *sinePtr : jconst(sin(theta_deg * PI / 180.0), 0.0);
|
|
1316
|
+
jet incidentCosine = sinePtr
|
|
1317
|
+
? jsqrt_j(jsub(jconst(1.0, 0.0), jmul(incidentSine, incidentSine)))
|
|
1318
|
+
: jconst(cos(theta_deg * PI / 180.0), 0.0);
|
|
1319
|
+
jet incidentEta = jadmittance(n0, incidentCosine, pol);
|
|
1320
|
+
jet substrateCosine = jsnell_cos(n0, incidentSine, ns);
|
|
1321
|
+
jet substrateEta = jadmittance(ns, substrateCosine, pol);
|
|
1322
|
+
|
|
1323
|
+
int M = (N > 0 ? N : 1);
|
|
1324
|
+
jmat2 *layerM = (jmat2 *)malloc(sizeof(jmat2) * M);
|
|
1325
|
+
jmat2 *layerDM = (jmat2 *)malloc(sizeof(jmat2) * M);
|
|
1326
|
+
for (int k = 0; k < N; k++) {
|
|
1327
|
+
/* Match jphase_core's skip rule for invalid negative/NaN thicknesses,
|
|
1328
|
+
* while retaining the useful derivative of a zero-thickness layer. */
|
|
1329
|
+
if (!(thick[k] >= 0.0)) {
|
|
1330
|
+
layerM[k] = jidentity();
|
|
1331
|
+
layerDM[k] = jzero();
|
|
1332
|
+
continue;
|
|
1333
|
+
}
|
|
1334
|
+
jet cosine = jsnell_cos(n0, incidentSine, layerN[k]);
|
|
1335
|
+
jlayer_matrix_dd(layerN[k], thick[k], wavelength, cosine, pol,
|
|
1336
|
+
&layerM[k], &layerDM[k]);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
jmat2 *prefix = (jmat2 *)malloc(sizeof(jmat2) * (N + 1));
|
|
1340
|
+
jmat2 *suffix = (jmat2 *)malloc(sizeof(jmat2) * (N + 1));
|
|
1341
|
+
prefix[0] = jidentity();
|
|
1342
|
+
int overflowed = 0;
|
|
1343
|
+
for (int k = 0; k < N; k++) {
|
|
1344
|
+
prefix[k + 1] = jmatmul(prefix[k], layerM[k]);
|
|
1345
|
+
if (jmatmag(prefix[k + 1]) > MATRIX_RESCALE_THRESHOLD) { overflowed = 1; break; }
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (overflowed) {
|
|
1349
|
+
jphase_core(lambda, omega, theta_deg, pol, n0, ns, layerN, thick, N, sinePtr, out);
|
|
1350
|
+
for (long i = 0; i < 8L * N; i++) deriv[i] = NAN;
|
|
1351
|
+
free(layerN); free(layerM); free(layerDM); free(prefix); free(suffix);
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
suffix[N] = jidentity();
|
|
1356
|
+
for (int k = N - 1; k >= 0; k--) suffix[k] = jmatmul(layerM[k], suffix[k + 1]);
|
|
1357
|
+
|
|
1358
|
+
jcoef coefficients = jcoef_from_matrix(prefix[N], incidentEta, substrateEta, 0.0);
|
|
1359
|
+
jphase(coefficients.reflection, &out[0]);
|
|
1360
|
+
jphase(coefficients.transmission, &out[5]);
|
|
1361
|
+
|
|
1362
|
+
for (int k = 0; k < N; k++) {
|
|
1363
|
+
jmat2 matrixDerivative = jmatmul(jmatmul(prefix[k], layerDM[k]), suffix[k + 1]);
|
|
1364
|
+
jet dReflection, dTransmission;
|
|
1365
|
+
jcoef_thickness(matrixDerivative, coefficients, incidentEta, substrateEta,
|
|
1366
|
+
&dReflection, &dTransmission);
|
|
1367
|
+
const jet sides[2] = { dReflection, dTransmission };
|
|
1368
|
+
const jet base[2] = { coefficients.reflection, coefficients.transmission };
|
|
1369
|
+
for (int s = 0; s < 2; s++) {
|
|
1370
|
+
cx dd[4];
|
|
1371
|
+
jderivs(jdiv(sides[s], base[s]), dd);
|
|
1372
|
+
deriv[((long)s * 4 + 0) * N + k] = -dd[0].im * 180.0 / PI;
|
|
1373
|
+
deriv[((long)s * 4 + 1) * N + k] = dd[1].im;
|
|
1374
|
+
deriv[((long)s * 4 + 2) * N + k] = dd[2].im;
|
|
1375
|
+
deriv[((long)s * 4 + 3) * N + k] = dd[3].im;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
free(layerN); free(layerM); free(layerDM); free(prefix); free(suffix);
|
|
1380
|
+
}
|