feat: LPSE with SRS (and 1d support) - #320
Open
physicistphil wants to merge 4 commits into
Open
Conversation
- New raman.py light-wave module and SRS coupling terms in epw.py - Raman seed/light-wave config in datamodel and helpers - SRS example config (configs/envelope-2d/srs.yaml) and test - Document SRS options in lpse2d config docs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The field-save interpolators assumed >=2 transverse cells: interpax.interp2d returns NaN off a single y-node, and RegularGridInterpolator fills 0 for a single-node y axis. This blanked all real-space/k-space field artifacts (fields.xr, k-fields.xr, plots/<field>/*) for ny=1 runs while the series.xr scalars stayed valid. Add an ny==1 path that interpolates in x only and keeps the single transverse row, for both the in-solve field saver and the background-density save. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- New adept/_lpse2d/diagnostics.py: laser-budget window means (names and
definitions match osiris_lpi/laser_budget.py), EPW growth fit copied
verbatim from osiris_lpi/epw_growth.py (per-w0 rates), electron energy
as cumulative EPW dissipation.
- Default save now also logs epw_energy (OSIRIS units: fields in me*c*w0/e,
lengths in c/w0), epw_dissipation (using the solver's own Landau +
collisional rates via the new module-level landau_damping_rate),
epw_boundary_loss, and discrete two-point flux probes for the laser
budget (incident/transmitted/reflected/backrefl, normalized to I0).
Probes sit at 2*boundary_width, clear of the absorber skirt; the legacy
reflectivity probe at 1.6*bw is unchanged for back-compat.
- post_process logs these scalars as MLflow metrics (previously only
write/plot times) and adds laser-budget / EPW-fit / electron-energy plots.
- terms.epw.source.{noise_amplitude,noise_seed} are config-driven; the
resolved seed is pinned into the cfg pre-log_params so runs are exactly
reproducible. Removed the dead density.noise draws that perturbed the
global RNG stream.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
terms.light.pump_depletion: true evolves the pump with the same staggered explicit FD envelope scheme as the Raman light (new core/light.py, CoupledLight), ported from the isPumpDepletion path of lpse-matlab m201805_matlabLpse_v11.m: - pump RHS: diffraction + local detuning + depletion coupling -i e/(4 w1 me) (laplacian phi) E1 (conjugate-free, partner-frequency denominator; Manley-Rowe-consistent with the E1 and EPW couplings) - two-point boundary injector at xmin + drivers.E0.offset (default 2*bw), multi-color, MATLAB amplitude calibration - both waves advance inside one staggered real/imag update (advancing them independently would break the discrete conservation) - substep limit = min over both carriers; E0 high-k filter in the EPW SRS source on the dynamic-pump path (MATLAB skips it on the static path) - budget flux probes convert the exact discrete two-point flux to physical flux via the FD group-velocity factor sin(k_grid dx)/(k dx); metrics normalize to the measured incident flux (the injector launches sin(k0 dx)/sin(k_grid dx) ~ 0.98 of nominal amplitude at 8 cells/lambda) - default off; the prescribed-pump path is untouched (verified: the three pre-existing SRS tests pass unchanged) Tests: pump injector flux+amplitude calibration against the discrete- dispersion prediction; seeded Raman-amplifier energy-budget closure (S_left - S_right vs 2x the field-only EPW energy rates -- the kinetic sloshing half doubles the electron heating; closes to ~4%, asserted <10%); R+T+absorbed==1; depletion actually reduces transmission vs the prescribed pump; epw_energy normalization; noise-seed reproducibility. Docs: overview pump-depletion note replaced, SRS diagnostics table added, config.md new keys (terms.light, probe_offset, noise_amplitude/seed, drivers.E0.offset/turn_on_time); datamodel updated to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds stimulated Raman scattering (backward SRS) to the
envelope-2d(lpse2d) solver. Until now the solver evolved the EPW potential against a prescribed pump with a TPD source only; theE1scattered-light field existed in the state vector but was never advanced. This PR evolvesE1with a paraxial finite-difference solver and closes the loop by adding the SRS source to the EPW equation, so backscatter grows self-consistently from noise (or from an optional injected seed).The physics is a direct translation of the
raman.solver = 'fd'branch of lpse-matlab (m201805_matlabLpse_v11.m); line references to the MATLAB source are kept in the code comments so the two can be diffed by hand.What's added
Raman light solver (
adept/_lpse2d/core/raman.py, new)Evolves the scattered-light envelope at
w1 = w0 - wp0:with the cross-derivative terms of the 2D paraxial operator, and the same staggered explicit update as MATLAB's
lightSplitStep(real part from the RHS att, imaginary part from the RHS att + dt/2).SRS source in the EPW equation (
core/epw.py)srsSource = i e wp0/(4 me w0 w1) · (n/n_env) · E0·conj(E1), added to the potential each step.E1is high-k filtered before the product (MATLAB'sisSuppressHighKSource, cutoff1.2 × k1_max) so only wavevectors near the light-wave envelope contribute; the pump is prescribed, so it is not filtered, matching the MATLAB static-laser path.Sub-cycling and stability (
helpers.py,datamodel.py)The light update is conditionally stable (
dt < ~dx² w1/c²), so it is sub-cycled inside each EPW step with the EPW potential held fixed. The number of sub-steps is derived from the stability bound generalized to 2D, or can be pinned withgrid.light_substeps— a value that violates the bound raises rather than silently going unstable. Absorbing boundaries are applied every sub-step, since light crosses the absorber at ~c.Optional Raman seed (
drivers.E1)A two-point antisymmetric injector at
x = xmax - offsetlaunches a-x-propagating wave at the localk1, with a configurable turn-on ramp and an optional 4th-order super-Gaussian transverse profile. The default offset (1.6 × boundary_width) keeps the injector clear of the absorber's tanh skirt, and a closer one warns. If the density at the injector is above thew1critical density the seed is evanescent, so setup fails with a message pointing at the three ways out (lowerdensity.max, moveoffset, or dropE1and run noise-seeded).Diagnostics
With SRS on, the default time series gains
e1_sqandreflectivity— the latter issqrt(eps1)·<|E1_y|²>_y / E0_source²at a probe on the low-density side, with thesqrt(eps1)factor accounting for the reduced group velocity relative to the vacuum pump.make_series_xarraysis now generic over whatever keys the save function returns instead of hard-codinge_sq/max_phi.Quasi-1D (
ny = 1) support1D SRS is the cheap configuration to run and the one the MATLAB
srs_1Dcase uses, but the field-save path assumed ≥2 transverse cells:interpax.interp2dreturns NaN off a single y-node andRegularGridInterpolatorrejects a single-node axis (filling 0). Both silently blanked every field artifact while the scalar series stayed valid. Added an x-only interpolation path forny == 1in the in-solve field saver and the background-density save, and adjusted the plotting to emit line plots vskxinstead of emptykx–kymaps.Also in passing:
density.basis: uniformnow honors avalkey instead of always returning 1.0, and the complex-dtype check inmake_field_xarraystests the actual float view rather than assumingcomplex128.Config surface
Example config:
configs/envelope-2d/srs.yaml— noise-seeded backward SRS on a 0.18–0.28 n_c linear ramp, thesrs_1Dcase.Validation
tests/test_lpse2d/test_srs.py:test_srs_growth_rate(parametrized 2D andny = 1) — noise-seeded homogeneous SRS. Fits the log-slope of the EPW energy over the late-time window and compares to the analytic backward-SRS rategamma0 = k v_os/4 · wpe/sqrt(w_ek w_s), evaluated at the phase-matchedkfrom a fixed-point solve of the Bohm–Gross/EM dispersion pair, with the pump wavenumber snapped to the FFT grid the way the solver launches it and the local density swelling folded intov_os. Agrees to 35%.test_srs_seed_propagation— pump and noise off, seed only. Checks the injected wave travels in-x, that its measured wavenumber matches the localk1to 5%, and that its amplitude matches the injector calibrationE1_source · sinc(k1 dx) / eps1^(1/4)to 30%.Notes and limitations
E0is reconstructed from the driver each step and is unaffected byE1, so reflectivities are only meaningful in the undepleted regime. Coupling depletion back intoE0is the natural follow-up.light_substepsextra RHS evaluations per EPW step, each with several FFT-free stencil passes plus oneifft2of the potential Laplacian per step. On the shipped example config this is single-digit sub-steps; shortdxat fixeddtraises it quadratically.drivers.E0.num_colors) composes with this for free — the Raman coupling consumesE0as a field, so broadband SRS works without further changes, though it is not exercised by a test here.Known issue, pre-existing and not addressed here:
drivers.E0.shape: arbitrary(the learnable amplitude/phase driver) looks broken onmain. #168 broadcast the driver output to(num_colors, ny)and switchedlaser.Light.laser_updateto index it as[i, :], butArbitraryDriver.__call__overridesUniformDriver.__call__and still returns 1-Dphases/intensities(modules/driver.py:174-178), so that path would raise on the first laser update.uniform/gaussian/lorentzianall inherit the broadcasting__call__and are fine, and no shipped config or test exercisesarbitrary, which is presumably why it has gone unnoticed. Flagging it rather than fixing it here to keep this PR to SRS — happy to fold in the one-line broadcast fix if reviewers would rather have it in the same change.Base branch
Branched from
mainand independent of the osiris wrapper (#279) — the two lines of work touch a disjoint set of files, and this one is confined toadept/_lpse2d,configs/envelope-2d,docs/source/solvers/lpse2d, andtests/test_lpse2d.🤖 Generated with Claude Code