Fitting the medium thickness and delay-zero#
Two quantities are usually fixed before a retrieval but only ever estimated:
the dispersive-slab thickness \(L\) of the forward model (
ForwardModel’smaterial/thickness), andthe delay-zero \(\tau_0\) — the point on the delay axis where the two pulse replicas overlap, set during preprocessing by the delay-marginal peak.
Both can instead be retrieved as extra scalar parameters alongside the
complex pulse, using lbfgs-ad, warm-lbfgs, lm, lm-optx or cma-es.
These fits can improve agreement when the calibration is uncertain, but they
also introduce correlations with the pulse. Start with independently measured
values and check how strongly the data constrain each fitted parameter.
lm-optx is the on-device Optimistix twin of lm; it runs an unconstrained
Levenberg–Marquardt, so it cannot hold the fitted thickness non-negative and
relies on starting from a good prior — use it as a polish (or warm start), as
recommended below for thickness in any case.
cma-es appends the extras to its global search vector as O(1)-scaled offsets.
The search is unconstrained (no thickness lower bound) and has no two-phase
polish — it frees the extras from the start. Because thickness wants a good
prior, the usual recipe is to fit \(\tau_0\) globally here, and fit thickness by
refining a pulse-only cma-es result with lbfgs-ad (polish=True).
res = croak.retrieve(
trace, g.omega, delays, "pg",
algorithm="lm",
material="SiO2", thickness=250e-6, npoints=20, omega0=omega0,
fit_thickness=True, # fit the slab thickness
fit_tau0=True, # fit the delay-zero offset
polish=True, # two-phase polish (see below)
)
print(res.thickness, res.tau0) # fitted values (m, s)
When each one helps#
Delay-zero \(\tau_0\) — useful for every interaction#
fit_tau0 adds a single scalar that slides the simulated trace along the delay
axis: the gate delay phase becomes \(e^{i\omega(\tau-\tau_0)}\). It is not a
trivial gauge of the pulse — translating the pulse in time leaves the trace
invariant, whereas a delay-axis offset moves it (for SHG, it moves the
trace’s symmetry centre). So a mis-set time zero raises the trace error, and
fitting \(\tau_0\) removes it.
A \(\tau_0\) error produces a characteristic red/blue tilt in the residual: because different colours peak at different delays in a chirped pulse, a delay offset leaves an antisymmetric, frequency-correlated residual. If you see that signature, fit \(\tau_0\). Preprocessing already estimates the marginal peak to sub-sample precision. That peak need not coincide with physical gate–probe coincidence; decide which delay-origin convention describes the experiment before adding a free offset.
Sign convention: a positive res.tau0 means the true delay-zero of the measured
trace lies at \(+\tau_0\) (the simulated gate uses \(e^{i\omega(\tau-\tau_0)}\)).
Thickness \(L\) — only for the dispersive PG/SD slab#
fit_thickness is supported for the dispersive PG/SD slab. The depth
integral makes thickness affect the trace beyond a single input spectral phase.
Whether a particular trace identifies it is a separate question (see below).
For SHG, croak has no dispersive slab model (the signal propagates at
\(2\omega_0\)), so fit_thickness with SHG is rejected with an error.
Even within PG/SD, the bulk-chirp part of \(L\) is partly degenerate with the retrieved phase; only the depth-distributed part constrains it. Thickness is therefore best fitted as a polish from a good prior, and is well determined only when the slab is dispersive enough that it visibly shapes the trace. With a weakly dispersive slab the direction is nearly flat and \(L\) will barely move — that is the model honestly telling you the data does not constrain it.
Triggering the fit: warm-start vs two-phase polish#
The joint pulse + extra-parameter landscape is harder than the pulse alone, so fit the extras from a good pulse estimate, not a cold start. Two ways:
Warm start (default). Run a normal retrieval first, then re-run with the extra flags enabled, seeding from the previous result. In the API pass the first result’s
spectrumasguess=; in the GUI pick Reuse previous result from the Initial guess selector together with the fit options.A warm start seeds the extras as well as the pulse. The extras are optimised as offsets from a centre —
thicknessfor \(L\),tau0for \(\tau_0\),smear_scalefor the smearing width — so passing the previous fit’s values makes the re-run continue from where the last one stopped rather than restarting from the priors (a spectrum that was retrieved jointly with a non-zero \(\tau_0\) is otherwise re-fitted against a differently-referenced delay axis). The GUI does this for you: Reuse previous result seeds every extra that is still being fitted, and says which in the status line. An extra whose fit box you have unticked keeps the value you set — an unticked box means “hold this where I put it”, and the model is evaluated there.first = croak.retrieve_from_tracedata(td, algorithm="lbfgs-ad", ...) refined = croak.retrieve_from_tracedata( td, algorithm="lbfgs-ad", guess=first.spectrum, thickness=first.thickness, # centres for the second fit tau0=first.tau0, fit_thickness=True, fit_tau0=True, material="SiO2", )
Two-phase polish (
polish=True). A single call that internally retrieves the pulse with the extras held fixed, then frees them for a joint final phase. Convenient when the fixed-extras pulse is already a reasonable fit (it is for a thickness prior; less so for a large \(\tau_0\) error, where a warm start is better).
Uncertainty on the fitted parameters#
When you fit \(L\) or \(\tau_0\), the analytic covariance can report their standard
errors under a local linear approximation. This answers a different question
from thickness_bootstrap(), which varies a fixed input
thickness according to an external calibration uncertainty. Do not count the
same uncertainty twice. See uncertainty estimation for the
assumptions and limitations:
from croak.covariance import parameter_covariance
cov = parameter_covariance(
res, trace, noise=0.01,
material="SiO2", thickness=res.thickness, npoints=20, omega0=omega0,
fit_thickness=True, fit_tau0=True,
)
print(cov.sigma_thickness, cov.sigma_tau0) # σ in m, s
The thickness/\(\tau_0\) columns are appended to the Jacobian and re-linearised about the fitted values, allowing the local covariance to include their correlation with the pulse. Weakly constrained directions, bounds and strong nonlinearity can make this approximation unreliable; inspect conditioning and compare with repeated fits or a suitable bootstrap.
In the GUI#
The retrieve stage has an Extra fit parameters group with Fit medium
thickness, Fit τ₀ delay offset, Fit smearing width and Two-phase polish.
Fit smearing width is enabled only when Enable geometrical smearing is on
(see Geometric smearing). Fit medium thickness
is enabled only when Enable dispersive propagation is on and the chosen solver
supports it (lbfgs-ad / warm-lbfgs / lm / lm-optx); the fitted values are shown in the result
summary. The thickness fit starts from the Thickness (µm) value in the
Dispersive propagation group.
Summary#
Option |
Solvers |
Requirements |
Notes |
|---|---|---|---|
|
|
any interaction |
sub-pixel time-zero; removes red/blue residual tilt |
|
|
dispersive slab, PG/SD |
identifiable only for a dispersive slab ( |
|
|
an extra enabled |
fit pulse first, then free the extras (not |
|
|
a smearing kernel, PG/SD |
one multiplier on the kernel widths; joint fits with |
The delay origin without a fitted τ₀: delay_origin="marginal_peak"#
Preprocessing centres a measured trace on its delay-marginal peak, but the
forward model puts delay zero at gate–probe coincidence. For a delay-symmetric
trace those coincide; for a coherently collected single-cycle trace they do not,
and a retrieval with the origin held at coincidence can only absorb the
misalignment by distorting the pulse. Rather than fitting tau0, which on a
chirped pulse trades against the chirp, use the same convention on both sides:
res = croak.retrieve(trace, omega, delays, "pg", algorithm="lbfgs-ad",
delay_origin="marginal_peak", ...)
This re-centres every model trace on its own marginal peak by the same sub-sample
rule the preprocessing used — one convention on both sides, no free parameter.
It needs a uniform delay axis (the retrieval grid is). If you do fit tau0,
tau0_bound (seconds; RetrieveParams.tau0_bound_fs) boxes it.