@photivra/engine 2.8.1 → 2.10.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +144 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +8 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +2 -0
  6. package/dist/capture/export-jpeg.d.ts +19 -0
  7. package/dist/capture/export-jpeg.js +190 -108
  8. package/dist/capture/export-jpeg.js.map +1 -1
  9. package/dist/capture/native-environment-lean.js +106 -12
  10. package/dist/capture/native-environment-lean.js.map +1 -1
  11. package/dist/capture/native-environment-tabulated.d.ts +37 -0
  12. package/dist/capture/native-environment-tabulated.js +42 -6
  13. package/dist/capture/native-environment-tabulated.js.map +1 -1
  14. package/dist/core/portable-math.d.ts +13 -0
  15. package/dist/core/portable-math.js +43 -0
  16. package/dist/core/portable-math.js.map +1 -1
  17. package/dist/core/version.d.ts +1 -1
  18. package/dist/core/version.js +1 -1
  19. package/dist/core/version.js.map +1 -1
  20. package/dist/facade/channel-response.d.ts +15 -0
  21. package/dist/facade/channel-response.js +33 -0
  22. package/dist/facade/channel-response.js.map +1 -0
  23. package/dist/facade/diffraction-provider.js +50 -15
  24. package/dist/facade/diffraction-provider.js.map +1 -1
  25. package/dist/facade/display-view.d.ts +75 -0
  26. package/dist/facade/display-view.js +471 -109
  27. package/dist/facade/display-view.js.map +1 -1
  28. package/dist/facade/expected-signal-setup.js +2 -1
  29. package/dist/facade/expected-signal-setup.js.map +1 -1
  30. package/dist/facade/expected-signal.d.ts +36 -4
  31. package/dist/facade/expected-signal.js +331 -43
  32. package/dist/facade/expected-signal.js.map +1 -1
  33. package/dist/facade/scene-radiance.d.ts +4 -1
  34. package/dist/facade/scene-radiance.js +4 -4
  35. package/dist/facade/scene-radiance.js.map +1 -1
  36. package/dist/facade/simulate-capture.d.ts +77 -5
  37. package/dist/facade/simulate-capture.js +232 -40
  38. package/dist/facade/simulate-capture.js.map +1 -1
  39. package/dist/facade/simulate-display-view.d.ts +25 -2
  40. package/dist/facade/simulate-display-view.js +55 -21
  41. package/dist/facade/simulate-display-view.js.map +1 -1
  42. package/dist/gpu/image-kernel-execution.d.ts +5 -0
  43. package/dist/gpu/image-kernel-execution.js +2 -2
  44. package/dist/gpu/image-kernel-execution.js.map +1 -1
  45. package/dist/index.d.ts +5 -4
  46. package/dist/index.js +3 -3
  47. package/dist/index.js.map +1 -1
  48. package/dist/optics/diffraction-psf-profile.d.ts +19 -17
  49. package/dist/optics/diffraction-psf-profile.js +49 -18
  50. package/dist/optics/diffraction-psf-profile.js.map +1 -1
  51. package/dist/optics/irradiance-convolution.d.ts +11 -0
  52. package/dist/optics/irradiance-convolution.js +64 -24
  53. package/dist/optics/irradiance-convolution.js.map +1 -1
  54. package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
  55. package/dist/optics/scene-to-sensor-irradiance.js +58 -24
  56. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  57. package/dist/optics/sensor-aperture-rays.js +2 -1
  58. package/dist/optics/sensor-aperture-rays.js.map +1 -1
  59. package/dist/optics/sensor-environment-query.js +4 -3
  60. package/dist/optics/sensor-environment-query.js.map +1 -1
  61. package/dist/optics/site-diffraction-kernel.d.ts +16 -0
  62. package/dist/optics/site-diffraction-kernel.js +60 -8
  63. package/dist/optics/site-diffraction-kernel.js.map +1 -1
  64. package/dist/output/print-jpeg.d.ts +28 -1
  65. package/dist/output/print-jpeg.js +79 -7
  66. package/dist/output/print-jpeg.js.map +1 -1
  67. package/dist/processing/demosaic.js +114 -37
  68. package/dist/processing/demosaic.js.map +1 -1
  69. package/dist/schema/scene-radiance.d.ts +8 -0
  70. package/dist/schema/scene-radiance.js +28 -17
  71. package/dist/schema/scene-radiance.js.map +1 -1
  72. package/dist/sensor/environment-photo-signal.d.ts +21 -0
  73. package/dist/sensor/environment-photo-signal.js +68 -2
  74. package/dist/sensor/environment-photo-signal.js.map +1 -1
  75. package/dist/sensor/stochastic-seed.d.ts +12 -0
  76. package/dist/sensor/stochastic-seed.js +25 -0
  77. package/dist/sensor/stochastic-seed.js.map +1 -1
  78. package/docs/API_REFERENCE.md +23 -1
  79. package/docs/QUICK_START.md +77 -8
  80. package/docs/USAGE.md +30 -0
  81. package/docs/api/capture-export-jpeg.md +10 -0
  82. package/docs/api/core-version.md +1 -1
  83. package/docs/api/exports.json +77 -2
  84. package/docs/api/facade-display-view.md +50 -0
  85. package/docs/api/facade-scene-radiance.md +2 -1
  86. package/docs/api/facade-simulate-capture.md +88 -8
  87. package/docs/api/facade-simulate-display-view.md +23 -1
  88. package/docs/api/gpu-image-kernel-execution.md +5 -0
  89. package/docs/api/optics-diffraction-psf-profile.md +1 -1
  90. package/docs/api/output-print-jpeg.md +41 -0
  91. package/package.json +1 -1
@@ -26,21 +26,21 @@
26
26
  * its many-blade (circular) limit. The sum of all cells is exactly 1 (Parseval), so the energy captured by
27
27
  * the finite kernel is Σ K over its window and the truncated energy is 1 − Σ K.
28
28
  *
29
- * **Cells much larger than the pattern (2.5).** The frequency-domain node counts grow with β = p·f_c (the cell
30
- * side over the cut-off period), so the work grows as β². From β ≥ `AIRY_REAL_SPACE_MINIMUM_BETA` (192) the cells
31
- * are integrated in real space instead, with the same model and no frequency integral: the Airy encircled energy is
29
+ * **Cells much larger than the pattern (2.5; threshold lowered to β = 32 after 2.8.1).** The frequency-domain node counts grow with β = p·f_c (the
30
+ * cell side over the cut-off period), so the work grows as β². From β ≥ `AIRY_REAL_SPACE_MINIMUM_BETA` (32) the cells are
31
+ * integrated in real space instead, with the same model and no frequency integral: the Airy encircled energy is
32
32
  * EE(r) = 1 − J₀(x)² − J₁(x)², x = π·r·f_c (it follows from J₀′ = −J₁ and J₁′ = J₀ − J₁/x, DLMF §10.6(i), free), so
33
33
  * the energy of the rectangle [0, X] × [0, Y] is (1/2π)∫ EE(r_edge(θ)) dθ over its quadrant, a one-dimensional
34
- * integral in θ. Every cell is a signed sum of such rectangles. x ≥ π·β/2 ≥ 301 there, where J₀ and J₁ come from
35
- * the Hankel asymptotic expansion (DLMF §10.17(i), eq. 10.17.3, free) with eight terms in each of P and Q; the
36
- * remainder is below the first neglected term (DLMF §10.17(iii)), under 1e-17. The θ integral uses 16-point
37
- * Gauss–Legendre panels on which the phase 2x changes by at most 2 radians, so the work grows only as β. The tests
38
- * check the two methods against each other where both are practical (β from 192 to 400), the real-space cells
39
- * against the asymptotic leakage 8√2/(π³·β) of the centre cell, and convergence under doubled panel counts.
40
- * The β threshold lies above every β a `simulateCapture` pitch up to 100 µm can reach (at most 100/(0.3805·1.4) ≈ 188),
41
- * so those captures keep the frequency-domain values unchanged. It cannot move much lower with this method: the innermost
42
- * boundary Z = ½ needs x = π·β/2 ≥ 300 for the expansion, so β ≥ 191; below that J₀ and J₁ would need another method, and
43
- * its cells would differ from the frequency-domain cells in the last bits, changing captures that exist today.
34
+ * integral in θ. Every cell is a signed sum of such rectangles. J₀² + J₁² comes from the Hankel asymptotic expansion
35
+ * (DLMF §10.17(i), eq. 10.17.3, free) from x = 300, with eight terms in each of P and Q (remainder under 1e-17,
36
+ * DLMF §10.17(iii)), and below it from Miller's backward recurrence normalized by J₀ + 2ΣJ₂ₖ = 1 (DLMF §3.6(iii),
37
+ * 10.12.4, free). The θ integral uses 16-point Gauss–Legendre panels on which the phase 2x changes by at most
38
+ * 2 radians, so the work grows only as β. The tests check the two methods against each other from β = 32 to 400, the
39
+ * real-space cells against the asymptotic leakage 8√2/(π³·β) of the centre cell, and convergence under doubled panel
40
+ * counts. The threshold was 192 in 2.5; it moved to 32 because from there the real-space cells agree with the frequency-domain cells
41
+ * within about 1e-12 and cost a few to a few hundred times less (a 750 µm whole-frame cell at f/8 took 3.4 s for 20
42
+ * wavelengths). Every β a native pitch up to 10 µm reaches (at most 10/(0.38·1.4) ≈ 19) stays in the frequency domain,
43
+ * so captures at sensor pitch are unchanged; larger cells (binned and whole-frame views) change in the last bits only.
44
44
  *
45
45
  * **Reuse (2.6).** The cells depend only on (β, R) (and the test-only node scale), so `calculateAiryCellEnergies` keeps the
46
46
  * 256 most recently used results and returns copies: tiles of one frame rendered in the same worker, and captures that
@@ -72,7 +72,7 @@ export const DIFFRACTION_LIMITED_PSF_PROFILE_LIMITS = Object.freeze({ maximumWav
72
72
  * Internal: β = p·f_c (cell side over the cut-off period) from which `calculateAiryCellEnergies` integrates the cells in
73
73
  * real space (2.5). Above every β of a `simulateCapture` pitch up to 100 µm, whose captures keep the frequency-domain values.
74
74
  */
75
- export const AIRY_REAL_SPACE_MINIMUM_BETA = 192;
75
+ export const AIRY_REAL_SPACE_MINIMUM_BETA = 32;
76
76
  /** Radius of the first dark ring of the Airy pattern in units of λ·N_w: j₁,₁/π, j₁,₁ = 3.8317059702 (first zero of J₁). */
77
77
  const AIRY_FIRST_ZERO_PER_LAMBDA_N = 3.8317059702075125 / Math.PI;
78
78
  const EVIDENCE = Object.freeze([Object.freeze({ sourceOrigin: "photivra",
@@ -174,13 +174,17 @@ const HANKEL = (() => {
174
174
  /** Smallest argument of `besselSquareSum`: there the 16-term expansion's remainder is below 1e-17. */
175
175
  const HANKEL_MINIMUM_ARGUMENT = 300;
176
176
  /**
177
- * J₀(x)² + J₁(x)² for x ≥ 300 from the Hankel expansion J_ν(x) = √(2/(πx))·(P_ν cos ω_ν − Q_ν sin ω_ν), ω_ν = x − νπ/2 − π/4,
177
+ * J₀(x)² + J₁(x)² for x > 0. From x ≥ 300 by the Hankel expansion J_ν(x) = √(2/(πx))·(P_ν cos ω_ν − Q_ν sin ω_ν), ω_ν = x − νπ/2 − π/4,
178
178
  * P_ν = Σ(−1)^k a_2k(ν)/x^2k, Q_ν = Σ(−1)^k a_2k+1(ν)/x^2k+1 (DLMF 10.17.3), eight terms each. With ω₁ = ω₀ − π/2,
179
- * J₁ = √(2/(πx))·(P₁ sin ω₀ + Q₁ cos ω₀). Internal.
179
+ * J₁ = √(2/(πx))·(P₁ sin ω₀ + Q₁ cos ω₀). Below 300 by Miller's backward recurrence J_{n−1} = (2n/x)·J_n − J_{n+1} (DLMF 10.6.1;
180
+ * the algorithm, DLMF §3.6(iii)), started at an order above x where J_n is negligible, normalized by J₀ + 2·Σ J_2k = 1
181
+ * (DLMF 10.12.4). Internal.
180
182
  */
181
183
  export function besselSquareSum(x) {
182
- if (!(x >= HANKEL_MINIMUM_ARGUMENT))
183
- throw new InvalidConfigurationError("The Hankel expansion of J0 and J1 needs an argument of at least 300.");
184
+ if (!(x > 0) || !Number.isFinite(x))
185
+ throw new InvalidConfigurationError("J0 and J1 need a positive finite argument.");
186
+ if (x < HANKEL_MINIMUM_ARGUMENT)
187
+ return besselSquareSumByRecurrence(x);
184
188
  const { a0, a1 } = HANKEL, inverse = 1 / x;
185
189
  let p0 = 0, q0 = 0, p1 = 0, q1 = 0, power = 1;
186
190
  for (let k = 0; k < 16; k++) {
@@ -199,6 +203,33 @@ export function besselSquareSum(x) {
199
203
  const j0 = p0 * c - q0 * s, j1 = p1 * s + q1 * c;
200
204
  return 2 / (Math.PI * x) * (j0 * j0 + j1 * j1);
201
205
  }
206
+ /**
207
+ * Miller's backward recurrence for 0 < x < 300 (see `besselSquareSum`). The start order x + 10·∛x + 40 (even) lies far enough in the
208
+ * region where J_n(x) falls faster than exponentially that the start values' error is below double precision at J₀ and J₁; the
209
+ * values are rescaled when they grow large, and normalized before squaring.
210
+ */
211
+ function besselSquareSumByRecurrence(x) {
212
+ const start = 2 * Math.ceil((x + 10 * Math.cbrt(x) + 40) / 2);
213
+ let next = 0, current = 1e-100, sum = 0, j1 = 0;
214
+ for (let n = start; n >= 1; n--) {
215
+ const previous = (2 * n / x) * current - next;
216
+ next = current;
217
+ current = previous;
218
+ if (n - 1 === 1)
219
+ j1 = current;
220
+ if (n - 1 > 0 && (n - 1) % 2 === 0)
221
+ sum += 2 * current;
222
+ if (Math.abs(current) > 1e100) {
223
+ current *= 1e-100;
224
+ next *= 1e-100;
225
+ sum *= 1e-100;
226
+ j1 *= 1e-100;
227
+ }
228
+ }
229
+ sum += current;
230
+ const j0 = current / sum, j1n = j1 / sum;
231
+ return j0 * j0 + j1n * j1n;
232
+ }
202
233
  /**
203
234
  * Real-space cell energies (2.5): see the module comment. G(Z, φ) = ∫₀^φ [J₀² + J₁²](π·β·Z/cos θ) dθ for cell boundaries at
204
235
  * half-integer distances Z (in cells), accumulated along θ for each Z with every needed φ = atan(W/Z) as a panel boundary.
@@ -1 +1 @@
1
- {"version":3,"file":"diffraction-psf-profile.js","sourceRoot":"","sources":["../../src/optics/diffraction-psf-profile.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Builds a `LensSampledPsfProfile` for an ideal, aberration-free, in-focus lens with a uniformly lit\n * circular pupil: the Airy pattern of scalar Fraunhofer diffraction, one unit-sum kernel per declared\n * wavelength on ONE shared sample grid (2.1.0).\n *\n * **Model.** The pupil diameter is D = f/N (entrance pupil = exit pupil, unity pupil magnification) and\n * the pupil-to-image distance z is the thin-lens image distance for the focus (f at infinity), so the\n * image-side (working) f-number is N_w = z/D. The incoherent optical transfer function (OTF) of a\n * uniform circular pupil is the normalized autocorrelation of the pupil,\n * H(ρ) = (2/π)·(acos ρ − ρ·√(1 − ρ²)), ρ = |f|/f_c ≤ 1, f_c = 1/(λ·N_w),\n * and the intensity PSF is its inverse Fourier transform (the Airy pattern, first zero at 1.21967·λ·N_w)\n * (MIT OpenCourseWare 2.71 Optics, Spring 2009, lectures 21–22, free: the intensity PSF as the transform of\n * the pupil autocorrelation and the circular-pupil MTF; also see: Goodman, Introduction to Fourier Optics,\n * 4th ed., 2017, §7.3).\n *\n * **Cell integration (exact, no Bessel functions).** Kernel sample (i, j) is the PSF energy inside the\n * square cell of side p centred on (i·p, j·p). The cell integral is the PSF convolved with the cell's\n * indicator, evaluated at the cell centre, so in the frequency domain\n * K(i, j) = p² ∫∫ H(|f|/f_c)·sinc(p·f_x)·sinc(p·f_y)·cos(2π·p·i·f_x)·cos(2π·p·j·f_y) d²f,\n * with sinc(x) = sin(πx)/(πx) (the sine terms vanish because H is even). It is evaluated in polar\n * coordinates: a trapezoid rule in angle (exponentially convergent for this smooth periodic integrand) and\n * Gauss–Legendre in t with ρ = 1 − t², which removes the (1 − ρ)^{3/2} edge behaviour of H. The node counts\n * follow from the integrand's bandwidth with a fixed margin; the tests check convergence by doubling them and\n * check the values against an independent real-space integration of `calculateIdealPolygonDiffractionPsf` in\n * its many-blade (circular) limit. The sum of all cells is exactly 1 (Parseval), so the energy captured by\n * the finite kernel is Σ K over its window and the truncated energy is 1 − Σ K.\n *\n * **Cells much larger than the pattern (2.5).** The frequency-domain node counts grow with β = p·f_c (the cell\n * side over the cut-off period), so the work grows as β². From β ≥ `AIRY_REAL_SPACE_MINIMUM_BETA` (192) the cells\n * are integrated in real space instead, with the same model and no frequency integral: the Airy encircled energy is\n * EE(r) = 1 − J₀(x)² − J₁(x)², x = π·r·f_c (it follows from J₀′ = −J₁ and J₁′ = J₀ − J₁/x, DLMF §10.6(i), free), so\n * the energy of the rectangle [0, X] × [0, Y] is (1/2π)∫ EE(r_edge(θ)) dθ over its quadrant, a one-dimensional\n * integral in θ. Every cell is a signed sum of such rectangles. x ≥ π·β/2 ≥ 301 there, where J₀ and J₁ come from\n * the Hankel asymptotic expansion (DLMF §10.17(i), eq. 10.17.3, free) with eight terms in each of P and Q; the\n * remainder is below the first neglected term (DLMF §10.17(iii)), under 1e-17. The θ integral uses 16-point\n * Gauss–Legendre panels on which the phase 2x changes by at most 2 radians, so the work grows only as β. The tests\n * check the two methods against each other where both are practical (β from 192 to 400), the real-space cells\n * against the asymptotic leakage 8√2/(π³·β) of the centre cell, and convergence under doubled panel counts.\n * The β threshold lies above every β a `simulateCapture` pitch up to 100 µm can reach (at most 100/(0.3805·1.4) ≈ 188),\n * so those captures keep the frequency-domain values unchanged. It cannot move much lower with this method: the innermost\n * boundary Z = ½ needs x = π·β/2 ≥ 300 for the expansion, so β ≥ 191; below that J₀ and J₁ would need another method, and\n * its cells would differ from the frequency-domain cells in the last bits, changing captures that exist today.\n *\n * **Reuse (2.6).** The cells depend only on (β, R) (and the test-only node scale), so `calculateAiryCellEnergies` keeps the\n * 256 most recently used results and returns copies: tiles of one frame rendered in the same worker, and captures that\n * change only the exposure, ISO, scene or output, reuse the kernels instead of integrating them again. The frequency-domain\n * loop also takes cos θ and sin θ from a per-call table computed with the same expressions, so the values are unchanged.\n *\n * **Truncation and normalization.** The kernel is the (2R+1)² window of cells. The Airy tails fall only as\n * r⁻³, so a finite window always misses some energy: it is reported per wavelength, and the window is then\n * renormalized to unit sum because the sampled-profile contract requires unit-energy kernels. The missing\n * tail energy is thereby put back inside the window (an approximation that slightly raises low-frequency\n * contrast compared with the untruncated pattern).\n *\n * **Approximations (labelled):** scalar (no polarization), aberration-free, uniform pupil amplitude, exactly in\n * focus (no defocus), thin lens with unity pupil magnification, the on-axis PSF used across the declared field\n * (isoplanatic), PSF energy binned into the sample cells, finite window renormalized. Not a measured lens.\n * @see docs/PSF_FOUNDATION.md, \"Diffraction-limited sampled profile (2.1)\".\n */\n\nimport { approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireAllowlistedRecord } from \"../core/record-validation.js\";\nimport type { EvidenceProvenance } from \"../core/evidence-provenance.js\";\nimport type { FocusPlane } from \"./focus-state.js\";\nimport { calculateThinLensImageDistance } from \"./thin-lens.js\";\nimport { parseLensSampledPsfProfile, type LensSampledPsfProfile } from \"./lens-psf-profile.js\";\n\nexport const DIFFRACTION_LIMITED_PSF_PROFILE_MODEL = Object.freeze({\n modelId: \"photivra-diffraction-limited-circular-pupil-psf\" as const, modelVersion: \"1.0.0\" as const });\n/** Input limits: at most 64 wavelengths, kernel radius 0–32 samples (the irradiance-convolution limit). */\nexport const DIFFRACTION_LIMITED_PSF_PROFILE_LIMITS = Object.freeze({ maximumWavelengths: 64, maximumKernelRadiusSamples: 32 });\n/**\n * Internal: β = p·f_c (cell side over the cut-off period) from which `calculateAiryCellEnergies` integrates the cells in\n * real space (2.5). Above every β of a `simulateCapture` pitch up to 100 µm, whose captures keep the frequency-domain values.\n */\nexport const AIRY_REAL_SPACE_MINIMUM_BETA = 192;\n/** Radius of the first dark ring of the Airy pattern in units of λ·N_w: j₁,₁/π, j₁,₁ = 3.8317059702 (first zero of J₁). */\nconst AIRY_FIRST_ZERO_PER_LAMBDA_N = 3.8317059702075125 / Math.PI;\n\nexport interface CreateDiffractionLimitedPsfProfileInput {\n /** Only `circular` in 2.1.0 (polygon pupils: `calculateIdealPolygonDiffractionPsf` gives point densities). */\n aperture: \"circular\";\n focalLengthMm: number;\n apertureFNumber: number;\n focus: FocusPlane;\n /** Strictly increasing wavelengths, nm, in the medium of propagation (no air/vacuum conversion is applied). 1–64. */\n wavelengthsNm: readonly number[];\n /** Square sample pitch of every kernel, µm. */\n samplePitchMicrometers: number;\n /** Kernel half-width in samples; each kernel is (2R + 1)². 0–32. */\n kernelRadiusSamples: number;\n /** Half-extent of the image field over which the on-axis PSF is declared (profile field axes ±x, ±y), mm. */\n fieldHalfExtentMm: { x: number; y: number };\n profileId: string;\n}\n\nexport interface DiffractionLimitedPsfWavelength {\n wavelengthNm: number;\n /** f_c = 1/(λ·N_w), cycles per µm. */\n cutoffFrequencyCyclesPerMicrometer: number;\n /** 1.21967·λ·N_w, µm. */\n firstZeroRadiusMicrometers: number;\n /** Σ of the cell energies inside the window before renormalization (of a total of 1). */\n capturedEnergyFraction: number;\n /** 1 − capturedEnergyFraction: the Airy tail energy outside the window, put back by renormalization. */\n truncatedEnergyFraction: number;\n /** Fraction of the renormalized kernel in the centre sample. */\n centreSampleFraction: number;\n}\n\nexport interface DiffractionLimitedPsfProfile {\n profile: LensSampledPsfProfile;\n model: typeof DIFFRACTION_LIMITED_PSF_PROFILE_MODEL;\n pupilDiameterMm: number;\n pupilToImageDistanceMm: number;\n workingFNumber: number;\n kernelRadiusSamples: number;\n samplePitchMicrometers: number;\n wavelengths: readonly DiffractionLimitedPsfWavelength[];\n /** Largest truncated energy over the wavelengths. */\n maximumTruncatedEnergyFraction: number;\n /**\n * How the cells were integrated. 2.5: wavelengths with β = p·f_c ≥ 192 (cells far larger than the pattern) use the real-space\n * encircled-energy method; `realSpaceWavelengthsNm` lists them, and `method` is `\"frequency-domain-and-real-space\"` when both ran.\n * `maximumRadialNodes` counts frequency-domain radial nodes (0 when only the real-space method ran); `maximumAngularNodes` the\n * angular nodes of either method (for the real-space method, all θ nodes of one wavelength).\n */\n quadrature: { method: \"polar-frequency-domain-trapezoid-gauss-legendre\" | \"real-space-encircled-energy-gauss-legendre\" | \"frequency-domain-and-real-space\";\n maximumRadialNodes: number; maximumAngularNodes: number; realSpaceWavelengthsNm?: readonly number[] };\n}\n\nconst EVIDENCE: readonly EvidenceProvenance[] = Object.freeze([Object.freeze({ sourceOrigin: \"photivra\" as const,\n sourceReference: \"photivra:diffraction-limited-psf-profile\", reuseStatus: \"photivra-owned\" as const })]);\nconst LIMITATIONS = Object.freeze([\n \"Scalar Fraunhofer diffraction of an ideal, uniformly lit circular pupil (Airy pattern); no aberration, apodization, polarization or stray light.\",\n \"Exactly in focus: no defocus or depth of field. Thin lens with unity pupil magnification; the pupil-to-image distance is the thin-lens image distance.\",\n \"The on-axis PSF is declared unchanged across the field (isoplanatic); no field-dependent aberration or pupil clipping.\",\n \"PSF energy is binned into square sample cells; the finite window misses the Airy tail energy listed per wavelength and is renormalized to unit sum.\",\n \"Wavelengths are used as given, in the declared basis, without air/vacuum conversion.\"\n]);\n\nfunction positive(value: unknown, name: string): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) throw new InvalidConfigurationError(name + \" must be a positive finite number.\");\n return value;\n}\n\n/** Gauss–Legendre nodes and weights on [0, 1] (Newton iteration on Pₙ; DLMF §3.5(v)). Cached per n. */\nconst GAUSS_LEGENDRE = new Map<number, { nodes: Float64Array; weights: Float64Array }>();\nfunction gaussLegendre(n: number): { nodes: Float64Array; weights: Float64Array } {\n const cached = GAUSS_LEGENDRE.get(n);\n if (cached) return cached;\n const nodes = new Float64Array(n), weights = new Float64Array(n);\n for (let k = 0; k < Math.ceil(n / 2); k++) {\n let x = Math.cos(Math.PI * (k + 0.75) / (n + 0.5));\n for (let iteration = 0; iteration < 100; iteration++) {\n let p0 = 1, p1 = x;\n for (let m = 2; m <= n; m++) { const p2 = ((2 * m - 1) * x * p1 - (m - 1) * p0) / m; p0 = p1; p1 = p2; }\n const step = p1 / (n * (x * p1 - p0) / (x * x - 1));\n x -= step;\n if (Math.abs(step) < 1e-16) break;\n }\n let p0 = 1, p1 = x;\n for (let m = 2; m <= n; m++) { const p2 = ((2 * m - 1) * x * p1 - (m - 1) * p0) / m; p0 = p1; p1 = p2; }\n const derivative = n * (x * p1 - p0) / (x * x - 1);\n const w = 1 / ((1 - x * x) * derivative * derivative);\n // Map [-1, 1] to [0, 1]: t = (1 ± x)/2, weight w (2/(1-x²)P'² halved).\n nodes[k] = (1 - x) / 2; weights[k] = w;\n nodes[n - 1 - k] = (1 + x) / 2; weights[n - 1 - k] = w;\n }\n const result = { nodes, weights };\n if (GAUSS_LEGENDRE.size < 64) GAUSS_LEGENDRE.set(n, result);\n return result;\n}\nfunction sinc(x: number): number {\n if (x === 0) return 1;\n const a = Math.PI * x;\n return Math.sin(a) / a;\n}\n\n/**\n * Cell energies K(i, j), 0 ≤ i, j ≤ R, of the Airy pattern for β = p·f_c (cell side in units of the cut-off\n * period): in the frequency domain below `AIRY_REAL_SPACE_MINIMUM_BETA`, in real space from it (2.5). `scale`\n * multiplies the node or panel counts (1 in production; the tests use 2 to check convergence).\n */\nexport function calculateAiryCellEnergies(beta: number, radius: number, scale = 1): { cells: Float64Array; radialNodes: number; angularNodes: number; realSpace: boolean } {\n // 2.6: a pure function of (β, R, scale), so results are memoized (most recently used first) and handed out as copies.\n // Tiles of one frame, and captures that change only the exposure or the scene, rebuild the same kernels.\n const key = beta + \"|\" + radius + \"|\" + scale, cached = AIRY_CELL_CACHE.get(key);\n if (cached) {\n AIRY_CELL_CACHE.delete(key); AIRY_CELL_CACHE.set(key, cached);\n return { ...cached, cells: cached.cells.slice() };\n }\n const result = beta >= AIRY_REAL_SPACE_MINIMUM_BETA ? { ...calculateAiryCellEnergiesInRealSpace(beta, radius, scale), radialNodes: 0, realSpace: true } :\n { ...calculateAiryCellEnergiesInFrequencyDomain(beta, radius, scale), realSpace: false };\n AIRY_CELL_CACHE.set(key, { ...result, cells: result.cells.slice() });\n if (AIRY_CELL_CACHE.size > AIRY_CELL_CACHE_ENTRIES) AIRY_CELL_CACHE.delete(AIRY_CELL_CACHE.keys().next().value!);\n return result;\n}\n/** Memoized cell energies (2.6): 256 entries of (R + 1)² values, at most 256 × 33² × 8 bytes ≈ 2.2 MB. */\nconst AIRY_CELL_CACHE_ENTRIES = 256;\nconst AIRY_CELL_CACHE = new Map<string, { cells: Float64Array; radialNodes: number; angularNodes: number; realSpace: boolean }>();\n\n/** Hankel asymptotic coefficients a_k(ν) of DLMF 10.17.1 for ν = 0 and 1, k = 0..15. */\nconst HANKEL = ((): { a0: Float64Array; a1: Float64Array } => {\n const a0 = new Float64Array(16), a1 = new Float64Array(16);\n a0[0] = 1; a1[0] = 1;\n for (let k = 1; k < 16; k++) {\n const odd = (2 * k - 1) * (2 * k - 1);\n a0[k] = a0[k - 1]! * (0 - odd) / (8 * k);\n a1[k] = a1[k - 1]! * (4 - odd) / (8 * k);\n }\n return { a0, a1 };\n})();\n/** Smallest argument of `besselSquareSum`: there the 16-term expansion's remainder is below 1e-17. */\nconst HANKEL_MINIMUM_ARGUMENT = 300;\n\n/**\n * J₀(x)² + J₁(x)² for x ≥ 300 from the Hankel expansion J_ν(x) = √(2/(πx))·(P_ν cos ω_ν − Q_ν sin ω_ν), ω_ν = x − νπ/2 − π/4,\n * P_ν = Σ(−1)^k a_2k(ν)/x^2k, Q_ν = Σ(−1)^k a_2k+1(ν)/x^2k+1 (DLMF 10.17.3), eight terms each. With ω₁ = ω₀ − π/2,\n * J₁ = √(2/(πx))·(P₁ sin ω₀ + Q₁ cos ω₀). Internal.\n */\nexport function besselSquareSum(x: number): number {\n if (!(x >= HANKEL_MINIMUM_ARGUMENT)) throw new InvalidConfigurationError(\"The Hankel expansion of J0 and J1 needs an argument of at least 300.\");\n const { a0, a1 } = HANKEL, inverse = 1 / x;\n let p0 = 0, q0 = 0, p1 = 0, q1 = 0, power = 1;\n for (let k = 0; k < 16; k++) {\n const sign = (k >> 1) % 2 === 0 ? 1 : -1;\n if (k % 2 === 0) { p0 += sign * a0[k]! * power; p1 += sign * a1[k]! * power; } else { q0 += sign * a0[k]! * power; q1 += sign * a1[k]! * power; }\n power *= inverse;\n }\n const omega = x - Math.PI / 4, c = Math.cos(omega), s = Math.sin(omega);\n const j0 = p0 * c - q0 * s, j1 = p1 * s + q1 * c;\n return 2 / (Math.PI * x) * (j0 * j0 + j1 * j1);\n}\n\n/**\n * Real-space cell energies (2.5): see the module comment. G(Z, φ) = ∫₀^φ [J₀² + J₁²](π·β·Z/cos θ) dθ for cell boundaries at\n * half-integer distances Z (in cells), accumulated along θ for each Z with every needed φ = atan(W/Z) as a panel boundary.\n * The rectangle [0, X] × [0, Y] holds 1/4 − (G(X, atan(Y/X)) + G(Y, atan(X/Y)))/(2π) of the energy. Internal.\n */\nexport function calculateAiryCellEnergiesInRealSpace(beta: number, radius: number, scale = 1): { cells: Float64Array; angularNodes: number } {\n const size = radius + 1, table = new Float64Array(size * size), { nodes, weights } = gaussLegendre(16);\n let angularNodes = 0;\n for (let z = 0; z < size; z++) {\n const Z = z + 0.5, k = Math.PI * beta * Z;\n let sum = 0, t0 = 1, theta0 = 0;\n for (let w = 0; w < size; w++) {\n // Panels equal in t = 1/cos θ, so the phase 2x = 2kt changes by at most 2 radians on each.\n const t1 = Math.hypot(Z, w + 0.5) / Z, theta1 = Math.atan2(w + 0.5, Z), panels = Math.max(1, Math.ceil(scale * k * (t1 - t0)));\n let a = theta0;\n for (let n = 1; n <= panels; n++) {\n const b = n === panels ? theta1 : Math.acos(1 / (t0 + (t1 - t0) * n / panels)), h = b - a;\n let panel = 0;\n for (let g = 0; g < 16; g++) panel += weights[g]! * besselSquareSum(k / Math.cos(a + h * nodes[g]!));\n sum += panel * h;\n a = b;\n }\n angularNodes += 16 * panels;\n table[z * size + w] = sum;\n t0 = t1; theta0 = theta1;\n }\n }\n // Boundaries of cell i along one axis as (half-integer index, weight): the centre cell is twice [0, 1/2].\n const bounds = (i: number): [number, number][] => i === 0 ? [[0, 2]] : [[i, 1], [i - 1, -1]];\n const cells = new Float64Array(size * size);\n for (let j = 0; j < size; j++) for (let i = 0; i < size; i++) {\n let constant = 0, leak = 0;\n for (const [a, wa] of bounds(i)) for (const [b, wb] of bounds(j)) {\n constant += wa * wb;\n leak += wa * wb * (table[a * size + b]! + table[b * size + a]!);\n }\n cells[j * size + i] = constant / 4 - leak / (2 * Math.PI);\n }\n return { cells, angularNodes };\n}\n\n/** Frequency-domain cell energies (2.1; see the module comment). Internal. */\nexport function calculateAiryCellEnergiesInFrequencyDomain(beta: number, radius: number, scale = 1): { cells: Float64Array; radialNodes: number; angularNodes: number } {\n // Bandwidth of the angular and radial integrands: c = π·β·(2R + 1)·√2 (the sinc factors and the highest cosine).\n const c = Math.PI * beta * (2 * radius + 1) * Math.SQRT2, margin = 10 * Math.cbrt(c) + 24;\n const quarter = Math.ceil(scale * (c + margin) / 4), radialNodes = Math.ceil(scale * (c + margin));\n const { nodes, weights } = gaussLegendre(radialNodes), size = radius + 1, cells = new Float64Array(size * size);\n const ca = new Float64Array(size), cb = new Float64Array(size), angularStep = (Math.PI / 2) / quarter;\n // cos θ and sin θ of every angular node, computed once with the same expressions as inside the loop (same values).\n const cosines = new Float64Array(quarter + 1), sines = new Float64Array(quarter + 1);\n for (let k = 0; k <= quarter; k++) { const theta = k * angularStep; cosines[k] = Math.cos(theta); sines[k] = Math.sin(theta); }\n for (let r = 0; r < radialNodes; r++) {\n const t = nodes[r]!, rho = 1 - t * t;\n // H(1 − t²) without cancellation: acos(1 − t²) = 2·asin(t/√2), ρ·√(1 − ρ²) = ρ·t·√(2 − t²).\n const otf = (2 / Math.PI) * (2 * Math.asin(t / Math.SQRT2) - rho * t * Math.sqrt(2 - t * t));\n // ∫₀¹ g(ρ) dρ = ∫₀¹ g(1 − t²)·2t dt; the 4 is the four symmetric quadrants of the angular trapezoid.\n const radialWeight = weights[r]! * 2 * t * rho * otf * 4 * angularStep;\n for (let k = 0; k <= quarter; k++) {\n const x = beta * rho * cosines[k]!, y = beta * rho * sines[k]!;\n const w = radialWeight * (k === 0 || k === quarter ? 0.5 : 1) * sinc(x) * sinc(y);\n // cos(2π·i·x) and cos(2π·j·y) by the Chebyshev recurrence.\n const cx = Math.cos(2 * Math.PI * x), cy = Math.cos(2 * Math.PI * y);\n ca[0] = 1; cb[0] = 1;\n if (size > 1) { ca[1] = cx; cb[1] = cy; }\n for (let i = 2; i < size; i++) { ca[i] = 2 * cx * ca[i - 1]! - ca[i - 2]!; cb[i] = 2 * cy * cb[i - 1]! - cb[i - 2]!; }\n for (let j = 0; j < size; j++) {\n const wj = w * cb[j]!;\n for (let i = 0; i < size; i++) { const o = j * size + i; cells[o] = cells[o]! + wj * ca[i]!; }\n }\n }\n }\n const area = beta * beta;\n for (let i = 0; i < cells.length; i++) cells[i] = cells[i]! * area;\n return { cells, radialNodes, angularNodes: 4 * quarter };\n}\n\n/**\n * Creates a sampled lens PSF profile for an ideal diffraction-limited lens with a circular pupil: one\n * unit-sum Airy kernel per wavelength on one shared (2R+1)² grid, with the truncated energy reported per\n * wavelength. The profile declares the given focal length, f-number and focus exactly, and the field axes\n * ±`fieldHalfExtentMm` with the same kernel at every field node.\n * @param input - See `CreateDiffractionLimitedPsfProfileInput`.\n * @returns The profile and its diffraction record. An approximation; not a measured lens.\n * @see docs/PSF_FOUNDATION.md, \"Diffraction-limited sampled profile (2.1)\".\n */\nexport function createDiffractionLimitedPsfProfile(input: CreateDiffractionLimitedPsfProfileInput): CalculationResult<DiffractionLimitedPsfProfile> {\n const r = requireAllowlistedRecord(input, [\"aperture\", \"focalLengthMm\", \"apertureFNumber\", \"focus\", \"wavelengthsNm\", \"samplePitchMicrometers\",\n \"kernelRadiusSamples\", \"fieldHalfExtentMm\", \"profileId\"], \"Invalid diffraction-limited PSF profile input.\");\n if (r.aperture !== \"circular\") throw new InvalidConfigurationError(\"aperture must be \\\"circular\\\".\");\n const focalLengthMm = positive(r.focalLengthMm, \"focalLengthMm\"), fNumber = positive(r.apertureFNumber, \"apertureFNumber\");\n const pitch = positive(r.samplePitchMicrometers, \"samplePitchMicrometers\");\n const L = DIFFRACTION_LIMITED_PSF_PROFILE_LIMITS;\n const radius = r.kernelRadiusSamples;\n if (typeof radius !== \"number\" || !Number.isSafeInteger(radius) || radius < 0 || radius > L.maximumKernelRadiusSamples) {\n throw new InvalidConfigurationError(\"kernelRadiusSamples must be an integer from 0 to \" + L.maximumKernelRadiusSamples + \".\");\n }\n const focus = requireAllowlistedRecord(r.focus, [\"kind\", \"distanceM\"], \"Invalid focus.\");\n let focusDiopters: number, distanceMm: number;\n if (focus.kind === \"infinity\") {\n if (focus.distanceM !== undefined) throw new InvalidConfigurationError(\"An infinity focus has no distanceM.\");\n focusDiopters = 0; distanceMm = focalLengthMm;\n } else if (focus.kind === \"finite\") {\n const distanceM = positive(focus.distanceM, \"focus.distanceM\");\n focusDiopters = 1 / distanceM;\n distanceMm = calculateThinLensImageDistance({ focalLengthMm, objectDistanceM: distanceM }).value.imageDistanceMm;\n } else throw new InvalidConfigurationError(\"focus.kind must be finite or infinity.\");\n const field = requireAllowlistedRecord(r.fieldHalfExtentMm, [\"x\", \"y\"], \"Invalid fieldHalfExtentMm.\");\n const fieldX = positive(field.x, \"fieldHalfExtentMm.x\"), fieldY = positive(field.y, \"fieldHalfExtentMm.y\");\n const list = r.wavelengthsNm;\n if (!Array.isArray(list) || list.length < 1 || list.length > L.maximumWavelengths) throw new InvalidConfigurationError(\"wavelengthsNm must list 1 to 64 wavelengths.\");\n const wavelengths = (list as unknown[]).map((value, index) => positive(value, \"wavelengthsNm[\" + index + \"]\"));\n for (let i = 1; i < wavelengths.length; i++) if (!(wavelengths[i]! > wavelengths[i - 1]!)) throw new InvalidConfigurationError(\"wavelengthsNm must be strictly increasing.\");\n if (typeof r.profileId !== \"string\" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(r.profileId)) throw new InvalidConfigurationError(\"profileId must be a public opaque ID.\");\n\n const pupilDiameterMm = focalLengthMm / fNumber, workingFNumber = distanceMm / pupilDiameterMm, size = 2 * radius + 1;\n let maximumRadialNodes = 0, maximumAngularNodes = 0, maximumTruncated = 0;\n const records: DiffractionLimitedPsfWavelength[] = [], kernels: number[][] = [], realSpaceWavelengthsNm: number[] = [];\n for (const nm of wavelengths) {\n const lambdaN = nm / 1000 * workingFNumber, cutoff = 1 / lambdaN;\n const airy = calculateAiryCellEnergies(pitch * cutoff, radius);\n if (airy.realSpace) realSpaceWavelengthsNm.push(nm);\n maximumRadialNodes = Math.max(maximumRadialNodes, airy.radialNodes); maximumAngularNodes = Math.max(maximumAngularNodes, airy.angularNodes);\n const values: number[] = new Array(size * size);\n let captured = 0;\n for (let ky = 0; ky < size; ky++) for (let kx = 0; kx < size; kx++) {\n const value = airy.cells[Math.abs(ky - radius) * (radius + 1) + Math.abs(kx - radius)]!;\n // Every cell energy of a non-negative PSF is positive; a negative value would be a quadrature failure.\n if (!(value > -1e-13)) throw new InvalidConfigurationError(\"Diffraction cell integration failed to converge.\");\n values[ky * size + kx] = Math.max(0, value);\n captured += values[ky * size + kx]!;\n }\n if (!(captured > 0 && captured <= 1 + 1e-9)) throw new InvalidConfigurationError(\"Diffraction cell integration failed to converge.\");\n const truncated = Math.max(0, 1 - captured);\n maximumTruncated = Math.max(maximumTruncated, truncated);\n const kernel = values.map((v) => v / captured);\n kernels.push(kernel);\n records.push({ wavelengthNm: nm, cutoffFrequencyCyclesPerMicrometer: cutoff, firstZeroRadiusMicrometers: AIRY_FIRST_ZERO_PER_LAMBDA_N * lambdaN,\n capturedEnergyFraction: captured, truncatedEnergyFraction: truncated, centreSampleFraction: kernel[radius * size + radius]! });\n }\n const uncertainty = { kind: \"not-quantified\" as const, limitation: \"Ideal diffraction-limited model; no measured lens or quadrature error bound.\" };\n const nodes = [-fieldX, fieldX].flatMap((fx) => [-fieldY, fieldY].flatMap((fy) => wavelengths.map((nm, index) => ({\n nodeId: \"airy-\" + (fx < 0 ? \"xm\" : \"xp\") + \"-\" + (fy < 0 ? \"ym\" : \"yp\") + \"-\" + index,\n coordinate: { focalLengthMm, focusDiopters, apertureFNumber: fNumber, fieldXmm: fx, fieldYmm: fy, wavelengthNm: nm, signedDefocusImagePlaneMicrometers: 0 },\n kernel: { widthSamples: size, heightSamples: size, samplePitchMicrometersX: pitch, samplePitchMicrometersY: pitch, centerSampleX: radius, centerSampleY: radius,\n normalizedIntensity: kernels[index]! },\n bestFocusImagePlaneOffsetMicrometers: 0, relativePupilThroughputFactor: 1, evidence: EVIDENCE, uncertainty }))));\n const profile = parseLensSampledPsfProfile({ schemaVersion: \"0.1.0\", profileId: r.profileId, profileVersion: DIFFRACTION_LIMITED_PSF_PROFILE_MODEL.modelVersion,\n scientificStatus: \"approximation\", opticalDomain: \"lens-primary-optical-path-only\", coordinateSystem: \"image-plane-metric\", fieldAxes: \"+X right, +Y up\",\n kernelEnergyNormalization: \"unit-energy-shape\", interpolation: \"bounded-regular-grid-multilinear\", throughputOwnership: \"separate-relative-pupil-throughput-factor\",\n responseIncludes: { diffraction: true, aberration: false, defocus: false, pupilClippingShape: false },\n sensorOpticalStackIncluded: false, sensorSamplingIncluded: false, reconstructionIncluded: false, strayLightIncluded: false,\n axes: { focalLengthMm: [focalLengthMm], focusDiopters: [focusDiopters], apertureFNumber: [fNumber], fieldXmm: [-fieldX, fieldX], fieldYmm: [-fieldY, fieldY],\n wavelengthNm: wavelengths, signedDefocusImagePlaneMicrometers: [0] },\n nodes, evidence: EVIDENCE, limitations: [...LIMITATIONS] });\n return approximationResult({ profile, model: DIFFRACTION_LIMITED_PSF_PROFILE_MODEL, pupilDiameterMm, pupilToImageDistanceMm: distanceMm, workingFNumber,\n kernelRadiusSamples: radius, samplePitchMicrometers: pitch, wavelengths: records, maximumTruncatedEnergyFraction: maximumTruncated,\n quadrature: realSpaceWavelengthsNm.length === 0 ? { method: \"polar-frequency-domain-trapezoid-gauss-legendre\" as const, maximumRadialNodes, maximumAngularNodes } :\n { method: realSpaceWavelengthsNm.length === wavelengths.length ? \"real-space-encircled-energy-gauss-legendre\" as const : \"frequency-domain-and-real-space\" as const,\n maximumRadialNodes, maximumAngularNodes, realSpaceWavelengthsNm } },\n DIFFRACTION_LIMITED_PSF_PROFILE_MODEL.modelId, DIFFRACTION_LIMITED_PSF_PROFILE_MODEL.modelVersion, [...LIMITATIONS]);\n}\n"]}
1
+ {"version":3,"file":"diffraction-psf-profile.js","sourceRoot":"","sources":["../../src/optics/diffraction-psf-profile.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,EAAE,mBAAmB,EAA0B,MAAM,+BAA+B,CAAC;AAC5F,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAGxE,OAAO,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,0BAA0B,EAA8B,MAAM,uBAAuB,CAAC;AAE/F,MAAM,CAAC,MAAM,qCAAqC,GAAG,MAAM,CAAC,MAAM,CAAC;IACjE,OAAO,EAAE,iDAA0D,EAAE,YAAY,EAAE,OAAgB;CAAE,CAAC,CAAC;AACzG,2GAA2G;AAC3G,MAAM,CAAC,MAAM,sCAAsC,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,kBAAkB,EAAE,EAAE,EAAE,0BAA0B,EAAE,EAAE,EAAE,CAAC,CAAC;AAChI;;;GAGG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,CAAC;AAC/C,2HAA2H;AAC3H,MAAM,4BAA4B,GAAG,kBAAkB,GAAG,IAAI,CAAC,EAAE,CAAC;AAsDlE,MAAM,QAAQ,GAAkC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,UAAmB;QAC9G,eAAe,EAAE,0CAA0C,EAAE,WAAW,EAAE,gBAAyB,EAAE,CAAC,CAAC,CAAC,CAAC;AAC3G,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;IAChC,kJAAkJ;IAClJ,wJAAwJ;IACxJ,wHAAwH;IACxH,qJAAqJ;IACrJ,sFAAsF;CACvF,CAAC,CAAC;AAEH,SAAS,QAAQ,CAAC,KAAc,EAAE,IAAY;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,oCAAoC,CAAC,CAAC;IACzJ,OAAO,KAAK,CAAC;AACf,CAAC;AAED,uGAAuG;AACvG,MAAM,cAAc,GAAG,IAAI,GAAG,EAA0D,CAAC;AACzF,SAAS,aAAa,CAAC,CAAS;IAC9B,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACrC,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;IACjE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QACnD,KAAK,IAAI,SAAS,GAAG,CAAC,EAAE,SAAS,GAAG,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC;YACrD,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;gBAAC,EAAE,GAAG,EAAE,CAAC;gBAAC,EAAE,GAAG,EAAE,CAAC;YAAC,CAAC;YACxG,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACpD,CAAC,IAAI,IAAI,CAAC;YACV,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK;gBAAE,MAAM;QACpC,CAAC;QACD,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;YAAC,EAAE,GAAG,EAAE,CAAC;YAAC,EAAE,GAAG,EAAE,CAAC;QAAC,CAAC;QACxG,MAAM,UAAU,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,GAAG,UAAU,CAAC,CAAC;QACtD,uEAAuE;QACvE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACvC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QAAC,OAAO,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IACzD,CAAC;IACD,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAClC,IAAI,cAAc,CAAC,IAAI,GAAG,EAAE;QAAE,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5D,OAAO,MAAM,CAAC;AAChB,CAAC;AACD,SAAS,IAAI,CAAC,CAAS;IACrB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;IACtB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAY,EAAE,MAAc,EAAE,KAAK,GAAG,CAAC;IAC/E,sHAAsH;IACtH,yGAAyG;IACzG,MAAM,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,MAAM,GAAG,GAAG,GAAG,KAAK,EAAE,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjF,IAAI,MAAM,EAAE,CAAC;QACX,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAAC,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC9D,OAAO,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IACpD,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,IAAI,4BAA4B,CAAC,CAAC,CAAC,EAAE,GAAG,oCAAoC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvJ,EAAE,GAAG,0CAA0C,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC3F,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACrE,IAAI,eAAe,CAAC,IAAI,GAAG,uBAAuB;QAAE,eAAe,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAM,CAAC,CAAC;IACjH,OAAO,MAAM,CAAC;AAChB,CAAC;AACD,0GAA0G;AAC1G,MAAM,uBAAuB,GAAG,GAAG,CAAC;AACpC,MAAM,eAAe,GAAG,IAAI,GAAG,EAAkG,CAAC;AAElI,wFAAwF;AACxF,MAAM,MAAM,GAAG,CAAC,GAA2C,EAAE;IAC3D,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,IAAI,YAAY,CAAC,EAAE,CAAC,CAAC;IAC3D,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACtC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;AACpB,CAAC,CAAC,EAAE,CAAC;AACL,sGAAsG;AACtG,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAEpC;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,CAAS;IACvC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,4CAA4C,CAAC,CAAC;IACvH,IAAI,CAAC,GAAG,uBAAuB;QAAE,OAAO,2BAA2B,CAAC,CAAC,CAAC,CAAC;IACvE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC;IAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAAC,EAAE,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,CAAE,GAAG,KAAK,CAAC;YAAC,EAAE,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,CAAE,GAAG,KAAK,CAAC;QAAC,CAAC;aAAM,CAAC;YAAC,EAAE,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,CAAE,GAAG,KAAK,CAAC;YAAC,EAAE,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,CAAE,GAAG,KAAK,CAAC;QAAC,CAAC;QACjJ,KAAK,IAAI,OAAO,CAAC;IACnB,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACxE,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;AACjD,CAAC;AACD;;;;GAIG;AACH,SAAS,2BAA2B,CAAC,CAAS;IAC5C,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9D,IAAI,IAAI,GAAG,CAAC,EAAE,OAAO,GAAG,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAChD,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC;QAC9C,IAAI,GAAG,OAAO,CAAC;QAAC,OAAO,GAAG,QAAQ,CAAC;QACnC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,EAAE,GAAG,OAAO,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC;QACvD,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,EAAE,CAAC;YAAC,OAAO,IAAI,MAAM,CAAC;YAAC,IAAI,IAAI,MAAM,CAAC;YAAC,GAAG,IAAI,MAAM,CAAC;YAAC,EAAE,IAAI,MAAM,CAAC;QAAC,CAAC;IACpG,CAAC;IACD,GAAG,IAAI,OAAO,CAAC;IACf,MAAM,EAAE,GAAG,OAAO,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,CAAC;IACzC,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,GAAG,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oCAAoC,CAAC,IAAY,EAAE,MAAc,EAAE,KAAK,GAAG,CAAC;IAC1F,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,YAAY,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC;IACvG,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC;QAC1C,IAAI,GAAG,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;QAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9B,2FAA2F;YAC3F,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;YAC/H,IAAI,CAAC,GAAG,MAAM,CAAC;YACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACjC,MAAM,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC1F,IAAI,KAAK,GAAG,CAAC,CAAC;gBACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE;oBAAE,KAAK,IAAI,OAAO,CAAC,CAAC,CAAE,GAAG,eAAe,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC;gBACrG,GAAG,IAAI,KAAK,GAAG,CAAC,CAAC;gBACjB,CAAC,GAAG,CAAC,CAAC;YACR,CAAC;YACD,YAAY,IAAI,EAAE,GAAG,MAAM,CAAC;YAC5B,KAAK,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;YAC1B,EAAE,GAAG,EAAE,CAAC;YAAC,MAAM,GAAG,MAAM,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,0GAA0G;IAC1G,MAAM,MAAM,GAAG,CAAC,CAAS,EAAsB,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7F,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE;QAAE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7D,IAAI,QAAQ,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC;YAC3B,KAAK,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC;gBAAE,KAAK,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;oBACjE,QAAQ,IAAI,EAAE,GAAG,EAAE,CAAC;oBACpB,IAAI,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAE,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAE,CAAC,CAAC;gBAClE,CAAC;YACD,KAAK,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5D,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;AACjC,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,0CAA0C,CAAC,IAAY,EAAE,MAAc,EAAE,KAAK,GAAG,CAAC;IAChG,iHAAiH;IACjH,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IAC1F,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IACnG,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,WAAW,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,YAAY,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAChH,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC;IACtG,mHAAmH;IACnH,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;IACrF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;QAAC,MAAM,KAAK,GAAG,CAAC,GAAG,WAAW,CAAC;QAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAAC,CAAC;IAC/H,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,4FAA4F;QAC5F,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC7F,qGAAqG;QACrG,MAAM,YAAY,GAAG,OAAO,CAAC,CAAC,CAAE,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG,WAAW,CAAC;QACvE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;YAClC,MAAM,CAAC,GAAG,IAAI,GAAG,GAAG,GAAG,OAAO,CAAC,CAAC,CAAE,EAAE,CAAC,GAAG,IAAI,GAAG,GAAG,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;YAC/D,MAAM,CAAC,GAAG,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YAClF,2DAA2D;YAC3D,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YACrE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACrB,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;gBAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;gBAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YAAC,CAAC;YACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;gBAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;gBAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;YAAC,CAAC;YACtH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC9B,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAE,CAAC;gBACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;oBAAC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;oBAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAE,CAAC;gBAAC,CAAC;YAChG,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,GAAG,IAAI,CAAC;IACnE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC;AAC3D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kCAAkC,CAAC,KAA8C;IAC/F,MAAM,CAAC,GAAG,wBAAwB,CAAC,KAAK,EAAE,CAAC,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,OAAO,EAAE,eAAe,EAAE,wBAAwB;QAC3I,qBAAqB,EAAE,mBAAmB,EAAE,WAAW,CAAC,EAAE,gDAAgD,CAAC,CAAC;IAC9G,IAAI,CAAC,CAAC,QAAQ,KAAK,UAAU;QAAE,MAAM,IAAI,yBAAyB,CAAC,gCAAgC,CAAC,CAAC;IACrG,MAAM,aAAa,GAAG,QAAQ,CAAC,CAAC,CAAC,aAAa,EAAE,eAAe,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAC;IAC3H,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,sBAAsB,EAAE,wBAAwB,CAAC,CAAC;IAC3E,MAAM,CAAC,GAAG,sCAAsC,CAAC;IACjD,MAAM,MAAM,GAAG,CAAC,CAAC,mBAAmB,CAAC;IACrC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,0BAA0B,EAAE,CAAC;QACvH,MAAM,IAAI,yBAAyB,CAAC,mDAAmD,GAAG,CAAC,CAAC,0BAA0B,GAAG,GAAG,CAAC,CAAC;IAChI,CAAC;IACD,MAAM,KAAK,GAAG,wBAAwB,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACzF,IAAI,aAAqB,EAAE,UAAkB,CAAC;IAC9C,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;YAAE,MAAM,IAAI,yBAAyB,CAAC,qCAAqC,CAAC,CAAC;QAC9G,aAAa,GAAG,CAAC,CAAC;QAAC,UAAU,GAAG,aAAa,CAAC;IAChD,CAAC;SAAM,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;QAC/D,aAAa,GAAG,CAAC,GAAG,SAAS,CAAC;QAC9B,UAAU,GAAG,8BAA8B,CAAC,EAAE,aAAa,EAAE,eAAe,EAAE,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC;IACnH,CAAC;;QAAM,MAAM,IAAI,yBAAyB,CAAC,wCAAwC,CAAC,CAAC;IACrF,MAAM,KAAK,GAAG,wBAAwB,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,4BAA4B,CAAC,CAAC;IACtG,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC;IAC3G,MAAM,IAAI,GAAG,CAAC,CAAC,aAAa,CAAC;IAC7B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,kBAAkB;QAAE,MAAM,IAAI,yBAAyB,CAAC,8CAA8C,CAAC,CAAC;IACvK,MAAM,WAAW,GAAI,IAAkB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC;IAC/G,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,CAAC,WAAW,CAAC,CAAC,CAAE,GAAG,WAAW,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,4CAA4C,CAAC,CAAC;IAC7K,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,qCAAqC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,uCAAuC,CAAC,CAAC;IAE9K,MAAM,eAAe,GAAG,aAAa,GAAG,OAAO,EAAE,cAAc,GAAG,UAAU,GAAG,eAAe,EAAE,IAAI,GAAG,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;IACtH,IAAI,kBAAkB,GAAG,CAAC,EAAE,mBAAmB,GAAG,CAAC,EAAE,gBAAgB,GAAG,CAAC,CAAC;IAC1E,MAAM,OAAO,GAAsC,EAAE,EAAE,OAAO,GAAe,EAAE,EAAE,sBAAsB,GAAa,EAAE,CAAC;IACvH,KAAK,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,GAAG,cAAc,EAAE,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC;QACjE,MAAM,IAAI,GAAG,yBAAyB,CAAC,KAAK,GAAG,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/D,IAAI,IAAI,CAAC,SAAS;YAAE,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpD,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAAC,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC5I,MAAM,MAAM,GAAa,IAAI,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QAChD,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE;YAAE,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC;gBACnE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC,CAAE,CAAC;gBACxF,uGAAuG;gBACvG,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC;oBAAE,MAAM,IAAI,yBAAyB,CAAC,kDAAkD,CAAC,CAAC;gBAC/G,MAAM,CAAC,EAAE,GAAG,IAAI,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;gBAC5C,QAAQ,IAAI,MAAM,CAAC,EAAE,GAAG,IAAI,GAAG,EAAE,CAAE,CAAC;YACtC,CAAC;QACD,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,IAAI,QAAQ,IAAI,CAAC,GAAG,IAAI,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,kDAAkD,CAAC,CAAC;QACrI,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC;QAC5C,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,kCAAkC,EAAE,MAAM,EAAE,0BAA0B,EAAE,4BAA4B,GAAG,OAAO;YAC7I,sBAAsB,EAAE,QAAQ,EAAE,uBAAuB,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG,MAAM,CAAE,EAAE,CAAC,CAAC;IACnI,CAAC;IACD,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,gBAAyB,EAAE,UAAU,EAAE,8EAA8E,EAAE,CAAC;IACpJ,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAChH,MAAM,EAAE,OAAO,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,GAAG,KAAK;QACrF,UAAU,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,kCAAkC,EAAE,CAAC,EAAE;QAC3J,MAAM,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,uBAAuB,EAAE,KAAK,EAAE,uBAAuB,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM;YAC7J,mBAAmB,EAAE,OAAO,CAAC,KAAK,CAAE,EAAE;QACxC,oCAAoC,EAAE,CAAC,EAAE,6BAA6B,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW;KAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACnH,MAAM,OAAO,GAAG,0BAA0B,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,cAAc,EAAE,qCAAqC,CAAC,YAAY;QAC7J,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,gCAAgC,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,SAAS,EAAE,iBAAiB;QACxJ,yBAAyB,EAAE,mBAAmB,EAAE,aAAa,EAAE,kCAAkC,EAAE,mBAAmB,EAAE,2CAA2C;QACnK,gBAAgB,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE;QACrG,0BAA0B,EAAE,KAAK,EAAE,sBAAsB,EAAE,KAAK,EAAE,sBAAsB,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK;QAC1H,IAAI,EAAE,EAAE,aAAa,EAAE,CAAC,aAAa,CAAC,EAAE,aAAa,EAAE,CAAC,aAAa,CAAC,EAAE,eAAe,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC;YAC1J,YAAY,EAAE,WAAW,EAAE,kCAAkC,EAAE,CAAC,CAAC,CAAC,EAAE;QACtE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9D,OAAO,mBAAmB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,qCAAqC,EAAE,eAAe,EAAE,sBAAsB,EAAE,UAAU,EAAE,cAAc;QACrJ,mBAAmB,EAAE,MAAM,EAAE,sBAAsB,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,8BAA8B,EAAE,gBAAgB;QAClI,UAAU,EAAE,sBAAsB,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,iDAA0D,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,CAAC,CAAC;YACjK,EAAE,MAAM,EAAE,sBAAsB,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,4CAAqD,CAAC,CAAC,CAAC,iCAA0C;gBACjK,kBAAkB,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,EAAE,EACzE,qCAAqC,CAAC,OAAO,EAAE,qCAAqC,CAAC,YAAY,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC;AACvH,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Builds a `LensSampledPsfProfile` for an ideal, aberration-free, in-focus lens with a uniformly lit\n * circular pupil: the Airy pattern of scalar Fraunhofer diffraction, one unit-sum kernel per declared\n * wavelength on ONE shared sample grid (2.1.0).\n *\n * **Model.** The pupil diameter is D = f/N (entrance pupil = exit pupil, unity pupil magnification) and\n * the pupil-to-image distance z is the thin-lens image distance for the focus (f at infinity), so the\n * image-side (working) f-number is N_w = z/D. The incoherent optical transfer function (OTF) of a\n * uniform circular pupil is the normalized autocorrelation of the pupil,\n * H(ρ) = (2/π)·(acos ρ − ρ·√(1 − ρ²)), ρ = |f|/f_c ≤ 1, f_c = 1/(λ·N_w),\n * and the intensity PSF is its inverse Fourier transform (the Airy pattern, first zero at 1.21967·λ·N_w)\n * (MIT OpenCourseWare 2.71 Optics, Spring 2009, lectures 21–22, free: the intensity PSF as the transform of\n * the pupil autocorrelation and the circular-pupil MTF; also see: Goodman, Introduction to Fourier Optics,\n * 4th ed., 2017, §7.3).\n *\n * **Cell integration (exact, no Bessel functions).** Kernel sample (i, j) is the PSF energy inside the\n * square cell of side p centred on (i·p, j·p). The cell integral is the PSF convolved with the cell's\n * indicator, evaluated at the cell centre, so in the frequency domain\n * K(i, j) = p² ∫∫ H(|f|/f_c)·sinc(p·f_x)·sinc(p·f_y)·cos(2π·p·i·f_x)·cos(2π·p·j·f_y) d²f,\n * with sinc(x) = sin(πx)/(πx) (the sine terms vanish because H is even). It is evaluated in polar\n * coordinates: a trapezoid rule in angle (exponentially convergent for this smooth periodic integrand) and\n * Gauss–Legendre in t with ρ = 1 − t², which removes the (1 − ρ)^{3/2} edge behaviour of H. The node counts\n * follow from the integrand's bandwidth with a fixed margin; the tests check convergence by doubling them and\n * check the values against an independent real-space integration of `calculateIdealPolygonDiffractionPsf` in\n * its many-blade (circular) limit. The sum of all cells is exactly 1 (Parseval), so the energy captured by\n * the finite kernel is Σ K over its window and the truncated energy is 1 − Σ K.\n *\n * **Cells much larger than the pattern (2.5; threshold lowered to β = 32 after 2.8.1).** The frequency-domain node counts grow with β = p·f_c (the\n * cell side over the cut-off period), so the work grows as β². From β ≥ `AIRY_REAL_SPACE_MINIMUM_BETA` (32) the cells are\n * integrated in real space instead, with the same model and no frequency integral: the Airy encircled energy is\n * EE(r) = 1 − J₀(x)² − J₁(x)², x = π·r·f_c (it follows from J₀′ = −J₁ and J₁′ = J₀ − J₁/x, DLMF §10.6(i), free), so\n * the energy of the rectangle [0, X] × [0, Y] is (1/2π)∫ EE(r_edge(θ)) dθ over its quadrant, a one-dimensional\n * integral in θ. Every cell is a signed sum of such rectangles. J₀² + J₁² comes from the Hankel asymptotic expansion\n * (DLMF §10.17(i), eq. 10.17.3, free) from x = 300, with eight terms in each of P and Q (remainder under 1e-17,\n * DLMF §10.17(iii)), and below it from Miller's backward recurrence normalized by J₀ + 2ΣJ₂ₖ = 1 (DLMF §3.6(iii),\n * 10.12.4, free). The θ integral uses 16-point Gauss–Legendre panels on which the phase 2x changes by at most\n * 2 radians, so the work grows only as β. The tests check the two methods against each other from β = 32 to 400, the\n * real-space cells against the asymptotic leakage 8√2/(π³·β) of the centre cell, and convergence under doubled panel\n * counts. The threshold was 192 in 2.5; it moved to 32 because from there the real-space cells agree with the frequency-domain cells\n * within about 1e-12 and cost a few to a few hundred times less (a 750 µm whole-frame cell at f/8 took 3.4 s for 20\n * wavelengths). Every β a native pitch up to 10 µm reaches (at most 10/(0.38·1.4) ≈ 19) stays in the frequency domain,\n * so captures at sensor pitch are unchanged; larger cells (binned and whole-frame views) change in the last bits only.\n *\n * **Reuse (2.6).** The cells depend only on (β, R) (and the test-only node scale), so `calculateAiryCellEnergies` keeps the\n * 256 most recently used results and returns copies: tiles of one frame rendered in the same worker, and captures that\n * change only the exposure, ISO, scene or output, reuse the kernels instead of integrating them again. The frequency-domain\n * loop also takes cos θ and sin θ from a per-call table computed with the same expressions, so the values are unchanged.\n *\n * **Truncation and normalization.** The kernel is the (2R+1)² window of cells. The Airy tails fall only as\n * r⁻³, so a finite window always misses some energy: it is reported per wavelength, and the window is then\n * renormalized to unit sum because the sampled-profile contract requires unit-energy kernels. The missing\n * tail energy is thereby put back inside the window (an approximation that slightly raises low-frequency\n * contrast compared with the untruncated pattern).\n *\n * **Approximations (labelled):** scalar (no polarization), aberration-free, uniform pupil amplitude, exactly in\n * focus (no defocus), thin lens with unity pupil magnification, the on-axis PSF used across the declared field\n * (isoplanatic), PSF energy binned into the sample cells, finite window renormalized. Not a measured lens.\n * @see docs/PSF_FOUNDATION.md, \"Diffraction-limited sampled profile (2.1)\".\n */\n\nimport { approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireAllowlistedRecord } from \"../core/record-validation.js\";\nimport type { EvidenceProvenance } from \"../core/evidence-provenance.js\";\nimport type { FocusPlane } from \"./focus-state.js\";\nimport { calculateThinLensImageDistance } from \"./thin-lens.js\";\nimport { parseLensSampledPsfProfile, type LensSampledPsfProfile } from \"./lens-psf-profile.js\";\n\nexport const DIFFRACTION_LIMITED_PSF_PROFILE_MODEL = Object.freeze({\n modelId: \"photivra-diffraction-limited-circular-pupil-psf\" as const, modelVersion: \"1.0.0\" as const });\n/** Input limits: at most 64 wavelengths, kernel radius 0–32 samples (the irradiance-convolution limit). */\nexport const DIFFRACTION_LIMITED_PSF_PROFILE_LIMITS = Object.freeze({ maximumWavelengths: 64, maximumKernelRadiusSamples: 32 });\n/**\n * Internal: β = p·f_c (cell side over the cut-off period) from which `calculateAiryCellEnergies` integrates the cells in\n * real space (2.5). Above every β of a `simulateCapture` pitch up to 100 µm, whose captures keep the frequency-domain values.\n */\nexport const AIRY_REAL_SPACE_MINIMUM_BETA = 32;\n/** Radius of the first dark ring of the Airy pattern in units of λ·N_w: j₁,₁/π, j₁,₁ = 3.8317059702 (first zero of J₁). */\nconst AIRY_FIRST_ZERO_PER_LAMBDA_N = 3.8317059702075125 / Math.PI;\n\nexport interface CreateDiffractionLimitedPsfProfileInput {\n /** Only `circular` in 2.1.0 (polygon pupils: `calculateIdealPolygonDiffractionPsf` gives point densities). */\n aperture: \"circular\";\n focalLengthMm: number;\n apertureFNumber: number;\n focus: FocusPlane;\n /** Strictly increasing wavelengths, nm, in the medium of propagation (no air/vacuum conversion is applied). 1–64. */\n wavelengthsNm: readonly number[];\n /** Square sample pitch of every kernel, µm. */\n samplePitchMicrometers: number;\n /** Kernel half-width in samples; each kernel is (2R + 1)². 0–32. */\n kernelRadiusSamples: number;\n /** Half-extent of the image field over which the on-axis PSF is declared (profile field axes ±x, ±y), mm. */\n fieldHalfExtentMm: { x: number; y: number };\n profileId: string;\n}\n\nexport interface DiffractionLimitedPsfWavelength {\n wavelengthNm: number;\n /** f_c = 1/(λ·N_w), cycles per µm. */\n cutoffFrequencyCyclesPerMicrometer: number;\n /** 1.21967·λ·N_w, µm. */\n firstZeroRadiusMicrometers: number;\n /** Σ of the cell energies inside the window before renormalization (of a total of 1). */\n capturedEnergyFraction: number;\n /** 1 − capturedEnergyFraction: the Airy tail energy outside the window, put back by renormalization. */\n truncatedEnergyFraction: number;\n /** Fraction of the renormalized kernel in the centre sample. */\n centreSampleFraction: number;\n}\n\nexport interface DiffractionLimitedPsfProfile {\n profile: LensSampledPsfProfile;\n model: typeof DIFFRACTION_LIMITED_PSF_PROFILE_MODEL;\n pupilDiameterMm: number;\n pupilToImageDistanceMm: number;\n workingFNumber: number;\n kernelRadiusSamples: number;\n samplePitchMicrometers: number;\n wavelengths: readonly DiffractionLimitedPsfWavelength[];\n /** Largest truncated energy over the wavelengths. */\n maximumTruncatedEnergyFraction: number;\n /**\n * How the cells were integrated. 2.5: wavelengths with β = p·f_c ≥ `AIRY_REAL_SPACE_MINIMUM_BETA` (192 in 2.5–2.8.1, then 32) use the real-space\n * encircled-energy method; `realSpaceWavelengthsNm` lists them, and `method` is `\"frequency-domain-and-real-space\"` when both ran.\n * `maximumRadialNodes` counts frequency-domain radial nodes (0 when only the real-space method ran); `maximumAngularNodes` the\n * angular nodes of either method (for the real-space method, all θ nodes of one wavelength).\n */\n quadrature: { method: \"polar-frequency-domain-trapezoid-gauss-legendre\" | \"real-space-encircled-energy-gauss-legendre\" | \"frequency-domain-and-real-space\";\n maximumRadialNodes: number; maximumAngularNodes: number; realSpaceWavelengthsNm?: readonly number[] };\n}\n\nconst EVIDENCE: readonly EvidenceProvenance[] = Object.freeze([Object.freeze({ sourceOrigin: \"photivra\" as const,\n sourceReference: \"photivra:diffraction-limited-psf-profile\", reuseStatus: \"photivra-owned\" as const })]);\nconst LIMITATIONS = Object.freeze([\n \"Scalar Fraunhofer diffraction of an ideal, uniformly lit circular pupil (Airy pattern); no aberration, apodization, polarization or stray light.\",\n \"Exactly in focus: no defocus or depth of field. Thin lens with unity pupil magnification; the pupil-to-image distance is the thin-lens image distance.\",\n \"The on-axis PSF is declared unchanged across the field (isoplanatic); no field-dependent aberration or pupil clipping.\",\n \"PSF energy is binned into square sample cells; the finite window misses the Airy tail energy listed per wavelength and is renormalized to unit sum.\",\n \"Wavelengths are used as given, in the declared basis, without air/vacuum conversion.\"\n]);\n\nfunction positive(value: unknown, name: string): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) throw new InvalidConfigurationError(name + \" must be a positive finite number.\");\n return value;\n}\n\n/** Gauss–Legendre nodes and weights on [0, 1] (Newton iteration on Pₙ; DLMF §3.5(v)). Cached per n. */\nconst GAUSS_LEGENDRE = new Map<number, { nodes: Float64Array; weights: Float64Array }>();\nfunction gaussLegendre(n: number): { nodes: Float64Array; weights: Float64Array } {\n const cached = GAUSS_LEGENDRE.get(n);\n if (cached) return cached;\n const nodes = new Float64Array(n), weights = new Float64Array(n);\n for (let k = 0; k < Math.ceil(n / 2); k++) {\n let x = Math.cos(Math.PI * (k + 0.75) / (n + 0.5));\n for (let iteration = 0; iteration < 100; iteration++) {\n let p0 = 1, p1 = x;\n for (let m = 2; m <= n; m++) { const p2 = ((2 * m - 1) * x * p1 - (m - 1) * p0) / m; p0 = p1; p1 = p2; }\n const step = p1 / (n * (x * p1 - p0) / (x * x - 1));\n x -= step;\n if (Math.abs(step) < 1e-16) break;\n }\n let p0 = 1, p1 = x;\n for (let m = 2; m <= n; m++) { const p2 = ((2 * m - 1) * x * p1 - (m - 1) * p0) / m; p0 = p1; p1 = p2; }\n const derivative = n * (x * p1 - p0) / (x * x - 1);\n const w = 1 / ((1 - x * x) * derivative * derivative);\n // Map [-1, 1] to [0, 1]: t = (1 ± x)/2, weight w (2/(1-x²)P'² halved).\n nodes[k] = (1 - x) / 2; weights[k] = w;\n nodes[n - 1 - k] = (1 + x) / 2; weights[n - 1 - k] = w;\n }\n const result = { nodes, weights };\n if (GAUSS_LEGENDRE.size < 64) GAUSS_LEGENDRE.set(n, result);\n return result;\n}\nfunction sinc(x: number): number {\n if (x === 0) return 1;\n const a = Math.PI * x;\n return Math.sin(a) / a;\n}\n\n/**\n * Cell energies K(i, j), 0 ≤ i, j ≤ R, of the Airy pattern for β = p·f_c (cell side in units of the cut-off\n * period): in the frequency domain below `AIRY_REAL_SPACE_MINIMUM_BETA`, in real space from it (2.5). `scale`\n * multiplies the node or panel counts (1 in production; the tests use 2 to check convergence).\n */\nexport function calculateAiryCellEnergies(beta: number, radius: number, scale = 1): { cells: Float64Array; radialNodes: number; angularNodes: number; realSpace: boolean } {\n // 2.6: a pure function of (β, R, scale), so results are memoized (most recently used first) and handed out as copies.\n // Tiles of one frame, and captures that change only the exposure or the scene, rebuild the same kernels.\n const key = beta + \"|\" + radius + \"|\" + scale, cached = AIRY_CELL_CACHE.get(key);\n if (cached) {\n AIRY_CELL_CACHE.delete(key); AIRY_CELL_CACHE.set(key, cached);\n return { ...cached, cells: cached.cells.slice() };\n }\n const result = beta >= AIRY_REAL_SPACE_MINIMUM_BETA ? { ...calculateAiryCellEnergiesInRealSpace(beta, radius, scale), radialNodes: 0, realSpace: true } :\n { ...calculateAiryCellEnergiesInFrequencyDomain(beta, radius, scale), realSpace: false };\n AIRY_CELL_CACHE.set(key, { ...result, cells: result.cells.slice() });\n if (AIRY_CELL_CACHE.size > AIRY_CELL_CACHE_ENTRIES) AIRY_CELL_CACHE.delete(AIRY_CELL_CACHE.keys().next().value!);\n return result;\n}\n/** Memoized cell energies (2.6): 256 entries of (R + 1)² values, at most 256 × 33² × 8 bytes ≈ 2.2 MB. */\nconst AIRY_CELL_CACHE_ENTRIES = 256;\nconst AIRY_CELL_CACHE = new Map<string, { cells: Float64Array; radialNodes: number; angularNodes: number; realSpace: boolean }>();\n\n/** Hankel asymptotic coefficients a_k(ν) of DLMF 10.17.1 for ν = 0 and 1, k = 0..15. */\nconst HANKEL = ((): { a0: Float64Array; a1: Float64Array } => {\n const a0 = new Float64Array(16), a1 = new Float64Array(16);\n a0[0] = 1; a1[0] = 1;\n for (let k = 1; k < 16; k++) {\n const odd = (2 * k - 1) * (2 * k - 1);\n a0[k] = a0[k - 1]! * (0 - odd) / (8 * k);\n a1[k] = a1[k - 1]! * (4 - odd) / (8 * k);\n }\n return { a0, a1 };\n})();\n/** Smallest argument of `besselSquareSum`: there the 16-term expansion's remainder is below 1e-17. */\nconst HANKEL_MINIMUM_ARGUMENT = 300;\n\n/**\n * J₀(x)² + J₁(x)² for x > 0. From x ≥ 300 by the Hankel expansion J_ν(x) = √(2/(πx))·(P_ν cos ω_ν − Q_ν sin ω_ν), ω_ν = x − νπ/2 − π/4,\n * P_ν = Σ(−1)^k a_2k(ν)/x^2k, Q_ν = Σ(−1)^k a_2k+1(ν)/x^2k+1 (DLMF 10.17.3), eight terms each. With ω₁ = ω₀ − π/2,\n * J₁ = √(2/(πx))·(P₁ sin ω₀ + Q₁ cos ω₀). Below 300 by Miller's backward recurrence J_{n−1} = (2n/x)·J_n − J_{n+1} (DLMF 10.6.1;\n * the algorithm, DLMF §3.6(iii)), started at an order above x where J_n is negligible, normalized by J₀ + 2·Σ J_2k = 1\n * (DLMF 10.12.4). Internal.\n */\nexport function besselSquareSum(x: number): number {\n if (!(x > 0) || !Number.isFinite(x)) throw new InvalidConfigurationError(\"J0 and J1 need a positive finite argument.\");\n if (x < HANKEL_MINIMUM_ARGUMENT) return besselSquareSumByRecurrence(x);\n const { a0, a1 } = HANKEL, inverse = 1 / x;\n let p0 = 0, q0 = 0, p1 = 0, q1 = 0, power = 1;\n for (let k = 0; k < 16; k++) {\n const sign = (k >> 1) % 2 === 0 ? 1 : -1;\n if (k % 2 === 0) { p0 += sign * a0[k]! * power; p1 += sign * a1[k]! * power; } else { q0 += sign * a0[k]! * power; q1 += sign * a1[k]! * power; }\n power *= inverse;\n }\n const omega = x - Math.PI / 4, c = Math.cos(omega), s = Math.sin(omega);\n const j0 = p0 * c - q0 * s, j1 = p1 * s + q1 * c;\n return 2 / (Math.PI * x) * (j0 * j0 + j1 * j1);\n}\n/**\n * Miller's backward recurrence for 0 < x < 300 (see `besselSquareSum`). The start order x + 10·∛x + 40 (even) lies far enough in the\n * region where J_n(x) falls faster than exponentially that the start values' error is below double precision at J₀ and J₁; the\n * values are rescaled when they grow large, and normalized before squaring.\n */\nfunction besselSquareSumByRecurrence(x: number): number {\n const start = 2 * Math.ceil((x + 10 * Math.cbrt(x) + 40) / 2);\n let next = 0, current = 1e-100, sum = 0, j1 = 0;\n for (let n = start; n >= 1; n--) {\n const previous = (2 * n / x) * current - next;\n next = current; current = previous;\n if (n - 1 === 1) j1 = current;\n if (n - 1 > 0 && (n - 1) % 2 === 0) sum += 2 * current;\n if (Math.abs(current) > 1e100) { current *= 1e-100; next *= 1e-100; sum *= 1e-100; j1 *= 1e-100; }\n }\n sum += current;\n const j0 = current / sum, j1n = j1 / sum;\n return j0 * j0 + j1n * j1n;\n}\n\n/**\n * Real-space cell energies (2.5): see the module comment. G(Z, φ) = ∫₀^φ [J₀² + J₁²](π·β·Z/cos θ) dθ for cell boundaries at\n * half-integer distances Z (in cells), accumulated along θ for each Z with every needed φ = atan(W/Z) as a panel boundary.\n * The rectangle [0, X] × [0, Y] holds 1/4 − (G(X, atan(Y/X)) + G(Y, atan(X/Y)))/(2π) of the energy. Internal.\n */\nexport function calculateAiryCellEnergiesInRealSpace(beta: number, radius: number, scale = 1): { cells: Float64Array; angularNodes: number } {\n const size = radius + 1, table = new Float64Array(size * size), { nodes, weights } = gaussLegendre(16);\n let angularNodes = 0;\n for (let z = 0; z < size; z++) {\n const Z = z + 0.5, k = Math.PI * beta * Z;\n let sum = 0, t0 = 1, theta0 = 0;\n for (let w = 0; w < size; w++) {\n // Panels equal in t = 1/cos θ, so the phase 2x = 2kt changes by at most 2 radians on each.\n const t1 = Math.hypot(Z, w + 0.5) / Z, theta1 = Math.atan2(w + 0.5, Z), panels = Math.max(1, Math.ceil(scale * k * (t1 - t0)));\n let a = theta0;\n for (let n = 1; n <= panels; n++) {\n const b = n === panels ? theta1 : Math.acos(1 / (t0 + (t1 - t0) * n / panels)), h = b - a;\n let panel = 0;\n for (let g = 0; g < 16; g++) panel += weights[g]! * besselSquareSum(k / Math.cos(a + h * nodes[g]!));\n sum += panel * h;\n a = b;\n }\n angularNodes += 16 * panels;\n table[z * size + w] = sum;\n t0 = t1; theta0 = theta1;\n }\n }\n // Boundaries of cell i along one axis as (half-integer index, weight): the centre cell is twice [0, 1/2].\n const bounds = (i: number): [number, number][] => i === 0 ? [[0, 2]] : [[i, 1], [i - 1, -1]];\n const cells = new Float64Array(size * size);\n for (let j = 0; j < size; j++) for (let i = 0; i < size; i++) {\n let constant = 0, leak = 0;\n for (const [a, wa] of bounds(i)) for (const [b, wb] of bounds(j)) {\n constant += wa * wb;\n leak += wa * wb * (table[a * size + b]! + table[b * size + a]!);\n }\n cells[j * size + i] = constant / 4 - leak / (2 * Math.PI);\n }\n return { cells, angularNodes };\n}\n\n/** Frequency-domain cell energies (2.1; see the module comment). Internal. */\nexport function calculateAiryCellEnergiesInFrequencyDomain(beta: number, radius: number, scale = 1): { cells: Float64Array; radialNodes: number; angularNodes: number } {\n // Bandwidth of the angular and radial integrands: c = π·β·(2R + 1)·√2 (the sinc factors and the highest cosine).\n const c = Math.PI * beta * (2 * radius + 1) * Math.SQRT2, margin = 10 * Math.cbrt(c) + 24;\n const quarter = Math.ceil(scale * (c + margin) / 4), radialNodes = Math.ceil(scale * (c + margin));\n const { nodes, weights } = gaussLegendre(radialNodes), size = radius + 1, cells = new Float64Array(size * size);\n const ca = new Float64Array(size), cb = new Float64Array(size), angularStep = (Math.PI / 2) / quarter;\n // cos θ and sin θ of every angular node, computed once with the same expressions as inside the loop (same values).\n const cosines = new Float64Array(quarter + 1), sines = new Float64Array(quarter + 1);\n for (let k = 0; k <= quarter; k++) { const theta = k * angularStep; cosines[k] = Math.cos(theta); sines[k] = Math.sin(theta); }\n for (let r = 0; r < radialNodes; r++) {\n const t = nodes[r]!, rho = 1 - t * t;\n // H(1 − t²) without cancellation: acos(1 − t²) = 2·asin(t/√2), ρ·√(1 − ρ²) = ρ·t·√(2 − t²).\n const otf = (2 / Math.PI) * (2 * Math.asin(t / Math.SQRT2) - rho * t * Math.sqrt(2 - t * t));\n // ∫₀¹ g(ρ) dρ = ∫₀¹ g(1 − t²)·2t dt; the 4 is the four symmetric quadrants of the angular trapezoid.\n const radialWeight = weights[r]! * 2 * t * rho * otf * 4 * angularStep;\n for (let k = 0; k <= quarter; k++) {\n const x = beta * rho * cosines[k]!, y = beta * rho * sines[k]!;\n const w = radialWeight * (k === 0 || k === quarter ? 0.5 : 1) * sinc(x) * sinc(y);\n // cos(2π·i·x) and cos(2π·j·y) by the Chebyshev recurrence.\n const cx = Math.cos(2 * Math.PI * x), cy = Math.cos(2 * Math.PI * y);\n ca[0] = 1; cb[0] = 1;\n if (size > 1) { ca[1] = cx; cb[1] = cy; }\n for (let i = 2; i < size; i++) { ca[i] = 2 * cx * ca[i - 1]! - ca[i - 2]!; cb[i] = 2 * cy * cb[i - 1]! - cb[i - 2]!; }\n for (let j = 0; j < size; j++) {\n const wj = w * cb[j]!;\n for (let i = 0; i < size; i++) { const o = j * size + i; cells[o] = cells[o]! + wj * ca[i]!; }\n }\n }\n }\n const area = beta * beta;\n for (let i = 0; i < cells.length; i++) cells[i] = cells[i]! * area;\n return { cells, radialNodes, angularNodes: 4 * quarter };\n}\n\n/**\n * Creates a sampled lens PSF profile for an ideal diffraction-limited lens with a circular pupil: one\n * unit-sum Airy kernel per wavelength on one shared (2R+1)² grid, with the truncated energy reported per\n * wavelength. The profile declares the given focal length, f-number and focus exactly, and the field axes\n * ±`fieldHalfExtentMm` with the same kernel at every field node.\n * @param input - See `CreateDiffractionLimitedPsfProfileInput`.\n * @returns The profile and its diffraction record. An approximation; not a measured lens.\n * @see docs/PSF_FOUNDATION.md, \"Diffraction-limited sampled profile (2.1)\".\n */\nexport function createDiffractionLimitedPsfProfile(input: CreateDiffractionLimitedPsfProfileInput): CalculationResult<DiffractionLimitedPsfProfile> {\n const r = requireAllowlistedRecord(input, [\"aperture\", \"focalLengthMm\", \"apertureFNumber\", \"focus\", \"wavelengthsNm\", \"samplePitchMicrometers\",\n \"kernelRadiusSamples\", \"fieldHalfExtentMm\", \"profileId\"], \"Invalid diffraction-limited PSF profile input.\");\n if (r.aperture !== \"circular\") throw new InvalidConfigurationError(\"aperture must be \\\"circular\\\".\");\n const focalLengthMm = positive(r.focalLengthMm, \"focalLengthMm\"), fNumber = positive(r.apertureFNumber, \"apertureFNumber\");\n const pitch = positive(r.samplePitchMicrometers, \"samplePitchMicrometers\");\n const L = DIFFRACTION_LIMITED_PSF_PROFILE_LIMITS;\n const radius = r.kernelRadiusSamples;\n if (typeof radius !== \"number\" || !Number.isSafeInteger(radius) || radius < 0 || radius > L.maximumKernelRadiusSamples) {\n throw new InvalidConfigurationError(\"kernelRadiusSamples must be an integer from 0 to \" + L.maximumKernelRadiusSamples + \".\");\n }\n const focus = requireAllowlistedRecord(r.focus, [\"kind\", \"distanceM\"], \"Invalid focus.\");\n let focusDiopters: number, distanceMm: number;\n if (focus.kind === \"infinity\") {\n if (focus.distanceM !== undefined) throw new InvalidConfigurationError(\"An infinity focus has no distanceM.\");\n focusDiopters = 0; distanceMm = focalLengthMm;\n } else if (focus.kind === \"finite\") {\n const distanceM = positive(focus.distanceM, \"focus.distanceM\");\n focusDiopters = 1 / distanceM;\n distanceMm = calculateThinLensImageDistance({ focalLengthMm, objectDistanceM: distanceM }).value.imageDistanceMm;\n } else throw new InvalidConfigurationError(\"focus.kind must be finite or infinity.\");\n const field = requireAllowlistedRecord(r.fieldHalfExtentMm, [\"x\", \"y\"], \"Invalid fieldHalfExtentMm.\");\n const fieldX = positive(field.x, \"fieldHalfExtentMm.x\"), fieldY = positive(field.y, \"fieldHalfExtentMm.y\");\n const list = r.wavelengthsNm;\n if (!Array.isArray(list) || list.length < 1 || list.length > L.maximumWavelengths) throw new InvalidConfigurationError(\"wavelengthsNm must list 1 to 64 wavelengths.\");\n const wavelengths = (list as unknown[]).map((value, index) => positive(value, \"wavelengthsNm[\" + index + \"]\"));\n for (let i = 1; i < wavelengths.length; i++) if (!(wavelengths[i]! > wavelengths[i - 1]!)) throw new InvalidConfigurationError(\"wavelengthsNm must be strictly increasing.\");\n if (typeof r.profileId !== \"string\" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(r.profileId)) throw new InvalidConfigurationError(\"profileId must be a public opaque ID.\");\n\n const pupilDiameterMm = focalLengthMm / fNumber, workingFNumber = distanceMm / pupilDiameterMm, size = 2 * radius + 1;\n let maximumRadialNodes = 0, maximumAngularNodes = 0, maximumTruncated = 0;\n const records: DiffractionLimitedPsfWavelength[] = [], kernels: number[][] = [], realSpaceWavelengthsNm: number[] = [];\n for (const nm of wavelengths) {\n const lambdaN = nm / 1000 * workingFNumber, cutoff = 1 / lambdaN;\n const airy = calculateAiryCellEnergies(pitch * cutoff, radius);\n if (airy.realSpace) realSpaceWavelengthsNm.push(nm);\n maximumRadialNodes = Math.max(maximumRadialNodes, airy.radialNodes); maximumAngularNodes = Math.max(maximumAngularNodes, airy.angularNodes);\n const values: number[] = new Array(size * size);\n let captured = 0;\n for (let ky = 0; ky < size; ky++) for (let kx = 0; kx < size; kx++) {\n const value = airy.cells[Math.abs(ky - radius) * (radius + 1) + Math.abs(kx - radius)]!;\n // Every cell energy of a non-negative PSF is positive; a negative value would be a quadrature failure.\n if (!(value > -1e-13)) throw new InvalidConfigurationError(\"Diffraction cell integration failed to converge.\");\n values[ky * size + kx] = Math.max(0, value);\n captured += values[ky * size + kx]!;\n }\n if (!(captured > 0 && captured <= 1 + 1e-9)) throw new InvalidConfigurationError(\"Diffraction cell integration failed to converge.\");\n const truncated = Math.max(0, 1 - captured);\n maximumTruncated = Math.max(maximumTruncated, truncated);\n const kernel = values.map((v) => v / captured);\n kernels.push(kernel);\n records.push({ wavelengthNm: nm, cutoffFrequencyCyclesPerMicrometer: cutoff, firstZeroRadiusMicrometers: AIRY_FIRST_ZERO_PER_LAMBDA_N * lambdaN,\n capturedEnergyFraction: captured, truncatedEnergyFraction: truncated, centreSampleFraction: kernel[radius * size + radius]! });\n }\n const uncertainty = { kind: \"not-quantified\" as const, limitation: \"Ideal diffraction-limited model; no measured lens or quadrature error bound.\" };\n const nodes = [-fieldX, fieldX].flatMap((fx) => [-fieldY, fieldY].flatMap((fy) => wavelengths.map((nm, index) => ({\n nodeId: \"airy-\" + (fx < 0 ? \"xm\" : \"xp\") + \"-\" + (fy < 0 ? \"ym\" : \"yp\") + \"-\" + index,\n coordinate: { focalLengthMm, focusDiopters, apertureFNumber: fNumber, fieldXmm: fx, fieldYmm: fy, wavelengthNm: nm, signedDefocusImagePlaneMicrometers: 0 },\n kernel: { widthSamples: size, heightSamples: size, samplePitchMicrometersX: pitch, samplePitchMicrometersY: pitch, centerSampleX: radius, centerSampleY: radius,\n normalizedIntensity: kernels[index]! },\n bestFocusImagePlaneOffsetMicrometers: 0, relativePupilThroughputFactor: 1, evidence: EVIDENCE, uncertainty }))));\n const profile = parseLensSampledPsfProfile({ schemaVersion: \"0.1.0\", profileId: r.profileId, profileVersion: DIFFRACTION_LIMITED_PSF_PROFILE_MODEL.modelVersion,\n scientificStatus: \"approximation\", opticalDomain: \"lens-primary-optical-path-only\", coordinateSystem: \"image-plane-metric\", fieldAxes: \"+X right, +Y up\",\n kernelEnergyNormalization: \"unit-energy-shape\", interpolation: \"bounded-regular-grid-multilinear\", throughputOwnership: \"separate-relative-pupil-throughput-factor\",\n responseIncludes: { diffraction: true, aberration: false, defocus: false, pupilClippingShape: false },\n sensorOpticalStackIncluded: false, sensorSamplingIncluded: false, reconstructionIncluded: false, strayLightIncluded: false,\n axes: { focalLengthMm: [focalLengthMm], focusDiopters: [focusDiopters], apertureFNumber: [fNumber], fieldXmm: [-fieldX, fieldX], fieldYmm: [-fieldY, fieldY],\n wavelengthNm: wavelengths, signedDefocusImagePlaneMicrometers: [0] },\n nodes, evidence: EVIDENCE, limitations: [...LIMITATIONS] });\n return approximationResult({ profile, model: DIFFRACTION_LIMITED_PSF_PROFILE_MODEL, pupilDiameterMm, pupilToImageDistanceMm: distanceMm, workingFNumber,\n kernelRadiusSamples: radius, samplePitchMicrometers: pitch, wavelengths: records, maximumTruncatedEnergyFraction: maximumTruncated,\n quadrature: realSpaceWavelengthsNm.length === 0 ? { method: \"polar-frequency-domain-trapezoid-gauss-legendre\" as const, maximumRadialNodes, maximumAngularNodes } :\n { method: realSpaceWavelengthsNm.length === wavelengths.length ? \"real-space-encircled-energy-gauss-legendre\" as const : \"frequency-domain-and-real-space\" as const,\n maximumRadialNodes, maximumAngularNodes, realSpaceWavelengthsNm } },\n DIFFRACTION_LIMITED_PSF_PROFILE_MODEL.modelId, DIFFRACTION_LIMITED_PSF_PROFILE_MODEL.modelVersion, [...LIMITATIONS]);\n}\n"]}
@@ -64,3 +64,14 @@ export declare function calculateIrradianceConvolution(input: IrradianceConvolut
64
64
  plan: IrradianceConvolutionPlan;
65
65
  values: Float64Array;
66
66
  };
67
+ /**
68
+ * Internal (#453; not exported from the package): runs several convolutions that share one geometry (width, height,
69
+ * kernel radius, patch size, boundary, pixel limit) with one validation of that geometry. The first call is
70
+ * `calculateIrradianceConvolution` itself, with every check. Each later call checks its own arrays exactly as the plan
71
+ * does (array types and lengths against the geometry, every sample, every kernel weight and kernel sum, with the same
72
+ * messages) and then runs the same gather (`convolveIrradianceRows`) with the first call's plan, whose geometry fields are
73
+ * the same numbers a full validation of this call would give. The values are therefore identical to one
74
+ * `calculateIrradianceConvolution` per call; only the repeated record and geometry checks are skipped.
75
+ * @internal
76
+ */
77
+ export declare function createIrradianceConvolutionRunner(geometry: Omit<IrradianceConvolutionInput, "samples" | "kernels">): (samples: Float32Array | Float64Array, kernels: Float32Array | Float64Array) => Float64Array;
@@ -49,6 +49,38 @@ function positiveInteger(value, maximum) {
49
49
  }
50
50
  return value;
51
51
  }
52
+ /** Checks every sample (finite, non-negative, within the declared magnitude) and returns the largest. */
53
+ function checkedMaximumSampleMagnitude(samples) {
54
+ let maximumSampleMagnitude = 0;
55
+ for (let i = 0; i < samples.length; i++) {
56
+ const v = samples[i];
57
+ // Negated comparison also rejects NaN.
58
+ if (!(v >= 0 && v <= IRRADIANCE_CONVOLUTION_LIMITS.maximumSampleMagnitude)) {
59
+ throw new InvalidConfigurationError("Irradiance samples must be finite, non-negative and within the declared magnitude.");
60
+ }
61
+ if (v > maximumSampleMagnitude)
62
+ maximumSampleMagnitude = v;
63
+ }
64
+ return maximumSampleMagnitude;
65
+ }
66
+ /** Checks every kernel weight (finite, in [0, 1]) and kernel sum (at most 1 plus slack) and returns the largest sum. */
67
+ function checkedMaximumKernelSum(kernels, patchCount, tapCount) {
68
+ let maximumKernelSum = 0;
69
+ for (let p = 0; p < patchCount; p++) {
70
+ let sum = 0;
71
+ for (let t = p * tapCount, end = t + tapCount; t < end; t++) {
72
+ const w = kernels[t];
73
+ if (!(w >= 0 && w <= 1))
74
+ throw new InvalidConfigurationError("PSF kernel weights must be finite and within [0, 1].");
75
+ sum += w;
76
+ }
77
+ if (sum > IRRADIANCE_CONVOLUTION_LIMITS.maximumKernelSum)
78
+ throw new InvalidConfigurationError("A PSF kernel may not create energy (sum exceeds 1).");
79
+ if (sum > maximumKernelSum)
80
+ maximumKernelSum = sum;
81
+ }
82
+ return maximumKernelSum;
83
+ }
52
84
  /** Validates the request and returns its plan. Every sample and weight is checked once. */
53
85
  export function calculateIrradianceConvolutionPlan(input) {
54
86
  const r = requireAllowlistedRecord(input, ["schemaVersion", "width", "height", "samples", "kernelRadius", "patchWidth",
@@ -76,30 +108,8 @@ export function calculateIrradianceConvolutionPlan(input) {
76
108
  !(kernels instanceof Float32Array || kernels instanceof Float64Array) || kernels.length !== patchCount * tapCount) {
77
109
  throw new InvalidConfigurationError("Irradiance convolution arrays do not match the declared geometry.");
78
110
  }
79
- let maximumSampleMagnitude = 0;
80
- for (let i = 0; i < samples.length; i++) {
81
- const v = samples[i];
82
- // Negated comparison also rejects NaN.
83
- if (!(v >= 0 && v <= L.maximumSampleMagnitude)) {
84
- throw new InvalidConfigurationError("Irradiance samples must be finite, non-negative and within the declared magnitude.");
85
- }
86
- if (v > maximumSampleMagnitude)
87
- maximumSampleMagnitude = v;
88
- }
89
- let maximumKernelSum = 0;
90
- for (let p = 0; p < patchCount; p++) {
91
- let sum = 0;
92
- for (let t = p * tapCount, end = t + tapCount; t < end; t++) {
93
- const w = kernels[t];
94
- if (!(w >= 0 && w <= 1))
95
- throw new InvalidConfigurationError("PSF kernel weights must be finite and within [0, 1].");
96
- sum += w;
97
- }
98
- if (sum > L.maximumKernelSum)
99
- throw new InvalidConfigurationError("A PSF kernel may not create energy (sum exceeds 1).");
100
- if (sum > maximumKernelSum)
101
- maximumKernelSum = sum;
102
- }
111
+ const maximumSampleMagnitude = checkedMaximumSampleMagnitude(samples);
112
+ const maximumKernelSum = checkedMaximumKernelSum(kernels, patchCount, tapCount);
103
113
  return Object.freeze({ width, height, kernelRadius: radius, kernelSize, tapCount, patchWidth, patchHeight, patchColumns, patchRows,
104
114
  maximumKernelSum, maximumSampleMagnitude });
105
115
  }
@@ -139,4 +149,34 @@ export function calculateIrradianceConvolution(input) {
139
149
  convolveIrradianceRows(plan, input.samples, input.kernels, 0, plan.height, values);
140
150
  return { plan, values };
141
151
  }
152
+ /**
153
+ * Internal (#453; not exported from the package): runs several convolutions that share one geometry (width, height,
154
+ * kernel radius, patch size, boundary, pixel limit) with one validation of that geometry. The first call is
155
+ * `calculateIrradianceConvolution` itself, with every check. Each later call checks its own arrays exactly as the plan
156
+ * does (array types and lengths against the geometry, every sample, every kernel weight and kernel sum, with the same
157
+ * messages) and then runs the same gather (`convolveIrradianceRows`) with the first call's plan, whose geometry fields are
158
+ * the same numbers a full validation of this call would give. The values are therefore identical to one
159
+ * `calculateIrradianceConvolution` per call; only the repeated record and geometry checks are skipped.
160
+ * @internal
161
+ */
162
+ export function createIrradianceConvolutionRunner(geometry) {
163
+ let plan;
164
+ return (samples, kernels) => {
165
+ if (plan === undefined) {
166
+ const first = calculateIrradianceConvolution({ ...geometry, samples, kernels });
167
+ plan = first.plan;
168
+ return first.values;
169
+ }
170
+ const patchCount = plan.patchColumns * plan.patchRows;
171
+ if (!(samples instanceof Float32Array || samples instanceof Float64Array) || samples.length !== plan.width * plan.height ||
172
+ !(kernels instanceof Float32Array || kernels instanceof Float64Array) || kernels.length !== patchCount * plan.tapCount) {
173
+ throw new InvalidConfigurationError("Irradiance convolution arrays do not match the declared geometry.");
174
+ }
175
+ checkedMaximumSampleMagnitude(samples);
176
+ checkedMaximumKernelSum(kernels, patchCount, plan.tapCount);
177
+ const values = new Float64Array(plan.width * plan.height);
178
+ convolveIrradianceRows(plan, samples, kernels, 0, plan.height, values);
179
+ return values;
180
+ };
181
+ }
142
182
  //# sourceMappingURL=irradiance-convolution.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"irradiance-convolution.js","sourceRoot":"","sources":["../../src/optics/irradiance-convolution.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Blurs a sampled image-plane quantity with caller-supplied PSF (point-spread function) kernels, one\n * kernel per patch of the image. The quantity must be linear and additive (for example irradiance).\n * Each patch is treated as shift-invariant (\"isoplanatic\"): the same PSF applies everywhere inside it.\n * The PSFs are incoherent intensity PSFs.\n *\n * This is the CPU float64 reference for the optics-as-convolution step (#277). The `webgpu-f32`\n * path runs the same equation; see docs/WEBGPU.md.\n *\n * Equation (indexed by source site; an energy-conserving scatter written as a gather):\n * out(x, y) = Σ_{v=-r..r} Σ_{u=-r..r} in(x − u, y − v) · K_{p(x − u, y − v)}(u, v)\n * where `p(s)` is the patch containing the **source** site and `K_p(u, v)` is the fraction of the source\n * site's quantity that lands at displacement (+u right, +v down), in grid sites.\n *\n * Incoherent imaging is a linear sum of intensity PSFs: the output intensity is the input intensity\n * convolved with the intensity PSF (MIT OpenCourseWare 2.71 Optics, Spring 2009, lecture 22, free).\n * A PSF that varies across the field is approximated as constant inside each patch, as in methods\n * that divide the image into isoplanatic subsections (van de Ketterij et al., arXiv:2011.01738,\n * sec. 1, free). Also see: Goodman, Introduction to Fourier Optics, 4th ed., 2017, §7.1. Camera\n * simulators such as ISETCam use the same piecewise shift-invariant method\n * (Farrell et al., Proc. SPIE 8299, 2012). The caller chooses the patch size and kernels, and so\n * owns the patch approximation.\n *\n * Edges: the grid holds every source. Sources outside the grid are not represented. Anything\n * scattered outside the grid is lost; compare `inputSum` with `outputSum` to see how much. There is no\n * padding, reflection or renormalization.\n */\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireAllowlistedRecord } from \"../core/record-validation.js\";\nimport { FULL_RESOLUTION_CEILING_PIXELS, resolveRaisedLimit } from \"../core/pixel-limits.js\";\n\nexport const IRRADIANCE_CONVOLUTION_SCHEMA_VERSION = \"0.1.0\" as const;\n/** `maximumPixels` is the default grid limit; a request may raise it with its own `maximumPixels`, up to `ceilingPixels`. */\nexport const IRRADIANCE_CONVOLUTION_LIMITS = Object.freeze({\n maximumPixels: 24_000_000,\n ceilingPixels: FULL_RESOLUTION_CEILING_PIXELS,\n maximumDimension: 16_384,\n maximumKernelRadius: 32,\n maximumPatchCount: 4_096,\n maximumKernelWeights: 4_194_304,\n /** A kernel may lose energy (vignetting, transmission) but may not create it, beyond rounding slack. */\n maximumKernelSum: 1 + 2 ** -20,\n /** Inputs larger than this are rejected so that float32 execution cannot overflow. */\n maximumSampleMagnitude: 2 ** 100\n});\n\nexport interface IrradianceConvolutionInput {\n schemaVersion: typeof IRRADIANCE_CONVOLUTION_SCHEMA_VERSION;\n width: number;\n height: number;\n /** Row-major samples. Finite, non-negative, linear and additive (for example irradiance, or expected photons per site). */\n samples: Float32Array | Float64Array;\n /** Kernel half-width in grid sites. Each kernel is (2r+1)×(2r+1). */\n kernelRadius: number;\n /** Patch size in grid sites. The last row and column of patches may be partial. */\n patchWidth: number;\n patchHeight: number;\n /**\n * Finite non-negative weights, `[patchRow][patchColumn][v + r][u + r]`, one kernel per patch in\n * row-major patch order. Each kernel sums to at most 1 (+2^-20 rounding slack).\n */\n kernels: Float32Array | Float64Array;\n boundary: \"zero-outside-grid\";\n /**\n * Optional opt-in grid limit for large images: an integer from 1 to `IRRADIANCE_CONVOLUTION_LIMITS.ceilingPixels`.\n * Default `IRRADIANCE_CONVOLUTION_LIMITS.maximumPixels` (24 000 000). It does not change the result.\n */\n maximumPixels?: number;\n}\n\n/** Validated, immutable geometry of a convolution request. Sample arrays are borrowed, not copied. */\nexport interface IrradianceConvolutionPlan {\n width: number;\n height: number;\n kernelRadius: number;\n kernelSize: number;\n tapCount: number;\n patchWidth: number;\n patchHeight: number;\n patchColumns: number;\n patchRows: number;\n /** Largest kernel sum and largest input magnitude. The float32 error bound uses them. */\n maximumKernelSum: number;\n maximumSampleMagnitude: number;\n}\n\nfunction positiveInteger(value: unknown, maximum: number): number {\n if (typeof value !== \"number\" || !Number.isSafeInteger(value) || value <= 0 || value > maximum) {\n throw new InvalidConfigurationError(\"Invalid irradiance convolution geometry.\");\n }\n return value;\n}\n\n/** Validates the request and returns its plan. Every sample and weight is checked once. */\nexport function calculateIrradianceConvolutionPlan(input: IrradianceConvolutionInput): IrradianceConvolutionPlan {\n const r = requireAllowlistedRecord(input, [\"schemaVersion\", \"width\", \"height\", \"samples\", \"kernelRadius\", \"patchWidth\",\n \"patchHeight\", \"kernels\", \"boundary\", \"maximumPixels\"], \"Invalid irradiance convolution input.\");\n const L = IRRADIANCE_CONVOLUTION_LIMITS;\n if (r.schemaVersion !== IRRADIANCE_CONVOLUTION_SCHEMA_VERSION || r.boundary !== \"zero-outside-grid\") {\n throw new InvalidConfigurationError(\"Unsupported irradiance convolution schema or boundary.\");\n }\n const width = positiveInteger(r.width, L.maximumDimension), height = positiveInteger(r.height, L.maximumDimension);\n const maximumPixels = resolveRaisedLimit(r.maximumPixels, L.maximumPixels, L.ceilingPixels,\n \"Irradiance convolution maximumPixels must be an integer from 1 to \" + L.ceilingPixels + \".\");\n if (width * height > maximumPixels) throw new InvalidConfigurationError(\"Irradiance convolution grid exceeds the pixel limit.\");\n const radius = r.kernelRadius;\n if (typeof radius !== \"number\" || !Number.isSafeInteger(radius) || radius < 0 || radius > L.maximumKernelRadius) {\n throw new InvalidConfigurationError(\"Invalid irradiance convolution kernel radius.\");\n }\n const patchWidth = positiveInteger(r.patchWidth, width), patchHeight = positiveInteger(r.patchHeight, height);\n const patchColumns = Math.ceil(width / patchWidth), patchRows = Math.ceil(height / patchHeight);\n const kernelSize = 2 * radius + 1, tapCount = kernelSize * kernelSize, patchCount = patchColumns * patchRows;\n if (patchCount > L.maximumPatchCount || patchCount * tapCount > L.maximumKernelWeights) {\n throw new InvalidConfigurationError(\"Irradiance convolution kernels exceed the declared limits.\");\n }\n const samples = r.samples, kernels = r.kernels;\n if (!(samples instanceof Float32Array || samples instanceof Float64Array) || samples.length !== width * height ||\n !(kernels instanceof Float32Array || kernels instanceof Float64Array) || kernels.length !== patchCount * tapCount) {\n throw new InvalidConfigurationError(\"Irradiance convolution arrays do not match the declared geometry.\");\n }\n let maximumSampleMagnitude = 0;\n for (let i = 0; i < samples.length; i++) {\n const v = samples[i]!;\n // Negated comparison also rejects NaN.\n if (!(v >= 0 && v <= L.maximumSampleMagnitude)) {\n throw new InvalidConfigurationError(\"Irradiance samples must be finite, non-negative and within the declared magnitude.\");\n }\n if (v > maximumSampleMagnitude) maximumSampleMagnitude = v;\n }\n let maximumKernelSum = 0;\n for (let p = 0; p < patchCount; p++) {\n let sum = 0;\n for (let t = p * tapCount, end = t + tapCount; t < end; t++) {\n const w = kernels[t]!;\n if (!(w >= 0 && w <= 1)) throw new InvalidConfigurationError(\"PSF kernel weights must be finite and within [0, 1].\");\n sum += w;\n }\n if (sum > L.maximumKernelSum) throw new InvalidConfigurationError(\"A PSF kernel may not create energy (sum exceeds 1).\");\n if (sum > maximumKernelSum) maximumKernelSum = sum;\n }\n return Object.freeze({ width, height, kernelRadius: radius, kernelSize, tapCount, patchWidth, patchHeight, patchColumns, patchRows,\n maximumKernelSum, maximumSampleMagnitude });\n}\n\n/**\n * Reference gather for output rows `[y0, y1)`, written into `output` at `(y - y0) * width + x`.\n * The summation order (v outer, u inner, both ascending) is part of the contract shared with the\n * WGSL (WebGPU shader) kernel.\n * @internal\n */\nexport function convolveIrradianceRows(plan: IrradianceConvolutionPlan, samples: Float32Array | Float64Array,\n kernels: Float32Array | Float64Array, y0: number, y1: number, output: Float64Array): void {\n const { width, height, kernelRadius: r, kernelSize, tapCount, patchWidth, patchHeight, patchColumns, patchRows } = plan;\n for (let y = y0; y < y1; y++) {\n for (let x = 0; x < width; x++) {\n let sum = 0;\n for (let v = -r; v <= r; v++) {\n const sy = y - v;\n if (sy < 0 || sy >= height) continue;\n const patchRowBase = Math.min(Math.floor(sy / patchHeight), patchRows - 1) * patchColumns;\n const kernelRow = (v + r) * kernelSize + r, sampleRow = sy * width;\n for (let u = -r; u <= r; u++) {\n const sx = x - u;\n if (sx < 0 || sx >= width) continue;\n const patch = patchRowBase + Math.min(Math.floor(sx / patchWidth), patchColumns - 1);\n sum += samples[sampleRow + sx]! * kernels[patch * tapCount + kernelRow + u]!;\n }\n }\n output[(y - y0) * width + x] = sum;\n }\n }\n}\n\n/** Synchronous float64 reference for bounded grids. Use the image-kernel accelerator for large grids. */\nexport function calculateIrradianceConvolution(input: IrradianceConvolutionInput): { plan: IrradianceConvolutionPlan; values: Float64Array } {\n const plan = calculateIrradianceConvolutionPlan(input);\n const values = new Float64Array(plan.width * plan.height);\n convolveIrradianceRows(plan, input.samples, input.kernels, 0, plan.height, values);\n return { plan, values };\n}\n"]}
1
+ {"version":3,"file":"irradiance-convolution.js","sourceRoot":"","sources":["../../src/optics/irradiance-convolution.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Blurs a sampled image-plane quantity with caller-supplied PSF (point-spread function) kernels, one\n * kernel per patch of the image. The quantity must be linear and additive (for example irradiance).\n * Each patch is treated as shift-invariant (\"isoplanatic\"): the same PSF applies everywhere inside it.\n * The PSFs are incoherent intensity PSFs.\n *\n * This is the CPU float64 reference for the optics-as-convolution step (#277). The `webgpu-f32`\n * path runs the same equation; see docs/WEBGPU.md.\n *\n * Equation (indexed by source site; an energy-conserving scatter written as a gather):\n * out(x, y) = Σ_{v=-r..r} Σ_{u=-r..r} in(x − u, y − v) · K_{p(x − u, y − v)}(u, v)\n * where `p(s)` is the patch containing the **source** site and `K_p(u, v)` is the fraction of the source\n * site's quantity that lands at displacement (+u right, +v down), in grid sites.\n *\n * Incoherent imaging is a linear sum of intensity PSFs: the output intensity is the input intensity\n * convolved with the intensity PSF (MIT OpenCourseWare 2.71 Optics, Spring 2009, lecture 22, free).\n * A PSF that varies across the field is approximated as constant inside each patch, as in methods\n * that divide the image into isoplanatic subsections (van de Ketterij et al., arXiv:2011.01738,\n * sec. 1, free). Also see: Goodman, Introduction to Fourier Optics, 4th ed., 2017, §7.1. Camera\n * simulators such as ISETCam use the same piecewise shift-invariant method\n * (Farrell et al., Proc. SPIE 8299, 2012). The caller chooses the patch size and kernels, and so\n * owns the patch approximation.\n *\n * Edges: the grid holds every source. Sources outside the grid are not represented. Anything\n * scattered outside the grid is lost; compare `inputSum` with `outputSum` to see how much. There is no\n * padding, reflection or renormalization.\n */\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireAllowlistedRecord } from \"../core/record-validation.js\";\nimport { FULL_RESOLUTION_CEILING_PIXELS, resolveRaisedLimit } from \"../core/pixel-limits.js\";\n\nexport const IRRADIANCE_CONVOLUTION_SCHEMA_VERSION = \"0.1.0\" as const;\n/** `maximumPixels` is the default grid limit; a request may raise it with its own `maximumPixels`, up to `ceilingPixels`. */\nexport const IRRADIANCE_CONVOLUTION_LIMITS = Object.freeze({\n maximumPixels: 24_000_000,\n ceilingPixels: FULL_RESOLUTION_CEILING_PIXELS,\n maximumDimension: 16_384,\n maximumKernelRadius: 32,\n maximumPatchCount: 4_096,\n maximumKernelWeights: 4_194_304,\n /** A kernel may lose energy (vignetting, transmission) but may not create it, beyond rounding slack. */\n maximumKernelSum: 1 + 2 ** -20,\n /** Inputs larger than this are rejected so that float32 execution cannot overflow. */\n maximumSampleMagnitude: 2 ** 100\n});\n\nexport interface IrradianceConvolutionInput {\n schemaVersion: typeof IRRADIANCE_CONVOLUTION_SCHEMA_VERSION;\n width: number;\n height: number;\n /** Row-major samples. Finite, non-negative, linear and additive (for example irradiance, or expected photons per site). */\n samples: Float32Array | Float64Array;\n /** Kernel half-width in grid sites. Each kernel is (2r+1)×(2r+1). */\n kernelRadius: number;\n /** Patch size in grid sites. The last row and column of patches may be partial. */\n patchWidth: number;\n patchHeight: number;\n /**\n * Finite non-negative weights, `[patchRow][patchColumn][v + r][u + r]`, one kernel per patch in\n * row-major patch order. Each kernel sums to at most 1 (+2^-20 rounding slack).\n */\n kernels: Float32Array | Float64Array;\n boundary: \"zero-outside-grid\";\n /**\n * Optional opt-in grid limit for large images: an integer from 1 to `IRRADIANCE_CONVOLUTION_LIMITS.ceilingPixels`.\n * Default `IRRADIANCE_CONVOLUTION_LIMITS.maximumPixels` (24 000 000). It does not change the result.\n */\n maximumPixels?: number;\n}\n\n/** Validated, immutable geometry of a convolution request. Sample arrays are borrowed, not copied. */\nexport interface IrradianceConvolutionPlan {\n width: number;\n height: number;\n kernelRadius: number;\n kernelSize: number;\n tapCount: number;\n patchWidth: number;\n patchHeight: number;\n patchColumns: number;\n patchRows: number;\n /** Largest kernel sum and largest input magnitude. The float32 error bound uses them. */\n maximumKernelSum: number;\n maximumSampleMagnitude: number;\n}\n\nfunction positiveInteger(value: unknown, maximum: number): number {\n if (typeof value !== \"number\" || !Number.isSafeInteger(value) || value <= 0 || value > maximum) {\n throw new InvalidConfigurationError(\"Invalid irradiance convolution geometry.\");\n }\n return value;\n}\n\n/** Checks every sample (finite, non-negative, within the declared magnitude) and returns the largest. */\nfunction checkedMaximumSampleMagnitude(samples: Float32Array | Float64Array): number {\n let maximumSampleMagnitude = 0;\n for (let i = 0; i < samples.length; i++) {\n const v = samples[i]!;\n // Negated comparison also rejects NaN.\n if (!(v >= 0 && v <= IRRADIANCE_CONVOLUTION_LIMITS.maximumSampleMagnitude)) {\n throw new InvalidConfigurationError(\"Irradiance samples must be finite, non-negative and within the declared magnitude.\");\n }\n if (v > maximumSampleMagnitude) maximumSampleMagnitude = v;\n }\n return maximumSampleMagnitude;\n}\n\n/** Checks every kernel weight (finite, in [0, 1]) and kernel sum (at most 1 plus slack) and returns the largest sum. */\nfunction checkedMaximumKernelSum(kernels: Float32Array | Float64Array, patchCount: number, tapCount: number): number {\n let maximumKernelSum = 0;\n for (let p = 0; p < patchCount; p++) {\n let sum = 0;\n for (let t = p * tapCount, end = t + tapCount; t < end; t++) {\n const w = kernels[t]!;\n if (!(w >= 0 && w <= 1)) throw new InvalidConfigurationError(\"PSF kernel weights must be finite and within [0, 1].\");\n sum += w;\n }\n if (sum > IRRADIANCE_CONVOLUTION_LIMITS.maximumKernelSum) throw new InvalidConfigurationError(\"A PSF kernel may not create energy (sum exceeds 1).\");\n if (sum > maximumKernelSum) maximumKernelSum = sum;\n }\n return maximumKernelSum;\n}\n\n/** Validates the request and returns its plan. Every sample and weight is checked once. */\nexport function calculateIrradianceConvolutionPlan(input: IrradianceConvolutionInput): IrradianceConvolutionPlan {\n const r = requireAllowlistedRecord(input, [\"schemaVersion\", \"width\", \"height\", \"samples\", \"kernelRadius\", \"patchWidth\",\n \"patchHeight\", \"kernels\", \"boundary\", \"maximumPixels\"], \"Invalid irradiance convolution input.\");\n const L = IRRADIANCE_CONVOLUTION_LIMITS;\n if (r.schemaVersion !== IRRADIANCE_CONVOLUTION_SCHEMA_VERSION || r.boundary !== \"zero-outside-grid\") {\n throw new InvalidConfigurationError(\"Unsupported irradiance convolution schema or boundary.\");\n }\n const width = positiveInteger(r.width, L.maximumDimension), height = positiveInteger(r.height, L.maximumDimension);\n const maximumPixels = resolveRaisedLimit(r.maximumPixels, L.maximumPixels, L.ceilingPixels,\n \"Irradiance convolution maximumPixels must be an integer from 1 to \" + L.ceilingPixels + \".\");\n if (width * height > maximumPixels) throw new InvalidConfigurationError(\"Irradiance convolution grid exceeds the pixel limit.\");\n const radius = r.kernelRadius;\n if (typeof radius !== \"number\" || !Number.isSafeInteger(radius) || radius < 0 || radius > L.maximumKernelRadius) {\n throw new InvalidConfigurationError(\"Invalid irradiance convolution kernel radius.\");\n }\n const patchWidth = positiveInteger(r.patchWidth, width), patchHeight = positiveInteger(r.patchHeight, height);\n const patchColumns = Math.ceil(width / patchWidth), patchRows = Math.ceil(height / patchHeight);\n const kernelSize = 2 * radius + 1, tapCount = kernelSize * kernelSize, patchCount = patchColumns * patchRows;\n if (patchCount > L.maximumPatchCount || patchCount * tapCount > L.maximumKernelWeights) {\n throw new InvalidConfigurationError(\"Irradiance convolution kernels exceed the declared limits.\");\n }\n const samples = r.samples, kernels = r.kernels;\n if (!(samples instanceof Float32Array || samples instanceof Float64Array) || samples.length !== width * height ||\n !(kernels instanceof Float32Array || kernels instanceof Float64Array) || kernels.length !== patchCount * tapCount) {\n throw new InvalidConfigurationError(\"Irradiance convolution arrays do not match the declared geometry.\");\n }\n const maximumSampleMagnitude = checkedMaximumSampleMagnitude(samples);\n const maximumKernelSum = checkedMaximumKernelSum(kernels, patchCount, tapCount);\n return Object.freeze({ width, height, kernelRadius: radius, kernelSize, tapCount, patchWidth, patchHeight, patchColumns, patchRows,\n maximumKernelSum, maximumSampleMagnitude });\n}\n\n/**\n * Reference gather for output rows `[y0, y1)`, written into `output` at `(y - y0) * width + x`.\n * The summation order (v outer, u inner, both ascending) is part of the contract shared with the\n * WGSL (WebGPU shader) kernel.\n * @internal\n */\nexport function convolveIrradianceRows(plan: IrradianceConvolutionPlan, samples: Float32Array | Float64Array,\n kernels: Float32Array | Float64Array, y0: number, y1: number, output: Float64Array): void {\n const { width, height, kernelRadius: r, kernelSize, tapCount, patchWidth, patchHeight, patchColumns, patchRows } = plan;\n for (let y = y0; y < y1; y++) {\n for (let x = 0; x < width; x++) {\n let sum = 0;\n for (let v = -r; v <= r; v++) {\n const sy = y - v;\n if (sy < 0 || sy >= height) continue;\n const patchRowBase = Math.min(Math.floor(sy / patchHeight), patchRows - 1) * patchColumns;\n const kernelRow = (v + r) * kernelSize + r, sampleRow = sy * width;\n for (let u = -r; u <= r; u++) {\n const sx = x - u;\n if (sx < 0 || sx >= width) continue;\n const patch = patchRowBase + Math.min(Math.floor(sx / patchWidth), patchColumns - 1);\n sum += samples[sampleRow + sx]! * kernels[patch * tapCount + kernelRow + u]!;\n }\n }\n output[(y - y0) * width + x] = sum;\n }\n }\n}\n\n/** Synchronous float64 reference for bounded grids. Use the image-kernel accelerator for large grids. */\nexport function calculateIrradianceConvolution(input: IrradianceConvolutionInput): { plan: IrradianceConvolutionPlan; values: Float64Array } {\n const plan = calculateIrradianceConvolutionPlan(input);\n const values = new Float64Array(plan.width * plan.height);\n convolveIrradianceRows(plan, input.samples, input.kernels, 0, plan.height, values);\n return { plan, values };\n}\n\n/**\n * Internal (#453; not exported from the package): runs several convolutions that share one geometry (width, height,\n * kernel radius, patch size, boundary, pixel limit) with one validation of that geometry. The first call is\n * `calculateIrradianceConvolution` itself, with every check. Each later call checks its own arrays exactly as the plan\n * does (array types and lengths against the geometry, every sample, every kernel weight and kernel sum, with the same\n * messages) and then runs the same gather (`convolveIrradianceRows`) with the first call's plan, whose geometry fields are\n * the same numbers a full validation of this call would give. The values are therefore identical to one\n * `calculateIrradianceConvolution` per call; only the repeated record and geometry checks are skipped.\n * @internal\n */\nexport function createIrradianceConvolutionRunner(geometry: Omit<IrradianceConvolutionInput, \"samples\" | \"kernels\">):\n (samples: Float32Array | Float64Array, kernels: Float32Array | Float64Array) => Float64Array {\n let plan: IrradianceConvolutionPlan | undefined;\n return (samples, kernels) => {\n if (plan === undefined) {\n const first = calculateIrradianceConvolution({ ...geometry, samples, kernels });\n plan = first.plan;\n return first.values;\n }\n const patchCount = plan.patchColumns * plan.patchRows;\n if (!(samples instanceof Float32Array || samples instanceof Float64Array) || samples.length !== plan.width * plan.height ||\n !(kernels instanceof Float32Array || kernels instanceof Float64Array) || kernels.length !== patchCount * plan.tapCount) {\n throw new InvalidConfigurationError(\"Irradiance convolution arrays do not match the declared geometry.\");\n }\n checkedMaximumSampleMagnitude(samples);\n checkedMaximumKernelSum(kernels, patchCount, plan.tapCount);\n const values = new Float64Array(plan.width * plan.height);\n convolveIrradianceRows(plan, samples, kernels, 0, plan.height, values);\n return values;\n };\n}\n"]}
@@ -212,6 +212,19 @@ export declare function parseSceneToSensorIrradianceProfile(value: unknown): Sce
212
212
  * factor 1/(1 + m)² from it, so the two cannot drift apart.
213
213
  */
214
214
  export declare function thinLensWorkingFNumberFactor(focalLengthMm: number, objectDistanceM: number): number;
215
+ /**
216
+ * Internal (#453; not exported from the package): the sensor-plane spectral irradiance that
217
+ * {@link calculateSceneRadianceToSensorIrradiance} returns, for repeated queries of one optical state. This call runs the
218
+ * request-independent checks once (the public function's own code, in its order); each call of the returned function then
219
+ * runs the public function's per-query steps on an engine-built request and a result already parsed by the caller: the identity
220
+ * match, the wavelength-basis check, the transmission or T-stop path, the front-of-lens filters, the field throughput and
221
+ * the finite, non-negative irradiance, with the same operations, order and errors. It skips only the re-validation of the
222
+ * unchanged optical inputs, the re-parse of the request and result, and the result envelope.
223
+ */
224
+ export declare function prepareSceneRadianceToSensorIrradianceValue(optics: Pick<CalculateSceneRadianceToSensorIrradianceInput, "profile" | "focalLengthMm" | "nominalFNumber" | "focus" | "frontOfLensFilters">): (request: SceneRadianceEvaluationRequest, result: SceneRadianceEvaluationResult, imagePointMm: {
225
+ x: number;
226
+ y: number;
227
+ }, fieldThroughput: OpticalBridgeFieldThroughput) => number;
215
228
  /**
216
229
  * Applies the declared pupil acceptance and exactly one transmission or field-throughput path to
217
230
  * spectral scene radiance. Returns the irradiance before the sensor stack, with conservative