Grids, wavelength ranges and the SHG carrier#
Getting the grid and wavelength ranges right is the most common source of confusion in FROG retrieval — especially for SHG, whose trace lives at a different carrier than the pulse. This guide collects the rules.
Three domains#
Keep three things distinct:
The raw measurement — a wavelength axis and a delay/position axis, non-uniform, possibly noisy. The delay filtering is done with a non-uniform FFT, so an unevenly sampled delay axis is handled correctly without first resampling it.
The retrieval grid — a uniform, centred angular-frequency / time grid (
croak.grid.Grid) that the solvers run on.load_and_clean()builds it fromlam_min/lam_max.Post-retrieval filtering — optional windows applied to the result (
croak.processing.post_filter()), which never change the retrieval itself.
Building a grid directly#
A croak.grid.Grid is fixed by its size n and one step (dt or domega); the
matched axes follow from \(\Delta\omega = 2\pi/(\Delta t\,N)\):
g = croak.Grid(128, dt=0.3e-15) # 128 points, 0.3 fs step
g.omega, g.t # centred axes (zero in the middle)
To size a grid from physical ranges instead, use
gridparams_omega() (or gridparams_lambda()),
which picks dt from the Nyquist limit of the largest frequency offset and n
from the required time window, rounded to an FFT-friendly length:
n, dt, omega0 = croak.gridparams_lambda(
trange=200e-15, lambda_min=720e-9, lambda_max=900e-9)
g = croak.Grid(n, dt=dt)
The baseband convention#
croak works with the baseband spectrum on a grid centred at zero — the carrier \(\omega_0\) is not baked into the pulse shape. It enters the conversion to absolute optical frequencies, including:
material dispersion, wavelength-dependent focal fields and collection optics, through
omega0;the wavelength axis of a result, via
RetrievalResult.wavelength\(= 2\pi c/(\omega+\omega_0)\).
So a 2.5 fs pulse at 800 nm and the same envelope at 250 nm share the same
baseband omega; they differ only in omega0. See
Conventions.
The SHG carrier doubling#
Each interaction records where its signal sits
via omega0_scale:
Interaction |
|
Trace carrier |
|---|---|---|
SHG |
2.0 |
\(2\omega_0\) (≈ half the fundamental wavelength) |
SD, PG |
1.0 |
\(\omega_0\) (same band as the fundamental) |
load_and_clean uses this automatically: it builds the pulse grid from
lam_min/lam_max (the fundamental band you want to retrieve) and places the
trace at omega0_pulse * omega0_scale. The cleaned trace, the carrier
frequencies and the regridded delays all end up on the
TraceData.
Worked SHG example
A pulse spanning 730–900 nm produces an SHG-FROG trace near 365–450 nm. Pass the fundamental range to the retrieval grid:
td = croak.load_and_clean(Ifrog, lam_frog, delays, "shg",
lam_min=730e-9, lam_max=900e-9, input_unit="delay")
td.omega0_pulse is the fundamental carrier; td.omega0_trace = 2 * omega0_pulse
is where the measured trace lives. For PG/SD the two coincide.
Choose the band on a log scale, not a linear one
lam_min/lam_max apply a Planck taper, so anything outside them is gone. A
FROG trace carries real information in its wings — that is where the chirp shows
itself — and they are easy to underestimate: on a linear plot the signal looks
finished long before it is.
Pick the edges from the wavelength marginal at roughly 1e-4 of its peak (−40 dB), or read them off a dB image of the trace, and stop at the noise floor if it comes first. For SHG, convert the signal band to the fundamental band before setting the retrieval grid.
marg = trace.sum(axis=1)
keep = np.where(marg > 1e-4 * marg.max())[0]
lam_min, lam_max = lam[keep[0]], lam[keep[-1]]
Widening the band also shrinks the grid’s time step (dt ≈ 1/Δν), so it buys
temporal resolution as well as fidelity. The cost is only the extra frequency
points it adds.
The edge taper, and why the band must be generous#
regrid does not simply crop at lam_min/lam_max. It applies a Planck taper
that rolls the trace to zero across the outermost
croak.preprocess.TAPER_COLLAR_BINS (10) frequency bins at each end. This
is not optional: the forward model FFTs the trace on a periodic grid, so a trace
that does not reach zero at the boundary rings back into the fit.
The consequence is that the outermost bins carry almost no measurement,
whatever band you choose. The nonlinear mixing still couples the field at those
frequencies to the live rows, but only weakly: a probe spike at the grid edge
moves the trace error roughly 100× less than the same spike mid-band. The
optimiser can therefore park energy there for almost no penalty, and since
intensity is bounded below by zero it can only drift upward.
croak.processing.edge_energy_fraction() detects the result.
So the band must be wide enough that the taper lands on trace that has genuinely
died away. load_and_clean() measures this for you and
records it as TraceData.taper_loss
— the fraction of the measured trace the taper removed — and warns above 1 %:
UserWarning: the retrieval grid's edge taper removed 38.7% of the measured
trace: lam_min=700.0 nm / lam_max=900.0 nm clip signal that is still present.
A band picked from the marginal at 1e-4 typically loses well under 0.1 %. Pass
taper_warn_level=0.0 to silence the check when a tight band is deliberate.
Post-retrieval windowing#
To clean ringing or out-of-band energy after retrieval, apply
croak.processing.post_filter() — a temporal and/or spectral Planck taper that
returns a new RetrievalResult (the original is untouched, so
the effect is reversible):
clean = croak.processing.post_filter(result,
tau_lims=td.tau_lims, # measurement τm
lam_lims=td.lamm_lims) # measurement λm
Pass the measurement windows λm/τm — the ranges where the trace actually
carries signal, stored on the TraceData as td.lamm_lims / td.tau_lims. (The
full retrieval-grid extent, td.lam_min/td.lam_max, would taper only the
outermost few bins and do nothing useful.)
The stored trace and error still describe the original unfiltered fit. This is distinct from the spectral-match penalty, which constrains the retrieval itself rather than filtering its output.
Checklist#
Pass the fundamental wavelength band as
lam_min/lam_max, even for SHG.Make the band a little wider than the visible signal so the taper has room.
Set
omega0on dispersive solvers (or letretrieve_from_tracedatatake it fromtd.omega0_pulse).Make the periodic time window wide enough for the pulse, its delayed replicas and its propagation through the whole medium.
Use
post_filterfor cosmetic clean-up, and record it, since it changes the reported field; use regularisation to constrain the fit.