Modelling the collection aperture#
The chromatic focal mixture sums incoherently over focal-plane position. By Parseval that is exact only if the detector collects the whole signal beam — and most non-collinear instruments do not: the signal is picked out of the BOXCARS pattern by a hole, which is a filter in transverse wavevector. The field must then be transformed into the collection plane before taking its intensity.
When the hole is narrow the average over the focal spot is partly coherent, and a coherent sum sharpens where an incoherent one blurs. A finite aperture changes both the spectrum and the delay structure of the trace; it does not act as a narrower version of the same smearing kernel.
Practical rule. For near-single-cycle pulses through a restrictive aperture,
retrieve with the full model: focal= with collection= set to the instrument’s
aperture. In the companion papers’ simulated TG-FROG instrument this recovers a
1.05 fs pulse to within 3 % at every substrate thickness from 1 to 40 µm through a
0.5 mm collection hole, and its fixed-field trace predictions agree to 0.03–0.2 %
rms of peak (validation). The alternative is to open
the aperture until the extended model’s full-collection premise holds, as far as
pump leakage allows.
When it matters#
Compare two numbers at the carrier. On this page \(\omega\) is the absolute optical angular frequency; \(\Omega=\omega-\omega_0\) is the centred frequency of croak’s grids.
quantity |
expression |
reference instrument at 260 nm |
|---|---|---|
signal k footprint |
\(3(\omega/c)(D/2)/f\) |
3.63e5 rad/m |
aperture k half-width |
\((\omega/c)(D_\text{hole}/2)/z_\text{mask}\) |
6.04e4 rad/m |
If the aperture is comparable to or wider than the footprint, the incoherent sum is adequate. Here it is six times narrower — about 3 % of the solid angle — so the instrument sits far closer to the coherent limit than to the incoherent one, and the collection model is not a refinement.
The other way to see it: a disc of k half-width \(\kappa_a\) correlates the focal plane over an Airy kernel whose first null is at \(3.83/\kappa_a \approx 63\,\mu\text{m}\), while the region that actually emits (where the arrival offsets \(p, \delta\) stay inside the pulse duration) is only 15–30 µm across. The detector cannot resolve the smearing it is supposed to average over.
The quick version#
from croak.collection import mask_hole_aperture
from croak.focal import focal_mixture
aperture = mask_hole_aperture(
hole_x=-1.0e-3, hole_y=-1.0e-3, # collection hole centre, mask plane (m)
hole_diameter=0.5e-3, # its diameter (m)
z_mask=0.1, # mask plane distance from the focus (m)
apod="tanh", apod_param=96.9e-6, # the edge, in mask-plane metres
)
# A pnps window (compact raised-cosine edge used as a QUADRATURE weight over the
# nominal hard hole; the weight enters the collected energy once, not squared):
# mask_hole_aperture(..., apod="rcos", apod_param=64.6e-6, weighting="quadrature").
# aperture_from_scan() picks both up from the file's window_def_* record.
mixture = focal_mixture(
hole_diameter=1.0e-3, # the INPUT mask holes
hole_spacing=1.0e-3,
f_foc=0.1,
wavelength=260e-9,
collection=aperture,
)
res = croak.retrieve(
trace, g.omega, delays, "pg",
algorithm="lbfgs-ad", focal=mixture, omega0=omega0,
)
focal= is accepted by lbfgs-ad, warm-lbfgs, lm, lm-optx,
lbfgs-optx and cma-es, and the collection aperture is part of the mixture configuration. Leaving collection unset keeps the full-beam incoherent sum
of local intensities.
The same object passes through the high-level entry points: both
croak.pipeline.retrieve_from_tracedata(td, ..., focal=mixture) and
croak.session.pipeline.run_retrieval(params, td, focal=mixture) forward it to the
solver (and raise for a solver that cannot model it, exactly as smearing= does).
Build the params with smearing=False — the mixture replaces the reduced kernel,
and asking for both is refused.
From the GUI and from RetrieveParams#
RetrieveParams carries the mixture as scalars — focal (the switch),
focal_n_radial / focal_n_azimuth / focal_rmax_units (the node grid),
focal_f_mm (the focusing focal length, which the reduced kernel never needs
but the explicit mixture does; read from a simulated scan’s geometry record on
load), collection ("off" / "file" / "manual"), collection_mode
("integrated" / "reimaged"), collection_diam_mm (manual mode: a
hard-edged pinhole at the phase-matched corner) and spectrum_frame_p (the
spectral-frame exponent — the mixture’s field is the on-axis one, bluer than
an integrated measured spectrum; the exponent reweights the initial guess and
the reg_spectrum target together into that frame). The GUI exposes all of
these in the Chromatic focal mixture (advanced) group of the retrieve stage,
mutually exclusive with the reduced-kernel checkbox, and
croak.session.pipeline.focal_mixture_from_params(params, td, scan_path)
builds the object — run_retrieval calls it automatically when params.focal
is set and no explicit focal= is given (scan_path is only needed for
collection="file", which rebuilds the aperture from the scan’s own
window_def_* record).
Reading the aperture off a simulated scan#
A scansave file records the window it was collected through, so it does not have to be
retyped (and mistyped):
from croak.collection import aperture_from_scan
aperture = aperture_from_scan("scan.h5") # the Iω_win window
reimaged = aperture_from_scan("scan.h5", mode="reimaged") # Iω_win_reimaged
This resolves the simulator’s "default" apodisation width from the file’s own
transverse k-grid, which matters: for a soft-edged window it can be 96.9 µm on a
500 µm hole — an edge 19 % of the diameter, passing 0.994 at the centre and still
0.006 at \(r = D\). Treating that window as a hard hole is a large error, not a rounding
one. Other scans record a hard-hole integration weight instead, so read the window
from each file.
MaskWindowSpec is the raw record, and
SimulatedScan.mask_window carries it.
The two collection models#
Both are functionals of the same transformed field \(\tilde S(\boldsymbol\kappa,\omega)\),
selected with mode=:
"integrated"\(\sum_j \mathrm dk_j\,W_j^2\,|\tilde S_j|^2/(2\pi)^2\) — a spectrometer fed by everything the hole passes. The reference simulator’s
Iω_win, and the usual case."reimaged"\(\bigl|\sum_j \mathrm dk_j\,W_j\,\tilde S_j/(2\pi)^2\bigr|^2\) — the windowed field back at the focal origin, i.e. a spectrometer fed by an on-axis re-imaged pixel. The fully coherent limit (
Iω_win_reimaged).
For a physical amplitude transmission \(W\), the integrated observable uses
\(|W|^2\) and the reimaged observable uses \(W\) inside the coherent sum. A numerical
hard-hole quadrature weight is different: it approximates an area integral
and enters once. weighting="quadrature" records that distinction. Squaring a
quadrature taper as though it were a physical transmission changes the aperture.
Where the aperture has to be centred#
At the degenerate carrier, the paraxial geometry places the selected signal along \(\mathbf k_1+\mathbf k_2-\mathbf k_3\), which maps back to the mask position \(\mathbf r_s=\mathbf r_1+\mathbf r_2-\mathbf r_3\) — the fourth BOXCARS corner. In the carrier-removed frame the aperture therefore sits at
which is zero when the instrument collects on the phase-matched direction, and grows
with \(\omega\) otherwise: the aperture is chromatic, and so is any misalignment of it.
aperture_offset() reports
\(\mathbf x_\text{hole}-\mathbf r_s\) in metres, so a permuted arm order or a hole on the
wrong corner shows up as a number rather than as a mysteriously dark trace:
import numpy as np
from croak.collection import aperture_offset
assert np.abs(aperture_offset(mixture)).max() < 1e-9 # collecting on phase matching
focal_mixture() records its arms because the incoherent sum
needed only the arm differences that make \((p,\delta)\), but the aperture needs to
know where the signal actually goes.
For broadband mixing, the carrier’s fourth-corner construction does not make every participating frequency triplet exactly phase matched. Absolute optical frequency determines aperture mapping and its area Jacobian. The spatial phase restored from the local timing reduction uses baseband frequency \(\Omega\).
Geometric clearance#
For holes of input diameter \(D\) at \((\pm d,\pm d)\), adjacent centres are separated by \(2d\). A circular collection hole centred at the signal corner avoids overlap with an adjacent input hole only if
This is geometric clearance, not a guarantee against diffracted pump leakage or aberrations. Increasing collection must be tested against the actual background.
Cost and quadrature#
The reduction is one \(J\times K\) matrix product per \((\omega,\tau)\) against the \(K\) field builds already being done. Measured at production shape (K = 384 mixture nodes, J = 144 aperture nodes, 256 frequencies, 230 delays) it costs +5 % of runtime. Memory is \(16JK N_\omega\) bytes of precomputed transform matrix — 56 MB there — so the two node counts multiply and both are worth keeping modest.
One warning. The coherent integrand is not the incoherent one, so a mixture
quadrature converged for the \(|A|^6\) weight need not be converged here: on the reference
geometry n_azimuth=8 costs 4e-5 of peak where the incoherent sum is converged, against
3e-7 at the focal_mixture default of 16. If you have been running a coarse azimuthal
sampling, raise it before adding an aperture.
Where it stops being right: the depth integral#
The entrance-profile model assigns each depth the same transverse profile. This neglects input diffraction, beam walk and changes in local overlap. The exit signal also acquires a transverse propagation phase. These are separate approximations; the options below address different parts of them.
With the entrance-profile model and the instrument’s collection aperture, the full model already retrieves the simulated 1–40 µm series accurately (see validation). The options below are opt-in refinements for thicker slabs or tighter focusing; refine the depth and transverse quadratures for the field and geometry being modelled.
depth_transverse: modelling the missing decoherence#
The exit propagation phase can be included without adding a fitted parameter. Signal generated at depth \(z\) accumulates, over the remaining slab, the transverse phase the on-axis propagator \(e^{i\beta(L-z)}\) leaves out,
evaluated at each aperture node’s absolute transverse wavevector \(k_{\perp j} = \omega\rho_j/(c\,z_{\text{mask}})\) (mask positions with the hole centre folded in — the quadratic \(k_z\) is not invariant under the carrier removal, and the remainder is physical). At the reference instrument’s signal k-extent this reaches ~0.07 rad across a 40 µm slab — small, but it sits inside a coherent sum. The flag moves the depth sum inside the collection transform, each depth carrying \(e^{i\phi_j(\omega,z_q)}\) before the coherent sum over depth:
fn = make_param_trace_fn(
omega, delays, "pg",
material="SiO2", thickness=40e-6, npoints=10, omega0=omega0,
focal=mixture, # must carry a collection aperture
depth_transverse=True,
)
The same keyword is accepted by LBFGSAD (and therefore by
retrieve(..., algorithm="lbfgs-ad", depth_transverse=True, ...)); solvers that cannot
model it refuse loudly. Notes:
Requires
focalwith acollectionand a dispersive slab. For a full-collection variant there is deliberately no separate path: use an"integrated"window much wider than the signal footprint (a large chromatic hole, or achromatic=Falsewindow covering the whole k grid — with the flag off that route reproducescollection=Noneexactly, by Parseval).Assumptions. The full \(k_z\) square root is used (no paraxial expansion) with the full material index \(n(\omega)\); the input beams still propagate on-axis — only the generated signal’s exit propagation is corrected. The input-side repair (evolved, walked, per-arm filters) is the depth-resolved mixture below, and the two compose.
Cost. One aperture contraction per depth node instead of one total: ≈2.5× the collected trace at production shape (K = 96, J = 144, 256 frequencies, 230 delays, Q = 10 depth nodes; 0.61 s against 0.24 s per evaluation on an M-series CPU). Memory is unchanged — the per-depth transform is accumulated, never materialised.
Validation. The dense 2-D FFT reference test was extended with per-depth exact \(k_z(\omega,k_\perp)\) propagation (
test_matches_a_dense_fourier_reference_with_depth): the flag improves agreement with the exact reference by 25–91× (to 1.4–5.7×10⁻⁵ of peak), while a flipped sign would be worse than no correction at all — the sign is test-pinned, not argued. With a single depth node the phase is a per-node constant:"integrated"is provably unchanged (a unit-modulus factor dies in \(|\tilde S_j|^2\)) while"reimaged"shifts — asserted intests/test_depth_transverse.py, pinning where the phase sits relative to the modulus.
These tests check the calculation; check agreement with your instrument by comparing the predicted trace at a fixed known field.
fc-z: the depth-resolved mixture (evolve_profiles)#
depth_transverse changes exit propagation. The depth-resolved mixture also
evolves each input beamlet profile through the slab about its walked axis:
mixture = focal_mixture(
hole_diameter=1e-3, hole_spacing=1e-3, f_foc=0.1, wavelength=260e-9,
collection=aperture, # optional, composes as usual
evolve_profiles=True, # fc-z
material="SiO2", thickness=40e-6, npoints=10, # MUST match the trace map
)
fn = make_param_trace_fn(
omega, delays, "pg",
material="SiO2", thickness=40e-6, npoints=10, omega0=omega0,
focal=mixture,
)
Each arm \(j\) at node \(\mathbf r_k\) and depth \(z_q\) then applies its own evolved complex profile \(A_j(|\mathbf r_k - \bm\delta_j(z_q)|, \omega; z_q)\) — the aperture’s k-space disc propagated by the transverse part of \(k_z\) and evaluated at the in-glass walked radius \(\bm\delta_j(z) = -z\,\mathbf r_j/(f n_0)\) — in place of the single shared entrance filter. See the forward-model explanation for the formula and conventions. Notes:
The slab parameters on the mixture must match the trace map’s (
material/thickness/npoints/quadrature) — the per-depth filters must sit on the same depth nodes as the propagation phases; a mismatch raises.PG only;
fit_thicknessis not supported;depth_transversecomposes orthogonally (default off).Cost ≈ fc. The focal path already batch-FFTs over the depth axis, so the per-depth filters slot into the existing \((N, Q)\) multiply; new cost is a seconds-level numpy build of the \((3, K, Q, N)\) filter table at model construction (~10–50 MB at production shapes).
Validation. The evolved profile matches an independent plane-wave propagator to 10⁻⁶ of peak, a zero-evolution table reproduces the entrance filter exactly, and the Gaussian control is a null (
tests/test_fcz.py; see the developer validation guide).
Supported interactions#
The phase the incoherent sum discards is \(\Omega p\) for PG and \(-\Omega\delta\) for SD — in both cases the centred frequency \(\Omega\) times one of the two reduced smearing parameters, so the \((p,\delta)\) reduction is sufficient for an apertured instrument and not just a fully collected one. Only the PG case is derived and validated, so an SD interaction with a collection aperture raises rather than quietly using the wrong sign.
\(\Omega\) is the centred (envelope) frequency while the aperture’s own position
scales with the absolute one \(\omega_\text{abs}=\omega_0+\Omega\), so the two must be
kept distinct — see the warning in
croak.collection.
Checking it#
Two properties are worth asserting on your own geometry, and both are in
tests/test_collection.py: with an unbounded aperture on a Cartesian focal grid the
model must return the incoherent sum exactly (Parseval), and a dense 2-D FFT of the
focal field windowed in k must reproduce the quadrature model with no fitted scale.
Then increase the focal, aperture and depth quadratures until the trace stops
changing. The reference tests are described in
numerical validation.