# Modelling geometric time smearing Crossing beams have different local arrival times across a focal spot. A spectrometer that collects the isolated signal averages over those positions; ignoring this response can bias the inferred pulse. This page shows how to construct and use the reduced timing kernel. Start with the [model overview](../user_guide/models.md), and use the [worked example](../examples/geometric_smearing.md) to compare retrievals. The kernel assumes achromatic local profiles and full signal collection. For finite apertures or strongly chromatic focal fields, use the [collection model](collection_aperture.md). The derivation is retained in [the forward model](../explanation/forward_model.md#geometric-smearing). ## The quick version ```python import croak from croak.smearing import square_boxcars_kernel kernel = square_boxcars_kernel( "pg", # PG (used for TG-FROG) or SD hole_diameter=1.0e-3, # mask hole diameter D (m) hole_spacing=0.5e-3, # edge-to-edge gap between holes (m) wavelength=260e-9, # carrier ) print(kernel.sigma_delta * 1e15) # 0.357 fs res = croak.retrieve( trace, g.omega, delays, "pg", algorithm="lbfgs-ad", smearing=kernel, ) ``` The same `smearing=` keyword works on {func}`~croak.forward.maketrace` and {func}`~croak.forward_jax.maketrace_jax`, so you can generate a smeared trace and look at it without running a retrieval: ```python smeared = croak.maketrace(g.omega, delays, ew, "pg", smearing=kernel) ``` ## What the kernel is {class}`~croak.smearing.SmearingKernel` holds three numbers: the rms of the gate-splitting parameter `sigma_p`, the rms of the delay offset `sigma_delta`, and their correlation `rho`. `sigma_p` changes the *shape* of the nonlinear signal and costs an explicit quadrature (`npoints`, default 5); `sigma_delta` is a pure translation of the delay axis, evaluated by a Fourier-domain convolution on a uniform delay grid. For correlated coordinates, the conditional mean and variance depend on the gate-splitting node. Two constructors build one: - {func}`~croak.smearing.square_boxcars_kernel` — the standard folded square with four holes at $(\pm d,\pm d)$, $d = (\text{spacing} + D)/2$. This is the usual case, and the arm assignment follows from the interaction: for `"pg"` the scanned delay rides the **gate pair** — croak's operator convention $E(t)\,|E(t-\tau)|^2$ delays both gate arms together (an instrument that delays the probe instead is equivalent up to a reversal of the delay axis); `"sd"` puts it on the conjugated arm. - {func}`~croak.smearing.kernel_from_arms` — fully general, for any three mask hole positions. Give them in the interaction's **role order**: `(probe, gate unconjugated, gate conjugated)` for PG, `(unconjugated A, unconjugated B, conjugated)` for SD. The focal length never appears: the crossing angle scales as $1/f$ and the focal spot as $f$, so it cancels. Within this ideal-hole approximation, the widths depend on $d/D$ and wavelength. ```{admonition} The three numbers are arrangement-specific :class: warning For a folded square BOXCARS mask the closed forms depend on **which arm carries the scanned delay**, and they are not interchangeable: | Interaction | Delay on | $\sigma_p\,/\,(d/D)(\lambda/c)$ | $\sigma_\delta\,/\,(d/D)(\lambda/c)$ | $\rho$ | |---|---|---|---|---| | `"sd"` | conjugated arm | 0.69487 | 0.34743 | 0 | | `"pg"` | the gate pair (the usual TG-FROG case) | 0.49135 | 0.54934 | $1/\sqrt5$ | So the convenient SD relations — $\sigma_p = 2\sigma_\delta$ with the two uncorrelated — do **not** carry over to PG: there the pair is correlated and $\sigma_\delta$ is 1.58× larger. {func}`~croak.smearing.square_boxcars_kernel` selects the right pair from the `interaction` argument, which matters if you are constructing a {class}`~croak.smearing.SmearingKernel` by hand from numbers quoted for the other geometry. ``` ```{admonition} Which way does your delay axis run? :class: important The kernels are built in **croak's** delay convention, where the PG signal is $E(t)\,|E(t-\tau)|^2$. Only $\rho$ depends on it: reversing the delay axis sends $\delta \to -\delta$ and hence $\rho \to -\rho$, for which `square_boxcars_kernel(..., reverse_delay=True)` is provided. Which way it runs depends on **which arm sits on the delay stage** — the same $\chi^{(3)}$ nonlinearity either way. Scanning the gate pair gives $E(t)\,|E(t-\tau)|^2$; scanning the probe instead (as in the `ModelPNPS` reference simulation, and in TG setups generally) gives $|E(t)|^2 E(t-\tau_{\rm exp})$, which is the same operator with $\tau = -\tau_{\rm exp}$. Retrieving a probe-scanned trace against croak's convention without flipping the axis returns $E^*(-t)$: the spectral amplitude and the duration are unchanged, but the temporal profile is mirrored and the spectral phase negated, so the sign of the retrieved GDD/TOD flips. Use the file's documented delay convention (the simulated loader reads its `delay_convention` marker). Whenever the delay axis the retrieval sees is the probe-scanned one, build the kernel with `reverse_delay=True`, so that $\rho$ matches the trace. ``` ```{note} Full collection permits an incoherent intensity average by Parseval's identity. A finite aperture selects a coherent spatial field and can change delay-dependent structure. The reduced kernel is not a general upper bound on blur or duration; compare a suitable [collection model](collection_aperture.md). ``` ## Fixed or fitted **Fixed geometry** is the natural starting point: calculate the kernel from measured mask dimensions. Changing mask spacing provides a useful controlled test, but it can also change spatial collection and illumination. Model those changes before assigning a difference solely to smearing. ```{warning} The kernel is independent of the medium thickness, but the **duration offset it induces is not** — how much the free pulse must absorb depends on the whole trace, whose sensitivity to the input duration changes with the slab. Do not use a "constant offset across thicknesses" test as the discriminator; use the mask dependence. `examples/example_geometric_smearing.py` runs both sweeps and shows the difference. ``` **Fitted width** adds a nuisance parameter. `fit_smearing=True` multiplies both widths by one dimensionless scale while retaining their correlation. It tests whether that reduced response can explain the trace; the estimate can also absorb other model errors: ```python res = croak.retrieve( trace, g.omega, delays, "pg", algorithm="lbfgs-ad", smearing=kernel, fit_smearing=True, ) print(res.smear_scale) # compare with the nominal scale of 1.0 ``` A value near 1 is consistent with the nominal kernel; a value far from 1 usually means the reduced kernel is absorbing something else (collection, chromatic focal effects or another model error). ```{warning} **Do not fit the smearing width and the slab thickness in the same run.** The smearing multiplier will soak up dispersion-model error, and the two are not separately identifiable from a single trace. Fit one, hold the other, and check the parameter covariance. ``` Uncertainty follows the same route as the other extra parameters: {func}`~croak.covariance.parameter_covariance` accepts `smearing` and `fit_smearing` and reports `sigma_smear`. ### Splitting the two channels The kernel has two channels: the delay offset $\delta$ (a pure blur along the delay axis) and the gate-shape parameter $p$ (a symmetric split of the gate pair, which for a chirped gate shears the trace along frequency). The joint multiplier scales both together, which is the right physical prior — both derive from the same $d/D$ — but as a *diagnostic* it conflates them. Setting `fit_smearing_split=True` fits the two widths as independent multipliers: ```python res = croak.retrieve( trace, g.omega, delays, "pg", algorithm="lbfgs-ad", smearing=kernel, fit_smearing=True, fit_smearing_split=True, ) print(res.smear_scale) # p (gate-shape) channel multiplier print(res.smear_scale_delta) # delta (delay-blur) channel multiplier ``` The split is exact, not an approximation: the bivariate-normal kernel scales channel-wise, with the $p$ node positions carrying one factor and the conditional delay mean/width the other, at fixed correlation $\rho$. ```{note} For a transform-limited **Gaussian** field, splitting the gate changes only its amplitude, which a fitted intensity scale can absorb. This is not true for every transform-limited field: a sum of Gaussians gives a time-dependent gate ratio. A split-width estimate must therefore be interpreted using the pulse shape, phase and parameter correlations, not simply whether the field is chirped. ``` ### Temporal phase and spectral dispersion Write a Gaussian field as $E(u)=\exp[-u^2/(4\sigma^2)]\exp[i b u^2/2]$. Its split gate is ```{math} E(u+p/2)E^*(u-p/2) =\exp[-u^2/(2\sigma^2)]\exp[-p^2/(8\sigma^2)]\exp[i bup]. ``` For $b=0$ this is attenuation only. With the forward Fourier sign $\exp(+i\Omega t)$, the gate frequency shift is $-bp$. For a general temporal phase $\phi$, it is ```{math} \delta\omega_g(u;p)=-[\phi'(u+p/2)-\phi'(u-p/2)] =-p\phi''(u)-\frac{p^3}{24}\phi^{(4)}(u)+\cdots. ``` Temporal-phase derivatives are not the GDD, TOD and FOD, which are derivatives of **spectral** phase $\Phi(\Omega)$. In particular, a symmetric spectral amplitude with pure odd spectral phase can give a real temporal field with phase jumps rather than continuous temporal chirp. For Gaussian intensity of rms width $\sigma_t$, a quadratic spectral phase $\Phi_2$ gives $b=-\Phi_2/(4\sigma_{t,0}^4+\Phi_2^2)$, where $\sigma_{t,0}$ is the transform-limited intensity width. This relation uses the Fourier convention above; it is not a general conversion for non-Gaussian fields. {func}`~croak.covariance.parameter_covariance` accepts `fit_smearing_split` and additionally reports `sigma_smear_delta`. ## Cost About $(K+1)/2$ times the unsmeared model, where $K$ is `npoints` — roughly 3× at the default 5 nodes. The quadrature nodes are exactly antisymmetric, so each shifted field is built once and used twice, and the `p` axis is a `jax.vmap` batch. The delay integral adds nothing measurable. The delay axis must be **uniformly spaced** (the delay integral is a convolution along it) and comfortably wider than the kernel; both are checked, and violations raise. ## Solver support | Option | Solvers | Requirements | |---|---|---| | `smearing` | `warm-lbfgs`, `lbfgs-ad`, `lm`, `lm-optx`, `lbfgs-optx`, `cma-es` | PG (TG) or SD; uniform delay axis | | `fit_smearing` | `warm-lbfgs`, `lbfgs-ad`, `lm`, `lm-optx`, `cma-es` | a `smearing` kernel | | `fit_smearing_split` | `warm-lbfgs`, `lbfgs-ad`, `lm`, `lm-optx`, `cma-es` | `fit_smearing` | `copra`, `copra-jax`, `lbfgs` and `lbfgs-hand` cannot model smearing: the incoherent average over the kernel breaks COPRA's magnitude-replacement projection, and the hand-derived adjoint is not extended to it. Asking for smearing with them raises rather than silently dropping the kernel. SHG raises too: geometric smearing here is defined for the three-arm PG/SD forms. ## In the GUI The Retrieve stage has a **Geometrical smearing** group, greyed out unless the chosen solver supports it: - *Enable geometrical smearing* - *Hole diameter (mm)* and *Hole spacing (mm)* (edge to edge) — the physical inputs - *σ delay (fs)* — derived from them at the measured carrier, and **editable**: it back-computes the hole spacing, holding the diameter, so you can dial a width in directly. The same coupling as *Thickness* ↔ *Δz* ↔ *Npoints* in the group above. It has a floor at zero spacing (holes touching). - *Quadrature nodes* *Fit smearing width* sits in **Extra fit parameters** and is enabled only once the kernel is on. When it has been fitted, the ready line under the form reports the result next to the other fitted extras, as the physical delay width and the multiplier it came from: ``` Done. R = 0.1459% | retrieved FWHM 1.12 fs | … | smearing σ 14.24 fs (×1.457) ``` The width shown is `smear_scale × sigma_delta` of the kernel the run used — the two kernel widths scale together with the multiplier ({meth}`~croak.smearing.SmearingKernel.scaled`), so one number describes the fit. Selecting *Reuse previous result* on a re-run starts the fit from that multiplier instead of from 1.0. The mask geometry is what is stored and what reaches the forward model; the displayed width is derived from it, always at the carrier of the loaded measurement. A fitted multiplier is deliberately **not** written back into the diameter/spacing controls: the geometry is a measured property of the instrument, not a readout to be overwritten by a fit. To adopt a fitted width as the new nominal, type it into *σ delay (fs)*, which back-computes the spacing. ## Worked example `examples/example_geometric_smearing.py` synthesises a trace with a known kernel and retrieves it with and without, printing the trace error, FWHM and rms width of each fit (see the [gallery page](../examples/geometric_smearing.md)): ``` uv run python examples/example_geometric_smearing.py ```