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:

  1. 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.

  2. The retrieval grid — a uniform, centred angular-frequency / time grid (croak.grid.Grid) that the solvers run on. load_and_clean() builds it from lam_min/lam_max.

  3. 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

omega0_scale

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 omega0 on dispersive solvers (or let retrieve_from_tracedata take it from td.omega0_pulse).

  • Make the periodic time window wide enough for the pulse, its delayed replicas and its propagation through the whole medium.

  • Use post_filter for cosmetic clean-up, and record it, since it changes the reported field; use regularisation to constrain the fit.