tmmcore 0.3.1 → 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
@@ -1324,25 +1324,47 @@ void tmm_phase_spectrum(const double *lambdas, const double *omegas, int nLam,
1324
1324
  * derivative, matching the point evaluator's base result.
1325
1325
  *
1326
1326
  * out : 10 as tmm_phase_one
1327
- * deriv : 8 × N [side][quantity][layer], side 0 = r, 1 = t,
1328
- * 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
1329
1334
  *
1330
1335
  * The prefix/suffix decomposition cannot carry a rescaling, so if the matrix
1331
1336
  * product overflows, `out` is still filled from the plain path and every entry
1332
1337
  * of `deriv` is set to NaN. */
1333
1338
 
1334
- TMM_EXPORT("tmm_phase_jacobian")
1335
- void tmm_phase_jacobian(double lambda, double omega, double theta_deg, int pol,
1336
- const double *n0jet, const double *nsjet,
1337
- const double *layerJets, const double *thick, int N,
1338
- const double *sinJet, double *out, double *deriv) {
1339
- jet n0 = jread(n0jet), ns = jread(nsjet);
1340
- jet *layerN = (jet *)malloc(sizeof(jet) * (N > 0 ? N : 1));
1341
- for (int k = 0; k < N; k++) layerN[k] = jread(&layerJets[8 * k]);
1342
- jet sine;
1343
- if (sinJet) sine = jread(sinJet);
1344
- 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;
1345
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
+ }
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;
1346
1368
  jet wavelength = jwavelength(lambda, omega);
1347
1369
  jet incidentSine = sinePtr ? *sinePtr : jconst(sin(theta_deg * PI / 180.0), 0.0);
1348
1370
  jet incidentCosine = jincident_cos(n0, incidentSine, sinePtr != NULL, theta_deg);
@@ -1350,9 +1372,6 @@ void tmm_phase_jacobian(double lambda, double omega, double theta_deg, int pol,
1350
1372
  jet substrateCosine = jsnell_cos(n0, incidentSine, ns);
1351
1373
  jet substrateEta = jadmittance(ns, substrateCosine, pol);
1352
1374
 
1353
- int M = (N > 0 ? N : 1);
1354
- jmat2 *layerM = (jmat2 *)malloc(sizeof(jmat2) * M);
1355
- jmat2 *layerDM = (jmat2 *)malloc(sizeof(jmat2) * M);
1356
1375
  for (int k = 0; k < N; k++) {
1357
1376
  /* Match jphase_core's skip rule for invalid negative/NaN thicknesses,
1358
1377
  * while retaining the useful derivative of a zero-thickness layer. */
@@ -1366,8 +1385,6 @@ void tmm_phase_jacobian(double lambda, double omega, double theta_deg, int pol,
1366
1385
  &layerM[k], &layerDM[k]);
1367
1386
  }
1368
1387
 
1369
- jmat2 *prefix = (jmat2 *)malloc(sizeof(jmat2) * (N + 1));
1370
- jmat2 *suffix = (jmat2 *)malloc(sizeof(jmat2) * (N + 1));
1371
1388
  prefix[0] = jidentity();
1372
1389
  int overflowed = 0;
1373
1390
  for (int k = 0; k < N; k++) {
@@ -1377,8 +1394,7 @@ void tmm_phase_jacobian(double lambda, double omega, double theta_deg, int pol,
1377
1394
 
1378
1395
  if (overflowed) {
1379
1396
  jphase_core(lambda, omega, theta_deg, pol, n0, ns, layerN, thick, N, sinePtr, out);
1380
- for (long i = 0; i < 8L * N; i++) deriv[i] = NAN;
1381
- free(layerN); free(layerM); free(layerDM); free(prefix); free(suffix);
1397
+ for (long i = 0; i < 10L * N; i++) deriv[i] = NAN;
1382
1398
  return;
1383
1399
  }
1384
1400
 
@@ -1396,15 +1412,69 @@ void tmm_phase_jacobian(double lambda, double omega, double theta_deg, int pol,
1396
1412
  &dReflection, &dTransmission);
1397
1413
  const jet sides[2] = { dReflection, dTransmission };
1398
1414
  const jet base[2] = { coefficients.reflection, coefficients.transmission };
1399
- for (int s = 0; s < 2; s++) {
1415
+ for (int s2 = 0; s2 < 2; s2++) {
1400
1416
  cx dd[4];
1401
- jderivs(jdiv(sides[s], base[s]), dd);
1402
- deriv[((long)s * 4 + 0) * N + k] = -dd[0].im * 180.0 / PI;
1403
- deriv[((long)s * 4 + 1) * N + k] = dd[1].im;
1404
- deriv[((long)s * 4 + 2) * N + k] = dd[2].im;
1405
- 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;
1406
1426
  }
1407
1427
  }
1428
+ }
1408
1429
 
1409
- 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);
1410
1480
  }
Binary file