tmmcore 0.1.0 → 0.2.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 +11 -5
- package/src/build.ps1 +4 -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 +207 -13
- package/src/tmm_kernel.c +468 -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));
|
|
@@ -242,7 +247,7 @@ void tmm_spectrum(const double *lambdas, int nLam,
|
|
|
242
247
|
|
|
243
248
|
/* ── Exported: analytic thickness Jacobian for one (λ, θ, pol) ────────────────
|
|
244
249
|
* 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
|
|
250
|
+
* analytic dR/dd_k, dT/dd_k, dA/dd_k for every layer at one sample : the DLS
|
|
246
251
|
* refiner's per-step gradient (2·N fewer evals than central differences).
|
|
247
252
|
*
|
|
248
253
|
* `layers` is N triples [n_re, n_im, d]; layers used AS-IS (no d>0 filter) for
|
|
@@ -317,7 +322,7 @@ void tmm_jacobian(double lambda_nm, double theta_deg, int pol,
|
|
|
317
322
|
vec2 dV = cmatvec(Pre[k], cmatvec(dMk, Post[k + 1]));
|
|
318
323
|
cx dB = dV.x, dC = dV.y;
|
|
319
324
|
|
|
320
|
-
/* metrics(dB,dC)
|
|
325
|
+
/* metrics(dB,dC) : verbatim from validated tmmNeedleScan.metrics */
|
|
321
326
|
cx dr = cmul(f, csub(cmul(Cv, dB), cmul(Bv, dC)));
|
|
322
327
|
double dR = 2.0 * (cmul(cconj(r), dr)).re;
|
|
323
328
|
cx dt = cmul(neg1, cmul(f, cadd(cmul(eta0, dB), dC)));
|
|
@@ -329,7 +334,7 @@ void tmm_jacobian(double lambda_nm, double theta_deg, int pol,
|
|
|
329
334
|
}
|
|
330
335
|
|
|
331
336
|
/* ── Analytic needle P-function scan ─────────────────────────────────────────
|
|
332
|
-
* Faithful port of tmmNeedleScan() in thinFilmMath.js
|
|
337
|
+
* Faithful port of tmmNeedleScan() in thinFilmMath.js : the d→0 limit of
|
|
333
338
|
* Sullivan's pre/post method (Tikhonravov's analytic P-function). Returns the
|
|
334
339
|
* merit-gradient ingredients {dR,dT,dA} of inserting an infinitesimal needle of
|
|
335
340
|
* each candidate index at every gap position (0..N) and, optionally, at intra-
|
|
@@ -342,7 +347,7 @@ void tmm_jacobian(double lambda_nm, double theta_deg, int pol,
|
|
|
342
347
|
* intra : N*nFrac*nCand*3 layout [layer][frac][cand][dR,dT,dA] (nFrac>0)
|
|
343
348
|
*/
|
|
344
349
|
|
|
345
|
-
/* {dR,dT,dA} from d[B,C]/dd
|
|
350
|
+
/* {dR,dT,dA} from d[B,C]/dd : verbatim from tmm_jacobian.metrics. */
|
|
346
351
|
static void needle_metrics(cx Bv, cx Cv, cx eta0, cx f, cx r, cx t, double Tfac,
|
|
347
352
|
cx dB, cx dC, double *o) {
|
|
348
353
|
cx dr = cmul(f, csub(cmul(Cv, dB), cmul(Bv, dC)));
|
|
@@ -450,7 +455,7 @@ void tmm_needle_scan(double lambda_nm, double theta_deg, int pol,
|
|
|
450
455
|
}
|
|
451
456
|
|
|
452
457
|
/* ── Analytic thickness-Hessian kernel ───────────
|
|
453
|
-
* LINE-BY-LINE port of tmmThicknessHessian() in thinFilmMath.js
|
|
458
|
+
* LINE-BY-LINE port of tmmThicknessHessian() in thinFilmMath.js : the EXACT
|
|
454
459
|
* analytic second derivatives ∂²{R,T,A}/∂dᵢ∂dⱼ (full N×N symmetric) plus the
|
|
455
460
|
* first derivatives, at one (λ,θ,pol). Used by the bounded-SQP / Newton inner
|
|
456
461
|
* refiner; the JS remains the oracle (tests/wasm_hessian_equivalence.mjs).
|
|
@@ -587,3 +592,459 @@ void tmm_hessian(double lambda_nm, double theta_deg, int pol,
|
|
|
587
592
|
free(cosThJ); free(Ms); free(Pre); free(Post);
|
|
588
593
|
free(dM); free(d2M); free(v); free(dBa); free(dCa);
|
|
589
594
|
}
|
|
595
|
+
|
|
596
|
+
/* ── Third-order Taylor jets ─────────────────────────────────────────────────
|
|
597
|
+
* A jet holds [f, f', f''/2!, f'''/3!], each entry complex. Ordinary power-
|
|
598
|
+
* series algebra on these differentiates a function exactly, with no finite
|
|
599
|
+
* differences. Port of taylorJet.js; where the JS multiplies by a reciprocal
|
|
600
|
+
* rather than dividing, so does this, since the two are not bit-identical. */
|
|
601
|
+
|
|
602
|
+
#define JET_N 4
|
|
603
|
+
|
|
604
|
+
typedef struct { cx c[JET_N]; } jet;
|
|
605
|
+
|
|
606
|
+
static inline jet jconst(double re, double im) {
|
|
607
|
+
jet j;
|
|
608
|
+
j.c[0] = cmk(re, im);
|
|
609
|
+
j.c[1] = cmk(0.0, 0.0); j.c[2] = cmk(0.0, 0.0); j.c[3] = cmk(0.0, 0.0);
|
|
610
|
+
return j;
|
|
611
|
+
}
|
|
612
|
+
static inline jet jread(const double *p) {
|
|
613
|
+
jet j;
|
|
614
|
+
for (int i = 0; i < JET_N; i++) j.c[i] = cmk(p[2 * i], p[2 * i + 1]);
|
|
615
|
+
return j;
|
|
616
|
+
}
|
|
617
|
+
static inline jet jadd(jet a, jet b) {
|
|
618
|
+
jet o; for (int i = 0; i < JET_N; i++) o.c[i] = cadd(a.c[i], b.c[i]); return o;
|
|
619
|
+
}
|
|
620
|
+
static inline jet jsub(jet a, jet b) {
|
|
621
|
+
jet o; for (int i = 0; i < JET_N; i++) o.c[i] = csub(a.c[i], b.c[i]); return o;
|
|
622
|
+
}
|
|
623
|
+
static inline jet jscale(jet a, double s) {
|
|
624
|
+
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;
|
|
625
|
+
}
|
|
626
|
+
static inline jet jmul(jet a, jet b) {
|
|
627
|
+
jet o;
|
|
628
|
+
for (int order = 0; order < JET_N; order++) {
|
|
629
|
+
cx sum = cmk(0.0, 0.0);
|
|
630
|
+
for (int i = 0; i <= order; i++) sum = cadd(sum, cmul(a.c[i], b.c[order - i]));
|
|
631
|
+
o.c[order] = sum;
|
|
632
|
+
}
|
|
633
|
+
return o;
|
|
634
|
+
}
|
|
635
|
+
static inline jet jrecip(jet a) {
|
|
636
|
+
jet o;
|
|
637
|
+
o.c[0] = cdiv(cmk(1.0, 0.0), a.c[0]);
|
|
638
|
+
for (int order = 1; order < JET_N; order++) {
|
|
639
|
+
cx sum = cmk(0.0, 0.0);
|
|
640
|
+
for (int i = 1; i <= order; i++) sum = cadd(sum, cmul(a.c[i], o.c[order - i]));
|
|
641
|
+
cx q = cdiv(sum, a.c[0]);
|
|
642
|
+
o.c[order] = cmk(-q.re, -q.im);
|
|
643
|
+
}
|
|
644
|
+
return o;
|
|
645
|
+
}
|
|
646
|
+
static inline jet jdiv(jet a, jet b) { return jmul(a, jrecip(b)); }
|
|
647
|
+
|
|
648
|
+
static inline jet jsqrt_j(jet a) {
|
|
649
|
+
jet o;
|
|
650
|
+
o.c[0] = csqrt_(a.c[0]);
|
|
651
|
+
cx twiceRoot = cmk(o.c[0].re * 2.0, o.c[0].im * 2.0);
|
|
652
|
+
for (int order = 1; order < JET_N; order++) {
|
|
653
|
+
cx known = cmk(0.0, 0.0);
|
|
654
|
+
for (int i = 1; i < order; i++) known = cadd(known, cmul(o.c[i], o.c[order - i]));
|
|
655
|
+
o.c[order] = cdiv(csub(a.c[order], known), twiceRoot);
|
|
656
|
+
}
|
|
657
|
+
return o;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
static void jsincos(jet a, jet *sine, jet *cosine) {
|
|
661
|
+
double re = a.c[0].re, im = a.c[0].im;
|
|
662
|
+
sine->c[0] = cmk(sin(re) * cosh(im), cos(re) * sinh(im));
|
|
663
|
+
cosine->c[0] = cmk(cos(re) * cosh(im), -sin(re) * sinh(im));
|
|
664
|
+
for (int order = 1; order < JET_N; order++) {
|
|
665
|
+
cx sineSum = cmk(0.0, 0.0), cosineSum = cmk(0.0, 0.0);
|
|
666
|
+
for (int i = 1; i <= order; i++) {
|
|
667
|
+
cx ts = cmul(a.c[i], cosine->c[order - i]);
|
|
668
|
+
cx tc = cmul(a.c[i], sine->c[order - i]);
|
|
669
|
+
sineSum = cadd(sineSum, cmk(ts.re * i, ts.im * i));
|
|
670
|
+
cosineSum = cadd(cosineSum, cmk(tc.re * i, tc.im * i));
|
|
671
|
+
}
|
|
672
|
+
double inv = 1.0 / (double)order;
|
|
673
|
+
sine->c[order] = cmk( sineSum.re * inv, sineSum.im * inv);
|
|
674
|
+
cosine->c[order] = cmk(-cosineSum.re * inv, -cosineSum.im * inv);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/* Past the limit the layer is opaque: the derivatives are zero to machine
|
|
679
|
+
* precision and dropping them keeps cosh from overflowing the whole product. */
|
|
680
|
+
static inline jet jclampim(jet a, double limit) {
|
|
681
|
+
if (a.c[0].im > limit || a.c[0].im < -limit) {
|
|
682
|
+
double held = (a.c[0].im > limit) ? limit : -limit;
|
|
683
|
+
jet o;
|
|
684
|
+
o.c[0] = cmk(a.c[0].re, held);
|
|
685
|
+
for (int i = 1; i < JET_N; i++) o.c[i] = cmk(a.c[i].re, 0.0);
|
|
686
|
+
return o;
|
|
687
|
+
}
|
|
688
|
+
return a;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/* [f, f', f'', f'''] from the stored [f, f', f''/2!, f'''/3!]. */
|
|
692
|
+
static inline void jderivs(jet a, cx *out) {
|
|
693
|
+
out[0] = a.c[0];
|
|
694
|
+
out[1] = a.c[1];
|
|
695
|
+
out[2] = cmk(a.c[2].re * 2.0, a.c[2].im * 2.0);
|
|
696
|
+
out[3] = cmk(a.c[3].re * 6.0, a.c[3].im * 6.0);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/* λ(ω) = 2πc/ω. Needs no value for c: with λ and ω given, λ' = −λ/ω. */
|
|
700
|
+
static inline jet jwavelength(double lambda, double omega) {
|
|
701
|
+
jet o;
|
|
702
|
+
o.c[0] = cmk(lambda, 0.0);
|
|
703
|
+
o.c[1] = cmk(-lambda / omega, 0.0);
|
|
704
|
+
o.c[2] = cmk(lambda / (omega * omega), 0.0);
|
|
705
|
+
o.c[3] = cmk(-lambda / (omega * omega * omega), 0.0);
|
|
706
|
+
return o;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/* ── Jet-valued 2×2 matrices ─────────────────────────────────────────────── */
|
|
710
|
+
|
|
711
|
+
typedef struct { jet a, b, c, d; } jmat2;
|
|
712
|
+
|
|
713
|
+
static jmat2 jmatmul(jmat2 A, jmat2 B) {
|
|
714
|
+
jmat2 M;
|
|
715
|
+
M.a = jadd(jmul(A.a, B.a), jmul(A.b, B.c));
|
|
716
|
+
M.b = jadd(jmul(A.a, B.b), jmul(A.b, B.d));
|
|
717
|
+
M.c = jadd(jmul(A.c, B.a), jmul(A.d, B.c));
|
|
718
|
+
M.d = jadd(jmul(A.c, B.b), jmul(A.d, B.d));
|
|
719
|
+
return M;
|
|
720
|
+
}
|
|
721
|
+
static jmat2 jidentity(void) {
|
|
722
|
+
jmat2 M;
|
|
723
|
+
M.a = jconst(1.0, 0.0); M.b = jconst(0.0, 0.0);
|
|
724
|
+
M.c = jconst(0.0, 0.0); M.d = jconst(1.0, 0.0);
|
|
725
|
+
return M;
|
|
726
|
+
}
|
|
727
|
+
static jmat2 jzero(void) {
|
|
728
|
+
jmat2 M;
|
|
729
|
+
M.a = jconst(0.0, 0.0); M.b = jconst(0.0, 0.0);
|
|
730
|
+
M.c = jconst(0.0, 0.0); M.d = jconst(0.0, 0.0);
|
|
731
|
+
return M;
|
|
732
|
+
}
|
|
733
|
+
/* The order-0 matrix controls overflow in the physical coefficient. Once
|
|
734
|
+
* selected, one plain scalar rescales every jet order and cancels from r. */
|
|
735
|
+
static double jrescale(jmat2 *M, double threshold) {
|
|
736
|
+
jet *e[4] = { &M->a, &M->b, &M->c, &M->d };
|
|
737
|
+
double scale = 0.0;
|
|
738
|
+
for (int i = 0; i < 4; i++) {
|
|
739
|
+
scale = fmax(scale, fabs(e[i]->c[0].re));
|
|
740
|
+
scale = fmax(scale, fabs(e[i]->c[0].im));
|
|
741
|
+
}
|
|
742
|
+
if (scale <= threshold) return 0.0;
|
|
743
|
+
double inverse = 1.0 / scale;
|
|
744
|
+
for (int i = 0; i < 4; i++) *e[i] = jscale(*e[i], inverse);
|
|
745
|
+
return log(scale);
|
|
746
|
+
}
|
|
747
|
+
static double jmatmag(jmat2 M) {
|
|
748
|
+
jet *e[4] = { &M.a, &M.b, &M.c, &M.d };
|
|
749
|
+
double magnitude = 0.0;
|
|
750
|
+
for (int i = 0; i < 4; i++)
|
|
751
|
+
for (int o = 0; o < JET_N; o++)
|
|
752
|
+
magnitude = fmax(magnitude, fmax(fabs(e[i]->c[o].re), fabs(e[i]->c[o].im)));
|
|
753
|
+
return magnitude;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
static inline jet jsnell_cos(jet n0, jet sin0, jet nj) {
|
|
757
|
+
jet s = jdiv(jmul(n0, sin0), nj);
|
|
758
|
+
return jsqrt_j(jsub(jconst(1.0, 0.0), jmul(s, s)));
|
|
759
|
+
}
|
|
760
|
+
static inline jet jadmittance(jet n, jet cosv, int pol) {
|
|
761
|
+
return (pol == 0) ? jmul(n, cosv) : jdiv(n, cosv);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
static jmat2 jlayer_matrix(jet index, double thickness, jet wavelength, jet cosine, int pol) {
|
|
765
|
+
jet phase = jclampim(jscale(jdiv(jmul(index, cosine), wavelength),
|
|
766
|
+
2.0 * PI * thickness), MAX_IM_DELTA);
|
|
767
|
+
jet sine, cosinePhase;
|
|
768
|
+
jsincos(phase, &sine, &cosinePhase);
|
|
769
|
+
jet eta = jadmittance(index, cosine, pol);
|
|
770
|
+
jet minusI = jconst(0.0, -1.0);
|
|
771
|
+
jmat2 M;
|
|
772
|
+
M.a = cosinePhase;
|
|
773
|
+
M.b = jmul(minusI, jdiv(sine, eta));
|
|
774
|
+
M.c = jmul(minusI, jmul(eta, sine));
|
|
775
|
+
M.d = cosinePhase;
|
|
776
|
+
return M;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
static void jlayer_matrix_dd(jet index, double thickness, jet wavelength, jet cosine, int pol,
|
|
780
|
+
jmat2 *M, jmat2 *dM) {
|
|
781
|
+
jet phasePerUnit = jscale(jdiv(jmul(index, cosine), wavelength), 2.0 * PI);
|
|
782
|
+
jet rawPhase = jscale(phasePerUnit, thickness);
|
|
783
|
+
jet phase = jclampim(rawPhase, MAX_IM_DELTA);
|
|
784
|
+
jet phaseDerivative;
|
|
785
|
+
if (rawPhase.c[0].im == phase.c[0].im) {
|
|
786
|
+
phaseDerivative = phasePerUnit;
|
|
787
|
+
} else {
|
|
788
|
+
for (int i = 0; i < JET_N; i++)
|
|
789
|
+
phaseDerivative.c[i] = cmk(phasePerUnit.c[i].re, 0.0);
|
|
790
|
+
}
|
|
791
|
+
jet sine, cosinePhase;
|
|
792
|
+
jsincos(phase, &sine, &cosinePhase);
|
|
793
|
+
jet sineDerivative = jmul(cosinePhase, phaseDerivative);
|
|
794
|
+
jet cosineDerivative = jscale(jmul(sine, phaseDerivative), -1.0);
|
|
795
|
+
jet eta = jadmittance(index, cosine, pol);
|
|
796
|
+
jet minusI = jconst(0.0, -1.0);
|
|
797
|
+
M->a = cosinePhase;
|
|
798
|
+
M->b = jmul(minusI, jdiv(sine, eta));
|
|
799
|
+
M->c = jmul(minusI, jmul(eta, sine));
|
|
800
|
+
M->d = cosinePhase;
|
|
801
|
+
dM->a = cosineDerivative;
|
|
802
|
+
dM->b = jmul(minusI, jdiv(sineDerivative, eta));
|
|
803
|
+
dM->c = jmul(minusI, jmul(eta, sineDerivative));
|
|
804
|
+
dM->d = cosineDerivative;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
typedef struct { jet reflection, transmission, denominator; } jcoef;
|
|
808
|
+
|
|
809
|
+
static jcoef jcoef_from_matrix(jmat2 M, jet incidentEta, jet substrateEta, double logScale) {
|
|
810
|
+
jet boundaryB = jadd(M.a, jmul(M.b, substrateEta));
|
|
811
|
+
jet boundaryC = jadd(M.c, jmul(M.d, substrateEta));
|
|
812
|
+
jet incidentB = jmul(incidentEta, boundaryB);
|
|
813
|
+
jcoef o;
|
|
814
|
+
o.denominator = jadd(incidentB, boundaryC);
|
|
815
|
+
o.reflection = jdiv(jsub(incidentB, boundaryC), o.denominator);
|
|
816
|
+
o.transmission = jdiv(jscale(incidentEta, 2.0), o.denominator);
|
|
817
|
+
if (logScale != 0.0) o.transmission = jscale(o.transmission, exp(-logScale));
|
|
818
|
+
return o;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
static void jcoef_thickness(jmat2 dMat, jcoef base, jet incidentEta, jet substrateEta,
|
|
822
|
+
jet *dReflection, jet *dTransmission) {
|
|
823
|
+
jet dB = jadd(dMat.a, jmul(dMat.b, substrateEta));
|
|
824
|
+
jet dC = jadd(dMat.c, jmul(dMat.d, substrateEta));
|
|
825
|
+
jet dIncidentB = jmul(incidentEta, dB);
|
|
826
|
+
jet dDenominator = jadd(dIncidentB, dC);
|
|
827
|
+
jet dNumerator = jsub(dIncidentB, dC);
|
|
828
|
+
*dReflection = jdiv(jsub(dNumerator, jmul(base.reflection, dDenominator)), base.denominator);
|
|
829
|
+
*dTransmission = jscale(jdiv(jmul(base.transmission, dDenominator), base.denominator), -1.0);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/* ── Phase quantities from a coefficient jet ─────────────────────────────────
|
|
833
|
+
* Writes [phaseRad, GD, GDD, TOD, |coefficient|²]; all NaN where the
|
|
834
|
+
* coefficient is exactly zero and the phase is undefined.
|
|
835
|
+
*
|
|
836
|
+
* GD = Im(r'/r)
|
|
837
|
+
* GDD = Im(r''/r − (r'/r)²)
|
|
838
|
+
* TOD = Im(r'''/r − 3 r'r''/r² + 2 (r'/r)³) Birge & Kärtner
|
|
839
|
+
*
|
|
840
|
+
* GD comes out in the reciprocal of the caller's ω unit, GDD in its square and
|
|
841
|
+
* TOD in its cube. */
|
|
842
|
+
|
|
843
|
+
static void jphase(jet coefficient, double *out5) {
|
|
844
|
+
cx d[4];
|
|
845
|
+
jderivs(coefficient, d);
|
|
846
|
+
cx value = d[0];
|
|
847
|
+
double magnitudeSquared = value.re * value.re + value.im * value.im;
|
|
848
|
+
if (magnitudeSquared == 0.0 || !isfinite(magnitudeSquared)) {
|
|
849
|
+
for (int i = 0; i < 5; i++) out5[i] = NAN;
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
cx inverse = cdiv(cmk(1.0, 0.0), value);
|
|
853
|
+
cx firstRatio = cmul(d[1], inverse);
|
|
854
|
+
cx secondRatio = cmul(d[2], inverse);
|
|
855
|
+
cx thirdRatio = cmul(d[3], inverse);
|
|
856
|
+
cx squareFirst = cmk(firstRatio.re * firstRatio.re - firstRatio.im * firstRatio.im,
|
|
857
|
+
2.0 * firstRatio.re * firstRatio.im);
|
|
858
|
+
cx firstTimesSecond = cmk(
|
|
859
|
+
firstRatio.re * secondRatio.re - firstRatio.im * secondRatio.im,
|
|
860
|
+
firstRatio.re * secondRatio.im + firstRatio.im * secondRatio.re);
|
|
861
|
+
cx cubeFirst = cmk(squareFirst.re * firstRatio.re - squareFirst.im * firstRatio.im,
|
|
862
|
+
squareFirst.re * firstRatio.im + squareFirst.im * firstRatio.re);
|
|
863
|
+
out5[0] = -atan2(value.im, value.re);
|
|
864
|
+
out5[1] = firstRatio.im;
|
|
865
|
+
out5[2] = secondRatio.im - squareFirst.im;
|
|
866
|
+
out5[3] = thirdRatio.im - 3.0 * firstTimesSecond.im + 2.0 * cubeFirst.im;
|
|
867
|
+
out5[4] = magnitudeSquared;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/* ── Phase core: one wavelength, both coefficients ───────────────────────────
|
|
871
|
+
* `sinJet` is the incident-side sine as a jet, for a stack embedded in a
|
|
872
|
+
* dispersive medium at a fixed external angle; NULL uses the constant
|
|
873
|
+
* sin(theta_deg). out10 = [r: phaseRad, GD, GDD, TOD, |r|²][t: same]. */
|
|
874
|
+
|
|
875
|
+
static void jphase_core(double lambda, double omega, double theta_deg, int pol,
|
|
876
|
+
jet n0, jet ns, const jet *layerN, const double *thick, int N,
|
|
877
|
+
const jet *sinJet, double *out10) {
|
|
878
|
+
jet wavelength = jwavelength(lambda, omega);
|
|
879
|
+
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);
|
|
883
|
+
jet incidentEta = jadmittance(n0, incidentCosine, pol);
|
|
884
|
+
jet substrateCosine = jsnell_cos(n0, incidentSine, ns);
|
|
885
|
+
jet substrateEta = jadmittance(ns, substrateCosine, pol);
|
|
886
|
+
|
|
887
|
+
jmat2 M = jidentity();
|
|
888
|
+
double logScale = 0.0;
|
|
889
|
+
for (int k = 0; k < N; k++) {
|
|
890
|
+
if (!(thick[k] > 0.0)) continue;
|
|
891
|
+
jet cosine = jsnell_cos(n0, incidentSine, layerN[k]);
|
|
892
|
+
M = jmatmul(M, jlayer_matrix(layerN[k], thick[k], wavelength, cosine, pol));
|
|
893
|
+
logScale += jrescale(&M, MATRIX_RESCALE_THRESHOLD);
|
|
894
|
+
}
|
|
895
|
+
jcoef coefficients = jcoef_from_matrix(M, incidentEta, substrateEta, logScale);
|
|
896
|
+
jphase(coefficients.reflection, &out10[0]);
|
|
897
|
+
jphase(coefficients.transmission, &out10[5]);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/* ── Exported: phase dispersion at one wavelength ─────────────────────────────
|
|
901
|
+
* Mirrors tmmPhaseDispersion() in phase.js. Index jets are 8 doubles each,
|
|
902
|
+
* [re,im] per order. `sinJet` may be NULL. */
|
|
903
|
+
|
|
904
|
+
TMM_EXPORT("tmm_phase_one")
|
|
905
|
+
void tmm_phase_one(double lambda, double omega, double theta_deg, int pol,
|
|
906
|
+
const double *n0jet, const double *nsjet,
|
|
907
|
+
const double *layerJets, const double *thick, int N,
|
|
908
|
+
const double *sinJet, double *out) {
|
|
909
|
+
jet *layerN = (jet *)malloc(sizeof(jet) * (N > 0 ? N : 1));
|
|
910
|
+
for (int k = 0; k < N; k++) layerN[k] = jread(&layerJets[8 * k]);
|
|
911
|
+
jet sine;
|
|
912
|
+
if (sinJet) sine = jread(sinJet);
|
|
913
|
+
jphase_core(lambda, omega, theta_deg, pol,
|
|
914
|
+
jread(n0jet), jread(nsjet), layerN, thick, N,
|
|
915
|
+
sinJet ? &sine : NULL, out);
|
|
916
|
+
free(layerN);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/* ── Exported: batched phase dispersion over a wavelength grid ────────────────
|
|
920
|
+
* One call evaluates the whole grid, amortizing the JS↔WASM boundary the same
|
|
921
|
+
* way tmm_spectrum does. Polarization is an argument rather than both-at-once,
|
|
922
|
+
* because this kernel is an order of magnitude dearer per sample than the plain
|
|
923
|
+
* spectrum and callers at normal incidence would pay twice for nothing.
|
|
924
|
+
*
|
|
925
|
+
* Memory layout (all f64, caller-owned):
|
|
926
|
+
* lambdas : nLam
|
|
927
|
+
* omegas : nLam angular frequency per λ; sets the unit
|
|
928
|
+
* n0jets : nLam × 8 incident-medium index jet per λ
|
|
929
|
+
* nsjets : nLam × 8 substrate index jet per λ
|
|
930
|
+
* matJets : N × nLam × 8 per-layer index jet, layout [layer][λ]
|
|
931
|
+
* thick : N
|
|
932
|
+
* sinJets : nLam × 8, or NULL
|
|
933
|
+
* out : nLam × 10 [r: phaseRad,GD,GDD,TOD,|r|²][t: same] */
|
|
934
|
+
|
|
935
|
+
TMM_EXPORT("tmm_phase_spectrum")
|
|
936
|
+
void tmm_phase_spectrum(const double *lambdas, const double *omegas, int nLam,
|
|
937
|
+
const double *n0jets, const double *nsjets,
|
|
938
|
+
const double *matJets, const double *thick, int N,
|
|
939
|
+
double theta_deg, int pol,
|
|
940
|
+
const double *sinJets, double *out) {
|
|
941
|
+
jet *layerN = (jet *)malloc(sizeof(jet) * (N > 0 ? N : 1));
|
|
942
|
+
for (int li = 0; li < nLam; li++) {
|
|
943
|
+
for (int k = 0; k < N; k++) {
|
|
944
|
+
long base = ((long)k * nLam + li) * 8;
|
|
945
|
+
layerN[k] = jread(&matJets[base]);
|
|
946
|
+
}
|
|
947
|
+
jet sine;
|
|
948
|
+
if (sinJets) sine = jread(&sinJets[8 * (long)li]);
|
|
949
|
+
jphase_core(lambdas[li], omegas[li], theta_deg, pol,
|
|
950
|
+
jread(&n0jets[8 * (long)li]), jread(&nsjets[8 * (long)li]),
|
|
951
|
+
layerN, thick, N,
|
|
952
|
+
sinJets ? &sine : NULL, &out[10 * (long)li]);
|
|
953
|
+
}
|
|
954
|
+
free(layerN);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/* ── Exported: phase dispersion plus exact thickness derivatives ──────────────
|
|
958
|
+
* Mirrors tmmPhaseThicknessJacobian() in phase.js. Frequency stays the Taylor
|
|
959
|
+
* variable, so each thickness derivative is itself a third-order frequency jet.
|
|
960
|
+
* Zero-thickness layers are retained so derivative indices line up with the
|
|
961
|
+
* caller's design array. Negative and NaN thicknesses are skipped with a zero
|
|
962
|
+
* derivative, matching the point evaluator's base result.
|
|
963
|
+
*
|
|
964
|
+
* out : 10 as tmm_phase_one
|
|
965
|
+
* deriv : 8 × N [side][quantity][layer], side 0 = r, 1 = t,
|
|
966
|
+
* quantity 0 = dPhaseDeg, 1 = dGD, 2 = dGDD, 3 = dTOD
|
|
967
|
+
*
|
|
968
|
+
* The prefix/suffix decomposition cannot carry a rescaling, so if the matrix
|
|
969
|
+
* product overflows, `out` is still filled from the plain path and every entry
|
|
970
|
+
* of `deriv` is set to NaN. */
|
|
971
|
+
|
|
972
|
+
TMM_EXPORT("tmm_phase_jacobian")
|
|
973
|
+
void tmm_phase_jacobian(double lambda, double omega, double theta_deg, int pol,
|
|
974
|
+
const double *n0jet, const double *nsjet,
|
|
975
|
+
const double *layerJets, const double *thick, int N,
|
|
976
|
+
const double *sinJet, double *out, double *deriv) {
|
|
977
|
+
jet n0 = jread(n0jet), ns = jread(nsjet);
|
|
978
|
+
jet *layerN = (jet *)malloc(sizeof(jet) * (N > 0 ? N : 1));
|
|
979
|
+
for (int k = 0; k < N; k++) layerN[k] = jread(&layerJets[8 * k]);
|
|
980
|
+
jet sine;
|
|
981
|
+
if (sinJet) sine = jread(sinJet);
|
|
982
|
+
const jet *sinePtr = sinJet ? &sine : NULL;
|
|
983
|
+
|
|
984
|
+
jet wavelength = jwavelength(lambda, omega);
|
|
985
|
+
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);
|
|
989
|
+
jet incidentEta = jadmittance(n0, incidentCosine, pol);
|
|
990
|
+
jet substrateCosine = jsnell_cos(n0, incidentSine, ns);
|
|
991
|
+
jet substrateEta = jadmittance(ns, substrateCosine, pol);
|
|
992
|
+
|
|
993
|
+
int M = (N > 0 ? N : 1);
|
|
994
|
+
jmat2 *layerM = (jmat2 *)malloc(sizeof(jmat2) * M);
|
|
995
|
+
jmat2 *layerDM = (jmat2 *)malloc(sizeof(jmat2) * M);
|
|
996
|
+
for (int k = 0; k < N; k++) {
|
|
997
|
+
/* Match jphase_core's skip rule for invalid negative/NaN thicknesses,
|
|
998
|
+
* while retaining the useful derivative of a zero-thickness layer. */
|
|
999
|
+
if (!(thick[k] >= 0.0)) {
|
|
1000
|
+
layerM[k] = jidentity();
|
|
1001
|
+
layerDM[k] = jzero();
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
jet cosine = jsnell_cos(n0, incidentSine, layerN[k]);
|
|
1005
|
+
jlayer_matrix_dd(layerN[k], thick[k], wavelength, cosine, pol,
|
|
1006
|
+
&layerM[k], &layerDM[k]);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
jmat2 *prefix = (jmat2 *)malloc(sizeof(jmat2) * (N + 1));
|
|
1010
|
+
jmat2 *suffix = (jmat2 *)malloc(sizeof(jmat2) * (N + 1));
|
|
1011
|
+
prefix[0] = jidentity();
|
|
1012
|
+
int overflowed = 0;
|
|
1013
|
+
for (int k = 0; k < N; k++) {
|
|
1014
|
+
prefix[k + 1] = jmatmul(prefix[k], layerM[k]);
|
|
1015
|
+
if (jmatmag(prefix[k + 1]) > MATRIX_RESCALE_THRESHOLD) { overflowed = 1; break; }
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
if (overflowed) {
|
|
1019
|
+
jphase_core(lambda, omega, theta_deg, pol, n0, ns, layerN, thick, N, sinePtr, out);
|
|
1020
|
+
for (long i = 0; i < 8L * N; i++) deriv[i] = NAN;
|
|
1021
|
+
free(layerN); free(layerM); free(layerDM); free(prefix); free(suffix);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
suffix[N] = jidentity();
|
|
1026
|
+
for (int k = N - 1; k >= 0; k--) suffix[k] = jmatmul(layerM[k], suffix[k + 1]);
|
|
1027
|
+
|
|
1028
|
+
jcoef coefficients = jcoef_from_matrix(prefix[N], incidentEta, substrateEta, 0.0);
|
|
1029
|
+
jphase(coefficients.reflection, &out[0]);
|
|
1030
|
+
jphase(coefficients.transmission, &out[5]);
|
|
1031
|
+
|
|
1032
|
+
for (int k = 0; k < N; k++) {
|
|
1033
|
+
jmat2 matrixDerivative = jmatmul(jmatmul(prefix[k], layerDM[k]), suffix[k + 1]);
|
|
1034
|
+
jet dReflection, dTransmission;
|
|
1035
|
+
jcoef_thickness(matrixDerivative, coefficients, incidentEta, substrateEta,
|
|
1036
|
+
&dReflection, &dTransmission);
|
|
1037
|
+
const jet sides[2] = { dReflection, dTransmission };
|
|
1038
|
+
const jet base[2] = { coefficients.reflection, coefficients.transmission };
|
|
1039
|
+
for (int s = 0; s < 2; s++) {
|
|
1040
|
+
cx dd[4];
|
|
1041
|
+
jderivs(jdiv(sides[s], base[s]), dd);
|
|
1042
|
+
deriv[((long)s * 4 + 0) * N + k] = -dd[0].im * 180.0 / PI;
|
|
1043
|
+
deriv[((long)s * 4 + 1) * N + k] = dd[1].im;
|
|
1044
|
+
deriv[((long)s * 4 + 2) * N + k] = dd[2].im;
|
|
1045
|
+
deriv[((long)s * 4 + 3) * N + k] = dd[3].im;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
free(layerN); free(layerM); free(layerDM); free(prefix); free(suffix);
|
|
1050
|
+
}
|
package/src/tmm_kernel.wasm
CHANGED
|
Binary file
|