Source code for croak.session.params

"""Per-stage parameter dataclasses for a retrieval session.

These plain dataclasses describe a complete retrieval — loading, preprocessing,
retrieval, dispersion compensation and uncertainty — independently of any user
interface. The GUI (:doc:`/howto/gui`) binds its controls to them and the headless
engine (:mod:`croak.session.engine`) replays them; both share this one schema, so
a session saved from the GUI can be rerun from a script and vice versa.

The dataclasses are deliberately Qt-free: they carry only data and a little
classification logic (which load paths follow the dataset folder vs. which are
fixed, used by :mod:`croak.session.retarget`).
"""

from __future__ import annotations

from dataclasses import dataclass, field

__all__ = [
    "LoadParams",
    "SimulatedLoadParams",
    "PreprocParams",
    "RetrieveParams",
    "BeamPathMirror",
    "DispersionParams",
    "UncertaintyParams",
    "REG_DEFAULTS",
    "reg_defaults",
    "reg_family",
]


# Re-exported from croak.solver, their canonical home since the solver
# constructors themselves resolve these defaults; kept here because the GUI
# and session code historically import them from this module.
from ..solver import (  # noqa: E402  (re-export)
    REG_DEFAULTS,
    reg_defaults,
    reg_family,
)


[docs] @dataclass class LoadParams: """Stage 1: which files/datasets/units make up the measured trace. Holds the FROG trace file and its wavelength/scan datasets and units, the trace orientation (``transpose``) and delay-sign (``reverse_trace``) conventions, the optional independent spectrum, the optional backgrounds and spectral-response calibration curves, and the optional measured pulse energy used to rescale the temporal plots to absolute power. """ frog_path: str = "" ifrog_name: str = "" lam_name: str = "" scan_name: str = "" transpose: bool = False lam_unit: str = "nm" scan_unit: str = "fs" scan_type: str = "delay" #: Flip the delay-sign convention: negate the scan axis on loading, i.e. #: mirror the trace about τ = 0. Which end of a stage scan is "gate late" is #: a wiring convention of the measurement, and a measured file carries no #: marker recording it (unlike ``SimulatedLoadParams.reverse_trace``, which #: can auto-detect from ``/grid/delay_convention``), so this is a plain #: off-by-default override. It matters for PG/SD, whose kernels fix the #: direction of time: getting it wrong there time-reverses the retrieved #: pulse and flips the sign of every phase order. A thin-medium SHG trace is #: symmetric in delay (``Interaction.time_reversal_ambiguous``), so #: reversing it changes nothing and cannot settle SHG's direction-of-time #: ambiguity — that is :func:`croak.processing.resolve_time_direction`'s #: job. See :doc:`/reference/conventions`. reverse_trace: bool = False interaction: str = "shg" third_order: bool = False third_order_exp: float = 4.0 # Measured pulse energy (J), e.g. from a power meter. Optional measurement # metadata, not derived from the trace: when > 0 the temporal envelope plots # are rescaled from normalised "a.u." to absolute instantaneous power (and a # peak power is reported); 0 means unspecified (keep normalised units). energy_j: float = 0.0 # independent spectrum (CSV columns or HDF5/NPZ datasets) use_spec: bool = False spec_path: str = "" spec_is_csv: bool = True spec_lam_name: str = "" # HDF5/NPZ dataset name spec_int_name: str = "" spec_lam_col: int = 0 # CSV column index spec_int_col: int = 1 spec_lam_unit: str = "nm" # FROG background (own λ axis + spectrum/trace dataset) frog_bg_use: bool = False frog_bg_path: str = "" frog_bg_lam_name: str = "" frog_bg_int_name: str = "" # FROG spectral-response calibration curve (λ, scale CSV) frog_calib_use: bool = False frog_calib_path: str = "" frog_calib_lam_unit: str = "nm" # spectrum background / calibration spec_bg_use: bool = False spec_bg_path: str = "" spec_calib_use: bool = False spec_calib_path: str = "" spec_calib_lam_unit: str = "nm"
[docs] @staticmethod def dataset_path_fields() -> tuple[str, ...]: """Path fields that live *inside* the per-dataset folder. These move together when an :func:`~croak.session.retarget.retarget` points an existing session at a new dataset folder: the measured trace, the independent spectrum, and their backgrounds all share that folder and keep their filenames across acquisitions. """ return ("frog_path", "spec_path", "frog_bg_path", "spec_bg_path")
[docs] @staticmethod def fixed_path_fields() -> tuple[str, ...]: """Path fields that stay put when retargeting (shared calibration files).""" return ("frog_calib_path", "spec_calib_path")
[docs] @dataclass class SimulatedLoadParams: r"""Stage 1 (alternative): load a numerically simulated FROG scan. Describes how to turn a Luna ``scansave`` HDF5 file (see :func:`croak.io.read_simulated_scan`) into the same ``load_data`` dict the measured-trace loader produces. Unlike :class:`LoadParams` the file layout is fixed, so there are no dataset-name/unit pickers — only the physics options: which trace window, the loaded wavelength band, the third-order efficiency correction, the delay-sign convention, the nonlinear interaction, and which independent spectrum (if any) to feed the retrieval. Notes ----- The simulated trace is stored as an angular-frequency density :math:`I_\omega`; the loader divides it by :math:`\lambda^2` so the wavelength pipeline's :math:`\times\lambda^2` regrid recovers it undistorted (see :func:`croak.preprocess.omega_to_lambda_density`). The chromatic vignetting imprinted by the physical input mask is a linear filter on the *gating beam*, not a trace artefact, so it belongs in the reference spectrum: ``spectrum_source="beamlet"`` (the post-mask, transverse-integrated ``grid/Iω_beamlet``) constrains the retrieval with the spectrum that actually gates. Choose the independent spectrum and the truth in the same field reference as the model: an on-axis model (the focal mixture) needs the on-axis beamlet, or a frame conversion via ``RetrieveParams.spectrum_frame_p``. Detector response, the radiated source factor and wavelength/frequency density conversion are separate operations. See :doc:`/howto/loading_simulated` and :doc:`/reference/conventions`. """ frog_path: str = "" #: ``"Iω_win"`` (integrated through the selected aperture) or #: ``"Iω_win_reimaged"`` (on-axis). window_key: str = "Iω_win" #: Propagation slice index (``-1`` = the last saved slice, i.e. substrate exit); the #: low-level/back-compat knob, used only when ``z_thickness_um`` is ``None``. z_index: int = -1 #: Material thickness (µm) to load from a multi-thickness scan. When set, the #: nearest saved ``/grid/zsave`` slice is loaded (takes precedence over #: ``z_index``); ``None`` falls back to ``z_index`` (the exit slice), which #: also covers older single-thickness files that lack ``zsave``. z_thickness_um: float | None = None # wavelength band to load (the unphysical ω ≤ 0 bins are always dropped); the # retrieval grid range is chosen later in the preprocess stage. lam_min_nm: float = 140.0 lam_max_nm: float = 800.0 # third-order (χ³) efficiency correction: divide out the ∝ ω^n generation # response, equivalently multiply by (λ/µm)^n. The true response is a *curve* # (radiated ω² × chromatic collection), so a single exponent is only an # approximation — the better routes are the retrieval's per-frequency Rω # scaling or phase-only retrieval (see the manual). The default 2 matches the # radiated-field ω² factor; the marginal-check stage's Auto refines it. third_order: bool = True third_order_exp: float = 2.0 # Delay-sign convention. ``None`` (default) auto-detects from the file's # ``/grid/delay_convention`` marker: files stored in the gate-delay (paper) # frame load unreversed; older marker-less files (probe-delayed frame) are # negated. # An explicit ``True``/``False`` overrides the marker. reverse_trace: bool | None = None #: Keep only the longest uniformly spaced contiguous stretch of the delay #: axis (:func:`croak.preprocess.uniform_core_indices`). Simulated scans #: often extend a uniform core with coarser wing points (e.g. 1 fs-step #: wings to ±40 fs for the Raman wake); the smearing kernel's delay-axis #: convolution needs a uniform grid, so retrievals that model smearing #: must drop the wings, and so must ``delay_origin="marginal_peak"``, whose #: Fourier re-centring also needs uniform delays. Off by default: the wings #: are real data that the other models can use. uniform_delay_core: bool = False #: ``"pg"`` (transient-grating/PG) or ``"sd"``; both keep the carrier. interaction: str = "pg" # feed the simulation's known input spectrum as the independent spectrum # (enables spectral regularisation); turn off for a blind test use_spectrum: bool = True #: which spectrum when ``use_spectrum``: ``"beamlet"`` (post-mask #: ``grid/Iω_beamlet``, the transverse-integrated beam that gates — the #: 1-D models' frame), ``"beamlet_reimaged"`` (the ON-AXIS beamlet #: ``grid/Iω_beamlet_reimaged``, stored by pnps files — the chromatic focal #: mixture's own frame, so no ``spectrum_frame_p`` is then needed) or #: ``"source"`` (pre-mask ``grid/Iω``, the ideal input). Falls back along #: reimaged → beamlet → source when the file lacks the requested one. spectrum_source: str = "beamlet" #: which stored pulse to overlay as the time-domain truth (and to seed #: ``truth_init`` from): ``"beamlet"`` (post-mask ``grid/It_beamlet``, the #: beam that gates — default), ``"beamlet_reimaged"`` (its on-axis #: counterpart, ``grid/It_beamlet_reimaged`` — the like-for-like truth for #: a focal-mixture retrieval) or ``"source"`` (pre-mask ``grid/It``, the #: ideal input). Falls back when the file stores fewer. See #: :func:`croak.io.read_simulated_truth_keys`. truth_source: str = "beamlet" #: load the as-simulated ω-density trace on its native grid straight into the #: retrieval, skipping the marginal-check and preprocess (filtering + regrid) #: stages. Fully raw: no third-order correction; window/thickness/delay-sign/ #: known-spectrum selections are still honoured. For sanity-checking the #: retrieval free of any preprocessing doubt (see #: :func:`croak.session.pipeline.assemble_simulated_tracedata`). raw_direct: bool = False
[docs] @dataclass class PreprocParams: """Stage 2: the grid, windowing and filtering used to clean and regrid. Wavelengths are in nm and delays in fs (converted to SI when applied). The grid fields set the retrieval ω/τ grid; the windowing fields the measurement λm/τm extents; the filtering fields the fringe/DC/low-pass and thresholding. """ # -- grid -- # retrieval grid wavelength range (fundamental; = trace λ × ω0scale) lam_min_nm: float = 700.0 lam_max_nm: float = 900.0 lam_bound_min_nm: float = 350.0 # slider bounds (data-driven) lam_bound_max_nm: float = 1100.0 trange_fs: float = 1000.0 # time grid range; always on, default 2× scan delay range # delay range to use (hard crop of the delay axis) tau_min_fs: float = -500.0 tau_max_fs: float = 500.0 use_subsample: bool = False subsample_tau: int = 1 resample_t: bool = False prefilter: bool = True # anti-alias low-pass before downsampling the spectrum # -- windowing -- # measurement spectral window (trace λ domain) lamm_min_nm: float = 300.0 lamm_max_nm: float = 1100.0 lamm_bound_min_nm: float = 300.0 lamm_bound_max_nm: float = 1100.0 # measurement delay window (τm; always on, crops the delay axis) taum_min_fs: float = -500.0 taum_max_fs: float = 500.0 tau_bound_min_fs: float = -1000.0 tau_bound_max_fs: float = 1000.0 # -- filtering -- filter_fringes: bool = False filter_dc: bool = False dtau_min_fs: float = 0.0 # Takeda-FTSI de-fringe (carrier low-pass); mutually exclusive with # filter_fringes. defringe_fraction sets the cutoff as a fraction of the # carrier f_c = c/λ. defringe: bool = False defringe_fraction: float = 0.5 # Nyquist-guard mode when the de-fringe carrier aliases ("raise"/"warn"/ # "clamp"). Defaults to "clamp" (never fails; identical to "raise" when the # delay is sampled finely enough that the fringe does not alias). defringe_on_alias: str = "clamp" # arPLS signal-aware baseline removal (broad leakage |E_b|^2 along delay). # Applied right after de-fringing, before τ=0 centring; asymmetric-baseline # aware. baseline_tau_exclude_fs=0 ⇒ no peak hold-out; clip off by default. baseline: bool = False baseline_smoothness: float = 1e2 baseline_ratio: float = 1e-3 baseline_clip: bool = False baseline_tau_exclude_fs: float = 0.0 use_threshold: bool = False threshold: float = 1e-4 # noise floor as a fraction of the peak (e.g. 5e-3) marginal_correct: bool = False
[docs] @dataclass class RetrieveParams: """Stage 3: the solver, initial guess, dispersive slab and regularisation. Covers the algorithm and its tolerances, the guess mode, optional dispersive propagation through a slab (the forward model, distinct from the stage-4 dispersion *compensation*), the regularisation weights, the phase parameterisation, the CMA-ES global-search knobs and the post-filter toggles. """ #: "warm-lbfgs" is the extended model seeded from COPRA's local projection #: sweep: the same forward model as "lbfgs-ad" with a better starting point. #: It is the default because it is close to neutral where the landscape is #: benign and substantially better where it is not (see #: :mod:`croak.warm_lbfgs` for the measured comparison), so it is the safer #: choice for a user who does not want to reason about initial guesses. The #: papers' validation protocol instead runs "lbfgs-ad" from a #: transform-limited start (``tl_init``) for 400 iterations. solver: str = "warm-lbfgs" #: Maximum solver work budget; its unit depends on the algorithm #: (iterations, residual evaluations or objective evaluations). 300 and a #: 1e-5 relative tolerance are the settings our smearing and solver #: comparisons were measured with; 100 and 1e-4 stopped some dispersive #: retrievals short of convergence. maxiters: int = 300 reltol: float = 1e-5 abstol: float = 1e-8 full: bool = True R_omega: bool = True random_init: bool = False perfect_init: bool = False #: Seed from the known complex field carried by a synthetic or ModelPNPS #: load. The selected truth must contain complex spectral datasets; intensity- #: only files cannot provide this initial condition. truth_init: bool = False #: Start from the measured spectrum with flat phase: its transform limit. #: Preferred over ``perfect_init`` for near-transform-limited pulses: the #: two perform equivalently, but this one uses the amplitude that was #: actually measured and assumes no duration. Ignored if ``random_init`` or #: ``perfect_init`` is set. tl_init: bool = False reuse_result: bool = False # seed from the previous retrieval's spectrum # Live full-plot preview during retrieval (GUI only): redraw the visible plot page # ~1/s as the solver converges, rather than only the convergence curve. When # off, the cheap convergence curve is shown. Ignored by the headless pipeline. live_preview: bool = True perfect_init_fs: float = 10.0 dispersive: bool = False # dispersive propagation through a slab material: str = "SiO2" thickness_um: float = 10.0 # Default node count kept consistent with the tied defaults below: # thickness_um / delta_z_um = 10 µm / 1 µm = 10. npoints: int = 10 # Nominal depth step (µm) the GUI uses to pick ``npoints`` for the slab # integral: ``npoints = round(thickness_um / delta_z_um)``. The two are tied # so editing the thickness grows ``npoints`` to hold the step fixed; editing # ``npoints`` back-computes the step. Only ``npoints`` reaches the forward # model (:class:`croak.forward.ForwardModel`) — ``delta_z_um`` is the GUI knob. delta_z_um: float = 1.0 # Geometric time smearing of a non-collinear BOXCARS geometry (PG/SD only, # autodiff solvers only). The mask hole diameter and edge-to-edge spacing are # the physical inputs; the resulting delay-offset width is tied to them via # croak.smearing.square_boxcars_delay_width at the measured carrier, so editing # either geometry field updates the width and editing the width back-computes # the spacing — the same coupling as thickness / Delta z / npoints above. Only # the derived kernel reaches the forward model. smearing: bool = False smear_hole_diameter_mm: float = 1.0 smear_hole_spacing_mm: float = 0.5 smear_sigma_delay_fs: float = 0.357 #: COPRA global-step size factor (Geib's alpha, dimensionless). Inert in #: practice: on our dispersive TG-FROG tests a factor of five in it moved #: the complex error by a few percent, non-monotonically. copra_alpha: float = 0.25 #: Iterations without improvement before COPRA's local projection sweep #: hands over to its global descent. Not a smooth knob but a switch between #: the two stages, and with a smeared model the stages differ in whether #: they can represent the physics at all: the local projection cannot (a #: mixture has no single signal to project onto) while the global descent #: can. A very large value never leaves the local sweep. copra_stall_patience: int = 5 smear_nodes: int = 5 #: Fixed multiplier on both reduced-kernel widths (dimensionless), applied #: as a FIXED value (unlike ``fit_smearing``, which optimises it). It exists #: because the mask geometry can only express d/D = (gap + D)/2D >= 0.5, so #: a kernel narrower than that is not reachable by adjusting the gap at all. #: #: Use 1.0 for the apertured instrument: in our earlier scans of the #: kernel width against the 3D instrument simulation at the known truth, #: the optimum sat at 0.85-1.00 at both 9.5 and 20 um and at both d/D = 1.0 #: and 1.5. Gaussian-beam traces wanted about 0.5, on top of the 0.748 #: focal-weight rescale their effective gap already carries. A fitted or #: chosen value tests sensitivity; it need not identify a single physical #: mechanism. smear_scale: float = 1.0 #: Aperture layout the kernel is built from. ``"boxcars"`` is the four-hole #: BOXCARS mask used for TG/PG (and for a three-hole SD). ``"sd2"`` is a #: genuine TWO-beam self-diffraction: two collinear apertures, the probe #: entering twice and the gate once with conjugation. #: #: The distinction matters. In a two-beam SD the two unconjugated arms are #: the SAME beam, so their relative internal delay ``p`` (proportional to #: the difference of their tilts) is identically zero and the smearing #: collapses to a pure delay-axis convolution. Building such a #: measurement's kernel from the boxcars closed form instead attaches a #: spurious ``sigma_p`` of order 0.4 fs that the experiment does not have. smear_layout: str = "boxcars" #: Chromatic focal mixture (:mod:`croak.focal`) instead of the reduced #: smearing kernel — the full model. Each focal-plane node carries the #: chromatic Airy filter ``A(r, omega)`` and its own definite ``(p, theta)``, #: so the r-dependent (bluer-on-axis) local spectra that the reduced kernel #: averages away are modelled explicitly. Mutually exclusive with #: ``smearing`` (the mixture REPLACES the kernel; asking for both raises). #: Autodiff solvers only. Roughly 40x the reduced kernel's cost per #: iteration at the default 24x16 node grid. focal: bool = False focal_n_radial: int = 24 #: Number of azimuthal nodes. 16, not 8: the coherent (apertured) sum needs #: the finer azimuthal quadrature (8 costs 4e-5 of trace peak). Refine with #: the radial and collection quadratures to check convergence. focal_n_azimuth: int = 16 focal_rmax_units: float = 4.0 #: Focusing focal length (mm). The reduced kernel never needs it (f #: cancels between spot size and tilt); the explicit mixture does. For #: simulated scans it is stored in the file (``f_foc`` when available) and #: adopted with the rest of the geometry. focal_f_mm: float = 100.0 #: Collection model riding on the mixture: ``"off"`` sums incoherently #: over the focal plane (exact only for full-beam collection), ``"file"`` #: rebuilds the collection aperture from a simulated scan's own #: ``window_def_*`` record, ``"manual"`` builds a centred hole at the #: phase-matched corner from ``collection_diam_mm``. A tightly apertured #: instrument sits far from the incoherent limit: the finite aperture #: changes the spatial interference, and without this the mixture #: over-blurs. collection: str = "off" collection_mode: str = "integrated" collection_diam_mm: float = 0.5 #: Spectral-frame exponent ``p``: the measured spectrum used for the initial #: guess and the ``reg_spectrum`` target is reweighted in intensity by #: ``((omega + omega0)/omega0)**(2*p)`` (absolute over central frequency) #: before retrieval. The focal mixture's field is the ON-AXIS focal field, #: while a measured/beamlet spectrum is spatially integrated; in the #: declared diffraction convention ``p = 1`` converts the integrated #: aperture-diffraction beamlet to the on-axis entrance field. Opening the #: collection reduces the effective value (in our earlier scans, ~1.0 at a #: 0.5 mm gap and ~0.5 at 1.0 mm). Leave 0 for the reduced kernel, whose #: implicit frame is the mixture-averaged one. GDD and spectral span are #: insensitive to it; durations move by a few percent. See #: :doc:`/reference/conventions`. spectrum_frame_p: float = 0.0 # extra fitted parameters (lbfgs-ad / lm / lm-optx / cma-es): slab thickness # (dispersive PG/SD only), a delay-zero offset and the smearing strength; # two-phase polish is a local-solver option (cma-es frees the extras from the # start). # Generation envelope g(z) on the depth quadrature (lbfgs-ad only): the # transverse-geometry factor of the crossing focused beams — focal droop # over the Rayleigh range, beamlet walk-through over the overlap length, # and a net Gouy phase. Negligible for slabs much thinner than the # (in-medium) Rayleigh range; see docs/explanation/forward_model.md. # Lengths in um from the slab entrance; in-medium values. envelope: bool = False envelope_zr_um: float = 0.0 envelope_lw_um: float = 0.0 envelope_zf_um: float = 0.0 envelope_gouy: float = 1.0 fit_thickness: bool = False fit_tau0: bool = False fit_smearing: bool = False # fit the gate-shape (p) and delay (delta) kernel widths as two independent # multipliers instead of the single joint one — the diagnostic for which # smearing channel deviates from the geometric prediction; needs fit_smearing fit_smearing_split: bool = False polish_two_phase: bool = False #: Where the model's delay zero sits (AD solvers): ``"coincidence"`` #: (gate–probe coincidence, the forward model's native convention) or #: ``"marginal_peak"`` (every model trace re-centred on its own row-gain #: weighted delay-marginal peak, the convention the preprocessing gives the #: data, with no free parameter; needs uniform delays). The latter is the #: single-cycle choice: a coherently collected trace peaks tens of #: attoseconds from coincidence, growing with the chirp, so a fixed origin #: costs duration accuracy (~10 % in our earlier instrument scans) while a #: free ``fit_tau0`` wanders on chirped pulses. See #: :mod:`croak.delay_origin`. delay_origin: str = "coincidence" #: ``|τ0| ≤ tau0_bound_fs`` (fs) for a fitted delay offset; 0 = unbounded. tau0_bound_fs: float = 0.0 #: Amplitude-smoothness weight (second difference of ``|E(omega)|``, #: max-normalised). Used *together with* ``reg_spectrum``: on our dispersive #: TG-FROG tests, 0.03 alongside a spectrum weight of 0.01 beat every #: single-penalty setting on every metric at once, across eight arms at two #: depths: 12 % better in complex error, 2.4 points better in retrieved #: duration and 2.5x smoother in the worst case than ``reg_spectrum`` = 0.1 #: alone. Adding it never hurt in any configuration measured, noiseless or #: noisy. The same objective-scale caveat as ``reg_spectrum`` applies (the #: LM family uses a different scale). reg_amp: float = 0.03 reg_phase: float = 0.0 #: Spectrum-divergence weight (penalises mismatch between the retrieved and #: independently measured spectral amplitude). 1e-2 is the gradient-family #: default: on our dispersive TG-FROG tests, scanning it over three decades #: moved the trace error by only 14 % but the pulse error by 44 %, with a #: clear interior optimum. The penalty does not remove misfit so much as #: move it out of the spectral amplitude, where it is constrained, and into #: the phase, where it is not, so both too small a weight (amplitude error #: dominates) and too large a one (phase error dominates) are worse. Do NOT #: tune this by watching the trace error: it barely responds. #: #: The papers' validation protocol uses 0.03 for both this and ``reg_amp`` #: (with "lbfgs-ad" from a transform-limited start, 400 iterations). #: #: This value is for the L-BFGS/gradient family, which minimises the scalar #: R + lambda P. The LM solvers add the penalty as residual rows #: (R² + lambda' P), so the same number is far stronger there; see #: :data:`croak.solver.REG_DEFAULTS` for the per-family values and the #: lambda' = 2 R lambda mapping between them. croak does not translate #: automatically, so reset both weights when changing ``solver`` to #: "lm"/"lm-optx" (the GUI does this for you). reg_spectrum: float = 1e-2 # temporal (pedestal) penalty (LBFGS-AD / cma-es) + its time window (fs) reg_time: float = 0.0 reg_time_lo_fs: float = -500.0 reg_time_hi_fs: float = 500.0 # phase parameterisation (AD solvers + cma-es): pointwise or reduced B-spline phase_basis: str = "pointwise" n_nodes: int = 20 # global search (cma-es) cma_strategy: str = "cma" cma_popsize: int = 0 # 0 -> evosax default cma_std_init: float = 0.5 # post-retrieval cleanup (toggled on the result pane, no re-run needed) post_filter_lam: bool = False post_filter_tau: bool = False
[docs] @dataclass(frozen=True) class BeamPathMirror: """One mirror in the dispersion stage's beam-path stack (a serialisable row). A *stable* description of a single mirror reflection, so the stack survives a save/reload (unlike the live registry keys the GUI uses at runtime). A built-in mirror is named (``name`` in :data:`croak.mirrors.BUILTIN_COATINGS` or :data:`croak.mirrors.BUILTIN_MIRRORS`); a custom mirror leaves ``name`` empty and points ``custom_file`` at a CSV of wavelength + phase/GDD. Parameters ---------- name : str Built-in coating/mirror name; empty for a custom-file mirror. polarization : {"s", "p"} Polarisation for coatings (ignored by phase-only mirrors). bounces : int Number of reflections (0 disables the row). direction : {"remove", "add"} ``"remove"`` back-propagates the mirror out of the pulse (negative bounce count); ``"add"`` propagates it in. custom_file : str CSV path for a custom mirror (empty for a built-in). custom_lam_col, custom_val_col : int 0-based wavelength and value column indices in ``custom_file``. custom_unit : str Wavelength unit of ``custom_file`` (e.g. ``"nm"``). custom_datatype : {"phase", "gdd"} Whether the value column is spectral phase (rad) or GDD (fs²). """ name: str = "" polarization: str = "s" bounces: int = 0 direction: str = "remove" custom_file: str = "" custom_lam_col: int = 0 custom_val_col: int = 1 custom_unit: str = "nm" custom_datatype: str = "phase"
[docs] @dataclass class DispersionParams: """Stage 4: post-retrieval dispersion compensation applied to the pulse. Taylor coefficients (fs², fs³, fs⁴), an optional active gas (pressure + path), built-in material thicknesses (mm by name), and the beam-path mirror stack. This stage tunes/compresses the retrieved pulse; it does not re-run retrieval. Notes ----- Scalar fields are declared before the ``material_thickness_mm`` table and the ``mirrors`` array-of-tables because TOML requires a table's scalar keys to precede its sub-tables. """ gdd_fs2: float = 0.0 tod_fs3: float = 0.0 fod_fs4: float = 0.0 # active gas: pressure is baked into n(λ); the path length acts as thickness gas_name: str = "" gas_pressure_bar: float = 1.0 gas_path_cm: float = 0.0 # built-in materials (croak.materials.MATERIALS): {name: thickness_mm} material_thickness_mm: dict[str, float] = field(default_factory=dict) # beam-path mirror stack (signed by each row's direction/bounces) mirrors: list[BeamPathMirror] = field(default_factory=list)
[docs] @dataclass class UncertaintyParams: """Stage 5: the FWHM-uncertainty bootstrap configuration. The ``method`` selects a statistical bootstrap — ``"parametric"`` (add noise and re-retrieve) or ``"delay"``/``"frequency"`` (resample the trace) — the substrate-thickness *systematic* ``"thickness"`` (Monte-Carlo over the measured thickness prior), or the fast analytic ``"covariance"`` (linearised Gauss–Newton covariance; see :func:`croak.covariance.covariance_uncertainty`). The remaining fields tune the resampling, noise model and confidence interval. """ method: str = "parametric" n_resamples: int = 200 # noise model for the parametric/covariance methods: from the residual, or manual σ noise_source: str = "residual" # "residual" | "manual" noise_sigma: float = 0.01 # fraction of peak (manual source) warm_start: bool = True collect_profiles: bool = True interval: str = "bias-corrected" # "bias-corrected" | "percentile" # 1σ of the measured substrate thickness (µm), for the "thickness" method; the # central value is taken from RetrieveParams.thickness_um thickness_sigma_um: float = 0.0 # evaluate the FWHM/band at the dispersion-stage beamline point instead of the # measurement plane — propagate every replicate through the stage-4 dispersion propagate_to_dispersion_point: bool = False