Simulation
Model
The field is a complex envelope \(A(x, y, t)\) with carrier \(\exp[i(k_0 z - \omega_0 t)]\), propagated with the unidirectional pulse propagation equation (UPPE) in the frame moving at the group velocity at \(\lambda_0\):
Linear part: diffraction and the full dispersion of the gas (Sellmeier, Börzsönyi et al., Appl. Opt. 47, 4856, 2008).
Kerr part: instantaneous electronic response, with self-steepening (
self_steepening=Falsegives \(\gamma = n_2\omega_0/c\)). It is tapered to zero at the edges of the spectral window.Not modelled: ionisation and plasma, Raman response of molecular gases (N₂, air), the folded off-axis geometry (incidence astigmatism, real spot pattern), plates, windows and pressure gradients, vector effects.
Two solvers share the same pulses, diagnostics and files:
lens frame (recommended) |
standard |
|
|---|---|---|
classes |
||
grid |
follows the beam: a few waists wide, whatever \(d\) |
fixed: a few mirror spots wide |
diffraction |
paraxial |
non-paraxial |
use |
every cell, near-concentric ones above all |
cross-checks |
The lens frame is explained in The lens frame. The standard solver keeps the field in \((x, y, \Omega)\): the linear step needs only 2D spatial FFTs and the Kerr term only 1D temporal FFTs. They are combined by Strang splitting, with an RK4 Kerr step of size \(h = \varphi_\text{max}/(k_0 n_2 I_\text{peak})\).
The cell
MPC is a symmetric two-mirror cell, treated unfolded: the run starts right
after a mirror and each pass goes to the cell centre, on to the next mirror, and bounces.
mirror ──(d/2)──▶ cell centre ──(d/2)──▶ next mirror │ bounce
The pulse is defined at the cell centre, and the diagnostics are taken there after every pass. That plane is the waist of the eigenmode with a flat wavefront, so a Hermite–Gauss decomposition there is exact. A bounce applies the mirror curvature (focal length \(R/2\)), its reflectivity, GDD, TOD or measured phase, and an optional soft clear aperture.
With \(g = 1 - d/R\), the Gouy phase per pass is \(\theta = \arccos g\) and
MPC.herriott(R, N, k, medium) builds the cell that is re-entrant after \(N\) passes,
\(d = R\,[1\pm\cos(2\pi k/N)]\) (+: concentric branch). A linear re-entrant cell images any
input onto the output, top-hats included: run it once with \(n_2 = 0\) and check that it does.
Nonlinear mode matching. For a Gaussian at constant power \(P\), the Kerr lens changes the
eigenmode as if \(\lambda \to \lambda\sqrt{1 - P/P_c}\), with \(P_c = \lambda^2/(2\pi n_0 n_2)\):
the matched waist and spot are the linear ones times \((1-P/P_c)^{1/4}\).
cell.match(pulse) returns them with the B-integral per pass. A pulse is matched at one power only:
its wings see a mode closer to the linear one, a mismatch that the 4D simulation resolves.
The pulse
A pulse is a spectrum times a spatial beam, \(A(x, y, \Omega) = E(\Omega)\,U(x, y; \Omega)\).
E = m.gaussian_spectrum(grid, fwhm_fs=350) # or fwhm_nm=...
E = m.measured_spectrum(grid, wl_nm, intensity, phase_rad=ph) # spectrometer (+ retrieved phase)
E = m.spectrum_from_temporal_field(grid, t_fs, field) # retrieved pulse
E = m.add_spectral_phase(grid, E, gdd_fs2=500, tod_fs3=2e3)
beam = m.Beam(m.top_hat(4e-3),
zernike={"astig_0": 0.05, "coma_x": 0.02}, # RMS waves at lambda0
spatial_chirp_mm_per_nm=(0.01, 0), pft_fs_per_mm=(2.0, 0))
beam = m.Beam(m.knife_edge(m.gaussian(w), 0.5 * w, axis="x", side="+"))
beam = m.Beam(m.measured_profile(camera_image, pixel_size=5.5e-6))
Where the beam is defined matters: a top-hat imaged on the mirror and a top-hat focused at the centre are different inputs.
Builder |
Beam defined |
|---|---|
at the cell centre (matched Gaussian, tests) |
|
collimated, then focused into the cell by an achromatic lens (top-hat → Airy pattern) |
|
imaged on a plane of the cell, by default the first mirror, in physical coordinates there |
pulse = m.Pulse.from_near_field(grid, E, m.Beam(m.top_hat(4e-3)), energy,
nf_size=12e-3, nf_points=192, target_waist=mm["w0"])
pulse = m.imaged_near_field_lens(grid, E, m.Beam(m.top_hat(1.12 * mm["w_mirror"])), energy, cell)
Top-hats, hard edges and Airy patterns need a larger box (box_factor 10–14).
Running
sim = m.LensFrameSimulation(pulse, cell, phi_max=0.02, gouy_step=0.05, mode_reference=mm["w0"],
profile_wavelengths=[1010e-9, 1030e-9, 1050e-9], profile_bandwidth=2e-9)
sim.run() # or sim.run(n_passes=1) to go pass by pass
sim.save("run.npz")
record_every,near_field=False(no collimated-beam diagnostics, a few seconds less per record),record_mirror: what is recorded.profile_wavelengths: 1D profiles of single colours (bandpassprofile_bandwidth) at the centre, in the collimated beam and on the mirror, at every pass.Mirror records are in the frame of the beam: multiply positions by
sim.mirror_scaleand divide fluences by its square. The saved file keepsmirror_scale.
Long runs stop cleanly and resume:
if os.path.exists("ckpt.pkl"):
sim.resume("ckpt.pkl")
sim.run(max_seconds=3600) # stops after the pass that exceeds one hour
sim.checkpoint("ckpt.pkl")
Between passes, any element can be applied to sim.Aw (the field at the centre, physical in both
solvers), for example an aperture:
aperture = (grid.r2 < (1.5e-3) ** 2)[:, :, None]
for p in range(cell.n_passes):
sim.run(n_passes=1, verbose=False)
sim.Aw *= aperture
New physics (ionisation, Raman, …) goes in the nonlinear term: subclass the solver and override
nonlinear(), which works in \((x, y, \Omega) \leftrightarrow
(x, y, t)\). LensFrameSolver inherits it; there \(|B|^2\) is the
intensity times \(s^2\). Such a solver runs its RK4 in NumPy, not in the Numba kernels
(Numba kernels (CPU)).
Results
sim.history has one entry per recorded pass: energy, B-integral, peak power and intensity,
spectrum, fluence, spectrograms \(S(x,\lambda)\) and \(S(\theta,\lambda)\), Hermite–Gauss
content, homogeneity, beam radii and \(M^2\) (Recorded history). The functions of
thunder4d.diagnostics analyse any field:
import numpy as np
from thunder4d import diagnostics as dg
x, lam, S = dg.spectrogram_x_lambda(grid, sim.Aw)
c = dg.compress(grid, sim.Aw) # best uniform GDD, compressed pulse
d = np.load("run.npz") # the saved history