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/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 = csqrt_(csub(cmk(1.0, 0.0), cmul(sinTheta0, sinTheta0)));
164
+ cx cosTheta0 = incidentCosTheta(n0, sinTheta0);
153
165
 
154
166
  cx eta0 = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
155
167
 
@@ -314,7 +326,7 @@ void tmm_monitor_curve(double lambda_nm, double theta_deg,
314
326
  cx ns = cmk(ns_re, ns_im);
315
327
  cx ng = cmk(ng_re, ng_im);
316
328
  cx sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
317
- cx cosTheta0 = csqrt_(csub(cmk(1.0, 0.0), cmul(sinTheta0, sinTheta0)));
329
+ cx cosTheta0 = incidentCosTheta(n0, sinTheta0);
318
330
 
319
331
  for (int pol = 0; pol < 2; pol++) {
320
332
  cx eta0 = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
@@ -392,11 +404,11 @@ int tmm_deposition_spectra(const double *lambdas, int nLam,
392
404
  }
393
405
 
394
406
  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
407
 
397
408
  for (int li = 0; li < nLam; li++) {
398
409
  cx n0 = cmk(n0arr[2 * li + 0], n0arr[2 * li + 1]);
399
410
  cx ns = cmk(nsarr[2 * li + 0], nsarr[2 * li + 1]);
411
+ cx cosTheta0 = incidentCosTheta(n0, sinTheta0);
400
412
  for (int pol = 0; pol < 2; pol++) {
401
413
  size_t at = (size_t)pol * nLam + li;
402
414
  eta0v[at] = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
@@ -503,13 +515,13 @@ growing_eval *tmm_growing_eval_create(const double *lambdas, int nLam,
503
515
  }
504
516
 
505
517
  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
518
 
508
519
  for (int li = 0; li < nLam; li++) {
509
520
  h->lam[li] = lambdas[li];
510
521
  cx n0 = cmk(n0arr[2 * li + 0], n0arr[2 * li + 1]);
511
522
  cx ns = cmk(nsarr[2 * li + 0], nsarr[2 * li + 1]);
512
523
  h->n0[li] = n0;
524
+ cx cosTheta0 = incidentCosTheta(n0, h->sinTheta0);
513
525
  for (int pol = 0; pol < 2; pol++) {
514
526
  size_t at = (size_t)pol * nLam + li;
515
527
  h->eta0[at] = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
@@ -593,7 +605,7 @@ void tmm_jacobian(double lambda_nm, double theta_deg, int pol,
593
605
  cx ns = cmk(ns_re, ns_im);
594
606
 
595
607
  cx sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
596
- cx cosTheta0 = csqrt_(csub(cmk(1.0, 0.0), cmul(sinTheta0, sinTheta0)));
608
+ cx cosTheta0 = incidentCosTheta(n0, sinTheta0);
597
609
  cx eta0 = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
598
610
  cx cosThetaS = snellCosTheta(n0, sinTheta0, ns);
599
611
  cx etaS = (pol == 0) ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
@@ -696,7 +708,7 @@ void tmm_needle_scan(double lambda_nm, double theta_deg, int pol,
696
708
  double *base, double *gaps, double *intra) {
697
709
  cx n0 = cmk(n0_re, n0_im), ns = cmk(ns_re, ns_im);
698
710
  cx sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
699
- cx cosTheta0 = csqrt_(csub(cmk(1.0, 0.0), cmul(sinTheta0, sinTheta0)));
711
+ cx cosTheta0 = incidentCosTheta(n0, sinTheta0);
700
712
  cx eta0 = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
701
713
  cx cosThetaS = snellCosTheta(n0, sinTheta0, ns);
702
714
  cx etaS = (pol == 0) ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
@@ -805,7 +817,7 @@ void tmm_hessian(double lambda_nm, double theta_deg, int pol,
805
817
  cx ns = cmk(ns_re, ns_im);
806
818
 
807
819
  cx sinTheta0 = cmk(sin(theta_deg * PI / 180.0), 0.0);
808
- cx cosTheta0 = csqrt_(csub(cmk(1.0, 0.0), cmul(sinTheta0, sinTheta0)));
820
+ cx cosTheta0 = incidentCosTheta(n0, sinTheta0);
809
821
  cx eta0 = (pol == 0) ? cmul(n0, cosTheta0) : cdiv(n0, cosTheta0);
810
822
  cx cosThetaS = snellCosTheta(n0, sinTheta0, ns);
811
823
  cx etaS = (pol == 0) ? cmul(ns, cosThetaS) : cdiv(ns, cosThetaS);
@@ -1083,10 +1095,32 @@ static double jmatmag(jmat2 M) {
1083
1095
  return magnitude;
1084
1096
  }
1085
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. */
1086
1110
  static inline jet jsnell_cos(jet n0, jet sin0, jet nj) {
1087
- jet s = jdiv(jmul(n0, sin0), nj);
1111
+ jet s = jdiv(jmul(jreal(n0), sin0), nj);
1088
1112
  return jsqrt_j(jsub(jconst(1.0, 0.0), jmul(s, s)));
1089
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
+ }
1090
1124
  static inline jet jadmittance(jet n, jet cosv, int pol) {
1091
1125
  return (pol == 0) ? jmul(n, cosv) : jdiv(n, cosv);
1092
1126
  }
@@ -1207,9 +1241,7 @@ static void jphase_core(double lambda, double omega, double theta_deg, int pol,
1207
1241
  const jet *sinJet, double *out10) {
1208
1242
  jet wavelength = jwavelength(lambda, omega);
1209
1243
  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);
1244
+ jet incidentCosine = jincident_cos(n0, incidentSine, sinJet != NULL, theta_deg);
1213
1245
  jet incidentEta = jadmittance(n0, incidentCosine, pol);
1214
1246
  jet substrateCosine = jsnell_cos(n0, incidentSine, ns);
1215
1247
  jet substrateEta = jadmittance(ns, substrateCosine, pol);
@@ -1292,37 +1324,54 @@ void tmm_phase_spectrum(const double *lambdas, const double *omegas, int nLam,
1292
1324
  * derivative, matching the point evaluator's base result.
1293
1325
  *
1294
1326
  * 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
1327
+ * deriv : 10 × N [side][quantity][layer], side 0 = r, 1 = t,
1328
+ * quantity 0 = dPhaseDeg, 1 = dGD, 2 = dGDD, 3 = dTOD,
1329
+ * 4 = d(ln |coefficient|²)/dd, the relative intensity
1330
+ * derivative, which the amplitude ratio in ellipsometry
1331
+ * needs and which costs nothing here: it is the real part
1332
+ * of the same logarithmic derivative the phase is the
1333
+ * imaginary part of
1297
1334
  *
1298
1335
  * The prefix/suffix decomposition cannot carry a rescaling, so if the matrix
1299
1336
  * product overflows, `out` is still filled from the plain path and every entry
1300
1337
  * of `deriv` is set to NaN. */
1301
1338
 
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;
1339
+ /* Scratch for one wavelength of the phase Jacobian: per-layer matrices with
1340
+ * their thickness derivatives, and the prefix/suffix products. Allocated once
1341
+ * per call by the entry points below, so the batched one reuses it across the
1342
+ * whole grid. */
1343
+ typedef struct {
1344
+ jmat2 *layerM, *layerDM, *prefix, *suffix;
1345
+ } jphase_jacobian_scratch;
1346
+
1347
+ static jphase_jacobian_scratch jphase_jacobian_alloc(int N) {
1348
+ int M = (N > 0 ? N : 1);
1349
+ jphase_jacobian_scratch s;
1350
+ s.layerM = (jmat2 *)malloc(sizeof(jmat2) * M);
1351
+ s.layerDM = (jmat2 *)malloc(sizeof(jmat2) * M);
1352
+ s.prefix = (jmat2 *)malloc(sizeof(jmat2) * (N + 1));
1353
+ s.suffix = (jmat2 *)malloc(sizeof(jmat2) * (N + 1));
1354
+ return s;
1355
+ }
1313
1356
 
1357
+ static void jphase_jacobian_free(jphase_jacobian_scratch s) {
1358
+ free(s.layerM); free(s.layerDM); free(s.prefix); free(s.suffix);
1359
+ }
1360
+
1361
+ /* One wavelength of the phase Jacobian; see tmm_phase_jacobian for the layout
1362
+ * of `out` and `deriv`. */
1363
+ static void jphase_jacobian_core(double lambda, double omega, double theta_deg, int pol,
1364
+ jet n0, jet ns, const jet *layerN, const double *thick, int N,
1365
+ const jet *sinePtr, double *out, double *deriv,
1366
+ jphase_jacobian_scratch s) {
1367
+ jmat2 *layerM = s.layerM, *layerDM = s.layerDM, *prefix = s.prefix, *suffix = s.suffix;
1314
1368
  jet wavelength = jwavelength(lambda, omega);
1315
1369
  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);
1370
+ jet incidentCosine = jincident_cos(n0, incidentSine, sinePtr != NULL, theta_deg);
1319
1371
  jet incidentEta = jadmittance(n0, incidentCosine, pol);
1320
1372
  jet substrateCosine = jsnell_cos(n0, incidentSine, ns);
1321
1373
  jet substrateEta = jadmittance(ns, substrateCosine, pol);
1322
1374
 
1323
- int M = (N > 0 ? N : 1);
1324
- jmat2 *layerM = (jmat2 *)malloc(sizeof(jmat2) * M);
1325
- jmat2 *layerDM = (jmat2 *)malloc(sizeof(jmat2) * M);
1326
1375
  for (int k = 0; k < N; k++) {
1327
1376
  /* Match jphase_core's skip rule for invalid negative/NaN thicknesses,
1328
1377
  * while retaining the useful derivative of a zero-thickness layer. */
@@ -1336,8 +1385,6 @@ void tmm_phase_jacobian(double lambda, double omega, double theta_deg, int pol,
1336
1385
  &layerM[k], &layerDM[k]);
1337
1386
  }
1338
1387
 
1339
- jmat2 *prefix = (jmat2 *)malloc(sizeof(jmat2) * (N + 1));
1340
- jmat2 *suffix = (jmat2 *)malloc(sizeof(jmat2) * (N + 1));
1341
1388
  prefix[0] = jidentity();
1342
1389
  int overflowed = 0;
1343
1390
  for (int k = 0; k < N; k++) {
@@ -1347,8 +1394,7 @@ void tmm_phase_jacobian(double lambda, double omega, double theta_deg, int pol,
1347
1394
 
1348
1395
  if (overflowed) {
1349
1396
  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);
1397
+ for (long i = 0; i < 10L * N; i++) deriv[i] = NAN;
1352
1398
  return;
1353
1399
  }
1354
1400
 
@@ -1366,15 +1412,69 @@ void tmm_phase_jacobian(double lambda, double omega, double theta_deg, int pol,
1366
1412
  &dReflection, &dTransmission);
1367
1413
  const jet sides[2] = { dReflection, dTransmission };
1368
1414
  const jet base[2] = { coefficients.reflection, coefficients.transmission };
1369
- for (int s = 0; s < 2; s++) {
1415
+ for (int s2 = 0; s2 < 2; s2++) {
1370
1416
  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;
1417
+ /* d(ln coefficient)/dd: the imaginary part is the phase derivative
1418
+ * (negated for Macleod's sign), the real part is d(ln |c|)/dd, so
1419
+ * twice it is the relative derivative of |c|². */
1420
+ jderivs(jdiv(sides[s2], base[s2]), dd);
1421
+ deriv[((long)s2 * 5 + 0) * N + k] = -dd[0].im * 180.0 / PI;
1422
+ deriv[((long)s2 * 5 + 1) * N + k] = dd[1].im;
1423
+ deriv[((long)s2 * 5 + 2) * N + k] = dd[2].im;
1424
+ deriv[((long)s2 * 5 + 3) * N + k] = dd[3].im;
1425
+ deriv[((long)s2 * 5 + 4) * N + k] = 2.0 * dd[0].re;
1376
1426
  }
1377
1427
  }
1428
+ }
1378
1429
 
1379
- free(layerN); free(layerM); free(layerDM); free(prefix); free(suffix);
1430
+ TMM_EXPORT("tmm_phase_jacobian")
1431
+ void tmm_phase_jacobian(double lambda, double omega, double theta_deg, int pol,
1432
+ const double *n0jet, const double *nsjet,
1433
+ const double *layerJets, const double *thick, int N,
1434
+ const double *sinJet, double *out, double *deriv) {
1435
+ jet *layerN = (jet *)malloc(sizeof(jet) * (N > 0 ? N : 1));
1436
+ for (int k = 0; k < N; k++) layerN[k] = jread(&layerJets[8 * k]);
1437
+ jet sine;
1438
+ if (sinJet) sine = jread(sinJet);
1439
+ jphase_jacobian_scratch scratch = jphase_jacobian_alloc(N);
1440
+ jphase_jacobian_core(lambda, omega, theta_deg, pol, jread(n0jet), jread(nsjet),
1441
+ layerN, thick, N, sinJet ? &sine : NULL, out, deriv, scratch);
1442
+ jphase_jacobian_free(scratch);
1443
+ free(layerN);
1444
+ }
1445
+
1446
+ /* ── Exported: batched phase Jacobian over a wavelength grid ─────────────────
1447
+ * tmm_phase_jacobian at every wavelength of a grid in one call, for a caller
1448
+ * fitting a whole measured spectrum of phase-derived quantities: ellipsometric
1449
+ * Ψ and Δ, or group delay. Inputs are laid out as for tmm_phase_spectrum.
1450
+ *
1451
+ * out : nLam × 10 as tmm_phase_spectrum
1452
+ * deriv : nLam × 10 × N per λ, the 10 × N block of tmm_phase_jacobian
1453
+ *
1454
+ * A wavelength whose matrix product overflows has its `out` filled from the
1455
+ * plain path and its whole `deriv` block set to NaN, as the single-wavelength
1456
+ * entry point does. */
1457
+
1458
+ TMM_EXPORT("tmm_phase_jacobian_spectrum")
1459
+ void tmm_phase_jacobian_spectrum(const double *lambdas, const double *omegas, int nLam,
1460
+ const double *n0jets, const double *nsjets,
1461
+ const double *matJets, const double *thick, int N,
1462
+ double theta_deg, int pol,
1463
+ const double *sinJets, double *out, double *deriv) {
1464
+ jet *layerN = (jet *)malloc(sizeof(jet) * (N > 0 ? N : 1));
1465
+ jphase_jacobian_scratch scratch = jphase_jacobian_alloc(N);
1466
+ for (int li = 0; li < nLam; li++) {
1467
+ for (int k = 0; k < N; k++) {
1468
+ long base = ((long)k * nLam + li) * 8;
1469
+ layerN[k] = jread(&matJets[base]);
1470
+ }
1471
+ jet sine;
1472
+ if (sinJets) sine = jread(&sinJets[8 * (long)li]);
1473
+ jphase_jacobian_core(lambdas[li], omegas[li], theta_deg, pol,
1474
+ jread(&n0jets[8 * (long)li]), jread(&nsjets[8 * (long)li]),
1475
+ layerN, thick, N, sinJets ? &sine : NULL,
1476
+ &out[10 * (long)li], &deriv[10L * N * li], scratch);
1477
+ }
1478
+ jphase_jacobian_free(scratch);
1479
+ free(layerN);
1380
1480
  }
Binary file