Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
182 commits
Select commit Hold shift + click to select a range
58b5038
lpse2d: LPSE term parity -- TPD coefficient, thermal noise, Landau fo…
physicistphil Sep 14, 2026
87cd442
lpse2d: per-operation EPW energy ledger (terms.epw.energy_ledger)
physicistphil Sep 14, 2026
32f8c44
lpse2d: spectral light propagator and collisional light absorption
physicistphil Sep 14, 2026
77a545b
lpse2d: combined TPD+SRS solver (terms.epw.solver: combined)
physicistphil Sep 14, 2026
91b8de1
lpse2d: LPSE deck translator, metrics/field readers, and LPSE-style d…
physicistphil Sep 15, 2026
9ed9083
lpse2d: make the SRS source k-filter optional (LPSE lw.kFilter, off b…
physicistphil Sep 15, 2026
ffaf4b9
lpse2d: transverse projection of the SRS light sources and band-limit…
physicistphil Sep 15, 2026
744eddb
lpse2d: oblique incidence of the pump (drivers.E0.angle)
physicistphil Sep 15, 2026
a829cce
lpse2d: the original LPSE density profiles (density.basis: lpse-<shape>)
physicistphil Sep 15, 2026
9b78d9b
lpse2d: LPSE HPE controls and instruments (terms.hpe.*)
physicistphil Sep 15, 2026
2c6db21
lpse2d: absolute-threshold bisection driver (adept._lpse2d.threshold)
physicistphil Sep 15, 2026
704782c
lpse2d: separate absorber strength for the light fields (terms.light.…
physicistphil Sep 15, 2026
2cba188
lpse2d: checkpoint / restart (save.checkpoint, restart.file) and LPSE…
physicistphil Sep 15, 2026
a498561
lpse2d: LPSE diagnostics (Poynting fields, Thomson probes) and therma…
physicistphil Sep 15, 2026
c94d7b4
lpse2d: pump beamlets, transverse profile, Kubo-Anderson bandwidth, p…
physicistphil Sep 15, 2026
5c72f57
lpse2d: translator defaults grid.antiAliasing.range to LPSE's none wh…
physicistphil Sep 15, 2026
01805c3
lpse2d: the combined solver damps the pump with the light absorber (n…
physicistphil Sep 15, 2026
e39a9fb
lpse2d: translator reads LPSE's hpe.metrics.fluxMetric.N / powerMetri…
physicistphil Sep 15, 2026
e3b5f8d
lpse2d: wrap the HPE power-metric prefix line; translator test uses L…
physicistphil Sep 15, 2026
4c70767
lpse2d: LPSE particle-wall semantics for HPE; flux bins in keV; VminO…
physicistphil Sep 15, 2026
3c42990
lpse2d: spectral light solver sub-cycles to one cell per sub-step for…
physicistphil Sep 15, 2026
abfd8ea
feat(lpse2d): parity harness package and reference-run resolver (plan…
physicistphil Sep 16, 2026
e47a671
fix(lpse2d): read_frames returns LPSE 2-D frames in their C-order (Nx…
physicistphil Sep 16, 2026
c592111
test(lpse2d): reverse-mode gradient tests through every solver path (…
physicistphil Sep 16, 2026
8b83a50
feat(lpse2d): noise_calibrate: lpse ports LPSE's thermal-noise consta…
physicistphil Sep 16, 2026
f8fa145
fix(lpse2d): keep the FD light fields transverse; ledger of the proje…
physicistphil Sep 16, 2026
0988524
feat(lpse2d): HPE energy-conservation multiplier ported from LPSE (pl…
physicistphil Sep 16, 2026
dbd74b5
feat(lpse2d): three-component light fields, E_z purely transverse on …
physicistphil Sep 16, 2026
bcd4b1a
feat(lpse2d): beam polarization angle for the pump, the beams and the…
physicistphil Sep 17, 2026
27c4446
feat(lpse2d): in-plane magnetic field and p_z in the 2-D HPE push (pl…
physicistphil Sep 17, 2026
6d6b503
feat(lpse2d): initial_perturbation -- LPSE's plane-wave initial condi…
physicistphil Sep 17, 2026
9b4cbf9
feat(lpse2d): source windows, absorber/injector source suppression, I…
physicistphil Sep 17, 2026
481cb44
feat(lpse2d): parity batch driver, run tags, and the plan-2 job matrices
physicistphil Sep 17, 2026
7667936
fix(lpse2d): parity runs are float64 -- set JAX_ENABLE_X64 in the CLI…
physicistphil Sep 17, 2026
b3b89cb
feat(lpse2d): terms.light.snap_beam_ky -- launch an oblique pump at t…
physicistphil Sep 17, 2026
faa3151
fix(lpse2d): terms.light.transverse_fields defaults to false (plan 2 …
physicistphil Sep 17, 2026
2056c0c
feat(lpse2d): light spectrum probes -- save.light_spectrum (plan 2 L.6)
physicistphil Sep 17, 2026
206ebb3
test(lpse2d): s-pol no-TPD run compares the free-decay energy to roun…
physicistphil Sep 17, 2026
1ebdb86
feat(lpse2d): FD light stencil order 2/4/6 and leftward pump beams (p…
physicistphil Sep 17, 2026
81ff164
feat(lpse2d): FD injector for oblique, multiple and profiled pump bea…
physicistphil Sep 17, 2026
ae0bfe6
feat(lpse2d): resonance absorption of the pump at the critical surfac…
physicistphil Sep 17, 2026
84b0886
feat(lpse2d): PML absorbing layers for the FD light fields (plan 2 L.2)
physicistphil Sep 17, 2026
94f513f
feat(lpse2d): LPSE's absoluteThreshold search and its deck keys (plan…
physicistphil Sep 17, 2026
887c926
test(lpse2d): the FD injector now takes an oblique leftward beam (pla…
physicistphil Sep 17, 2026
fa4736c
fix(lpse2d): refuse TPD + SRS on the separate-equation solver, as LPS…
physicistphil Sep 17, 2026
078ec22
feat(lpse2d): quasilinear evolution of the electron distribution -- t…
physicistphil Sep 18, 2026
658b30d
style(lpse2d): wrap a qle docstring line
physicistphil Sep 18, 2026
43ee4bb
feat(lpse2d): finite-difference IAW solver with plasma-flow profiles …
physicistphil Sep 18, 2026
a7b34a0
fix(lpse2d): the tpd-srs-iaw example runs LPSE's combined formulation…
physicistphil Sep 18, 2026
bcee98d
feat(lpse2d): terms.light.one_way -- directional mask on the evolved …
physicistphil Sep 10, 2026
bea5d0b
fix(lpse2d): translator reads laser.pulseShape.* and bracketed vector…
physicistphil Sep 21, 2026
0a9a3d0
feat(lpse2d): implicit Fokker-Planck step for the QLE; light-only dec…
physicistphil Sep 21, 2026
3cdb700
fix(lpse2d): 1-D decks keep a periodic y boundary; compare() tolerate…
physicistphil Sep 21, 2026
621b27b
fix(lpse2d): QLE gradient falls back to the plain difference at a pla…
physicistphil Sep 21, 2026
aee5f9b
fix(lpse2d): refuse y-face beams in the translator and grazing beams …
physicistphil Sep 21, 2026
dcc187e
style(lpse2d): wrap a translator message
physicistphil Sep 21, 2026
28d150f
fix(lpse2d): the IAW density feeds back as Nelf * n_b(x), not as a fr…
physicistphil Sep 22, 2026
7248740
fix(lpse2d): the general FD injector wrote only one of a leftward bea…
physicistphil Sep 22, 2026
cc90a23
chore(lpse2d): parity batches for the 2026-09-22 IAW-feedback and inj…
physicistphil Sep 22, 2026
2fe551a
fix(lpse2d): translator maps laser.evolution.riseTime to drivers.E0.t…
physicistphil Sep 22, 2026
bd17d05
feat(lpse2d): pump launched from LPSE injector files -- drivers.E0.in…
physicistphil Sep 23, 2026
8531b26
fix(lpse2d): translated decks open the pump envelope before t = 0
physicistphil Sep 23, 2026
773e4b1
fix(lpse2d): translator anti-aliases at LPSE's default range 0.3334 w…
physicistphil Sep 23, 2026
8de4751
fix(lpse2d): pulse shape is LPSE's power factor, applied on every pum…
physicistphil Sep 23, 2026
a01bea2
fix(lpse2d): beam super-Gaussian defaults to LPSE's order 4; order or…
physicistphil Sep 23, 2026
549043a
fix(lpse2d): the fd IAW step is LPSE's -- drive sign, k-space cadence…
physicistphil Sep 23, 2026
dcbfa93
fix(lpse2d): combined solver damps and seeds noise as LPSE; separate …
physicistphil Sep 23, 2026
6eda074
fix(lpse2d): absorbing layers per field and injector planes as LPSE; …
physicistphil Sep 24, 2026
46833f9
fix(lpse2d): translator keys and defaults as the C++ reads them; LPSE…
physicistphil Sep 24, 2026
8aa0fdf
fix(lpse2d): translated grids keep LPSE's node counts; fd-combined de…
physicistphil Sep 24, 2026
6a73f94
feat(lpse2d): translate LPSE's own solver time steps; exact fd-light …
physicistphil Sep 24, 2026
914a0d7
feat(lpse2d): LPSE step order with time-interpolated sources
physicistphil Sep 24, 2026
8a95853
fix(lpse2d): combined solver drives the IAW with the whole field at wp0
physicistphil Sep 24, 2026
c8c8628
fix(lpse2d): IAW absorber acts over the IAW step
physicistphil Sep 24, 2026
d130908
fix(lpse2d): IAW band has its own wavenumber cap
physicistphil Sep 24, 2026
36582f0
fix(lpse2d): LPSE IAW solvers window the drive after the Laplacian
physicistphil Sep 24, 2026
e8bfef4
fix(lpse2d): translated density on LPSE's nodes; LPSE's cell size
physicistphil Sep 24, 2026
421e0ac
fix(lpse2d): rectangular dealias uses LPSE's anti-aliasing indices
physicistphil Sep 24, 2026
c1f7c77
fix(lpse2d): TPD source windowed after its k-space assembly
physicistphil Sep 24, 2026
8ec5102
test(lpse2d): neutralise the pump's own absorber in the FD projection…
physicistphil Sep 24, 2026
49a91e6
fix(lpse2d): light coupling sources suppressed as LPSE's calculateSou…
physicistphil Sep 24, 2026
b71fc8e
feat(lpse2d): HPE resonance at LPSE's expanded Bohm-Gross frequency
physicistphil Sep 24, 2026
f874833
feat(lpse2d): static pump swells by LPSE's constant factor by default
physicistphil Sep 24, 2026
e3d6b85
fix(lpse2d): absolute-threshold criterion as LPSE's checkIsAboveThres…
physicistphil Sep 24, 2026
b4828ad
fix(lpse2d): thermal-filamentation source as LPSE's
physicistphil Sep 24, 2026
1a8194f
feat(lpse2d)!: native defaults follow LPSE (C++ conventions)
physicistphil Sep 24, 2026
df54490
feat(lpse2d): LPSE's combined solver on the FD light propagator
physicistphil Sep 24, 2026
0001142
test(lpse2d): tests follow the C++ defaults; exchange mask in the rot…
physicistphil Sep 24, 2026
ffb03ff
fix(lpse2d): FD light absorbs each field at its own rate
physicistphil Sep 24, 2026
b866c23
test(lpse2d): restart test keeps the flat noise it checks
physicistphil Sep 24, 2026
306fc34
test(lpse2d): s / p launch comparison at the same light step
physicistphil Sep 24, 2026
350c32e
test(lpse2d): combined + FD light without an evolved pump is refused …
physicistphil Sep 24, 2026
044784f
fix(lpse2d): combined solver keeps the k = 0 mode of E1, as LPSE
physicistphil Sep 30, 2026
59e01d0
fix(lpse2d): combined solver builds its noise kick for the light sub-…
physicistphil Oct 8, 2026
1e3cf4a
fix(lpse2d): combined solver injects the pump with the spectral injector
physicistphil Oct 8, 2026
f6b1e1a
fix(lpse2d): combined-mode field saves and Raman flux maps exclude th…
physicistphil Oct 8, 2026
27418ef
fix(lpse2d): LPSE binary density files are not transposed twice
physicistphil Oct 8, 2026
f6c7429
fix(lpse2d): quasi-1D HPE push keeps periodic crossings for the wall …
physicistphil Oct 8, 2026
278fda7
test(lpse2d): the test_010 beam-direction translation runs from a che…
physicistphil Oct 8, 2026
4a3e3b1
Merge lpse2d/lpse-parity (PR #372 review fixes) into lpse2d/lpse-pari…
physicistphil Oct 8, 2026
0c8008c
fix(lpse2d): FD-IAW resampling aligns the zero frequency on odd grids
physicistphil Oct 8, 2026
c715248
fix(lpse2d): parity CLI reads lpse.metrics in the flattened reference…
physicistphil Oct 8, 2026
b00d903
test(lpse2d): verify baseline test skips when its run is not on the t…
physicistphil Oct 8, 2026
f5dc1a4
test(lpse2d): pin the noise seed of the in-run QLE flattening test
physicistphil Oct 8, 2026
3b582f6
test(lpse2d): QLE flattening is checked over the saved times, criteri…
physicistphil Oct 9, 2026
72b6df4
fix(lpse2d): combined solver injects the Raman seed (drivers.E1)
physicistphil Oct 9, 2026
c387f68
fix(lpse2d): HPE wall-energy bins count only energies inside their edges
physicistphil Oct 9, 2026
e5c7327
fix(lpse2d): one normal-incidence beam keeps its frequency shift and …
physicistphil Oct 9, 2026
8588bf1
Merge lpse2d/lpse-parity (PR #372 review round 2) into lpse2d/lpse-pa…
physicistphil Oct 10, 2026
033d613
docs(lpse2d): the combined-solver seed follows drivers.E1.polarization
physicistphil Oct 10, 2026
c214230
fix(lpse2d): QLE explicit update is reverse-mode differentiable
physicistphil Oct 10, 2026
aa48996
fix(lpse2d): laser-budget metrics follow the pump's entrance face
physicistphil Oct 10, 2026
c5fee59
fix(lpse2d): a disabled LPSE laser translates to no pump
physicistphil Oct 16, 2026
bc78533
fix(lpse2d): Raman flux diagnostics use the combined field's carrier
physicistphil Oct 16, 2026
de42398
Merge lpse2d/lpse-parity (PR #372 review round 3) into lpse2d/lpse-pa…
physicistphil Oct 16, 2026
5eae480
fix(lpse2d): the E1 light-spectrum probe uses the combined carrier
physicistphil Oct 16, 2026
3b810d8
fix(lpse2d): the FD combined solver injects the Raman seed (drivers.E1)
physicistphil Oct 16, 2026
962e2a1
fix(lpse2d): FD-IAW PPM fluxes are upwinded per shared face
physicistphil Oct 16, 2026
f687c21
fix(lpse2d): the combined E1 light-spectrum probe records the Raman l…
physicistphil Oct 16, 2026
e71050f
fix(lpse2d): the IAW absorber damps over the whole strided IAW step
physicistphil Oct 16, 2026
88a65bd
fix(lpse2d): the IAW and the Raman absorption see the combined light …
physicistphil Oct 16, 2026
bac837e
fix(lpse2d): combined runs skip the separate light solvers and their …
physicistphil Oct 16, 2026
fe8c40c
Merge lpse2d/lpse-parity (PR #372 review round 4) into lpse2d/lpse-pa…
physicistphil Oct 17, 2026
5a56eb0
fix(lpse2d): combined-mode absorption follows the evolved ion density
physicistphil Oct 17, 2026
cc48019
fix(lpse2d): parity batch dry runs record nothing
physicistphil Oct 17, 2026
f123c5e
fix(lpse2d): the deck translator runs LPSE's square cells
physicistphil Oct 19, 2026
3fccddb
fix(lpse2d): the FD pump injector refuses the time controls it does n…
physicistphil Oct 19, 2026
a415138
Merge lpse2d/lpse-parity (PR #372 review round 5) into lpse2d/lpse-pa…
physicistphil Oct 19, 2026
d4fab87
fix(lpse2d): resonance absorption -- one enable predicate, absolute c…
physicistphil Oct 19, 2026
c440b7e
fix(lpse2d): the combined field is damped at the Raman absorption
physicistphil Oct 19, 2026
6724180
fix(lpse2d): threshold bisection refuses a zero lower bound
physicistphil Oct 19, 2026
ae6a6da
Merge lpse2d/lpse-parity (PR #372 review round 6) into lpse2d/lpse-pa…
physicistphil Oct 19, 2026
8b1430b
fix(lpse2d): the quasi-1D resonance-absorption filter keeps its passband
physicistphil Oct 19, 2026
38d5783
fix(lpse2d): KAP phase histories are prefix-stable as the run grows
physicistphil Oct 19, 2026
73c8b7d
fix(lpse2d): restart rebases the light-spectrum probes onto the resum…
physicistphil Oct 19, 2026
df8a935
fix(lpse2d): parity batch files keep every window; unknown job keys a…
physicistphil Oct 19, 2026
7821ca1
fix(lpse2d): the LPSE threshold search refuses a zero starting intensity
physicistphil Oct 19, 2026
8f5668d
fix(lpse2d): a light-only PML run uses the pump's layer
physicistphil Oct 20, 2026
2577b05
fix(lpse2d): build the separate EPW solver only on the separate path
physicistphil Oct 20, 2026
55fd66f
fix(lpse2d): refuse fd rotation coupling with the transverse projection
physicistphil Oct 20, 2026
3a096b8
Merge branch lpse2d/lpse-parity (round-7 fixes) into lpse2d/lpse-pari…
physicistphil Oct 20, 2026
4223609
fix(lpse2d): fd rotation refusal follows plan2's transverse_source de…
physicistphil Oct 20, 2026
adec784
fix(lpse2d): FD combined source takes the EPW window and the pump k-f…
physicistphil Oct 20, 2026
d24121d
docs(lpse2d): the combined depletion is unprojected
physicistphil Oct 20, 2026
19cbe2e
fix(lpse2d): put the interpolated field save on a coarser grid, in x-…
physicistphil Oct 20, 2026
7081182
test(lpse2d): gradient objective takes the EPW energy from the grid
physicistphil Oct 20, 2026
952252c
fix(lpse2d): the spectral light band ignores the EPW wavenumber cap
physicistphil Oct 20, 2026
ca6ced8
fix(lpse2d): signed EPW dissipation with HPE allow_growth
physicistphil Oct 20, 2026
7a22883
Merge branch lpse2d/lpse-parity (light band, signed dissipation) into…
physicistphil Oct 20, 2026
9119fd2
fix(lpse2d): combined-mode EPW dissipation and lw heating use the com…
physicistphil Oct 20, 2026
d9f0d46
Merge branch lpse2d/lpse-parity (combined-mode damping in diagnostics…
physicistphil Oct 20, 2026
8bd768f
test(lpse2d): combined boundary-loss diagnostic uses the combined layer
physicistphil Oct 20, 2026
1f35493
fix(lpse2d): the FD combined light step obeys the combined bound with…
physicistphil Oct 20, 2026
72f578b
fix(lpse2d): the FD IAW fine-grid band is the coarse mask mode by mode
physicistphil Oct 20, 2026
3e8f4f9
docs(lpse2d): HPE gamma limits bound the rate before the energy-conse…
physicistphil Oct 20, 2026
957a791
Merge branch lpse2d/lpse-parity (HPE limit docs) into lpse2d/lpse-par…
physicistphil Oct 20, 2026
0d50e67
fix(lpse2d): the dissipation diagnostic follows the evolved QLE Landa…
physicistphil Oct 20, 2026
971f999
fix(lpse2d): HPE gamma limits bound the applied rate after energy con…
physicistphil Oct 20, 2026
a694ba0
Merge branch lpse2d/lpse-parity (HPE limits after energy conservation…
physicistphil Oct 20, 2026
1d2bac5
fix(lpse2d): the FD IAW restriction to the EPW grid keeps only the IA…
physicistphil Oct 20, 2026
9eb7ff5
fix(lpse2d): saved EPW ex/ey without the stray 4/(nx ny) factor
physicistphil Oct 20, 2026
984f0f6
docs(lpse2d): scope the saved-field Fourier identities to full-grid s…
physicistphil Oct 20, 2026
86606c8
test(lpse2d): run the slow HPE trapping and smoke regressions again
physicistphil Oct 20, 2026
c25d7eb
Merge branch lpse2d/lpse-parity (slow HPE tests) into lpse2d/lpse-par…
physicistphil Oct 20, 2026
4d193ad
test(lpse2d): HPE linear closure is measured against the configured L…
physicistphil Oct 20, 2026
85f29cc
fix(lpse2d): FD-combined inverse-Landau growth is exponential; FD IAW…
physicistphil Oct 20, 2026
df43c9b
test(lpse2d): HPE smoke test with enough particles for its kpeak check
physicistphil Oct 21, 2026
8dee419
Merge branch lpse2d/lpse-parity (HPE smoke test particles) into lpse2…
physicistphil Oct 21, 2026
5686b91
docs(lpse2d): list the intentional departures from LPSE
physicistphil Oct 21, 2026
360d65f
docs(lpse2d): single trailing newline in the overview (end-of-file-fi…
physicistphil Oct 21, 2026
bca2538
docs(lpse2d): complete the LPSE departures list from an audit; fix wr…
physicistphil Oct 21, 2026
e2bc460
Merge PR #388 (fix/lpse2d-interpolated-field-save, 984f0f6) into lpse…
physicistphil Oct 22, 2026
71fbc43
Merge branch lpse2d/lpse-parity (PR #388 merge) into lpse2d/lpse-pari…
physicistphil Oct 22, 2026
a85fbd5
test(lpse2d): full-grid field saves where tpd.yaml's save spacing is …
physicistphil Oct 22, 2026
4f4c124
Merge branch lpse2d/lpse-parity (full-grid saves in tests) into lpse2…
physicistphil Oct 22, 2026
02df382
test(lpse2d): PR #388's field-save tests and the light-spectrum probe…
physicistphil Oct 22, 2026
5119513
fix(lpse2d): refresh the prescribed pump before the IAW and EPW read it
physicistphil Oct 23, 2026
30493b4
docs(lpse2d): the prescribed-pump timing in Departures from LPSE
physicistphil Oct 23, 2026
75d3399
fix(lpse2d): HPE flux parity compares the same time window on both sides
physicistphil Oct 23, 2026
7099c40
fix(lpse2d): parity verify fails on a non-finite stored or recomputed…
physicistphil Oct 23, 2026
a62b52c
fix(lpse2d): refuse Poynting maps on a single-point x save grid at setup
physicistphil Oct 23, 2026
04b432a
Merge branch lpse2d/lpse-parity (Poynting save-grid check) into lpse2…
physicistphil Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
425 changes: 425 additions & 0 deletions adept/_lpse2d/core/combined.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Include the evolved ion density in combined-mode absorption

CombinedSolver.__init__ freezes self.collisional and self.absorb0 from the background density. In __call__, the interpolated iaw_density changes only the phase in detuning; every substep still multiplies by those background-only absorption factors. Thus a combined spectral run with absorption and enabled IAW feedback has the same collisional attenuation in compressed and depleted regions, despite the configured density-squared absorption law. This differs from the neighboring RamanLight.__call__ and CoupledLight.__call__, which evaluate absorption from the effective density at each substep. Recompute both factors from the same interpolated density, respecting the separate pump/Raman feedback switches, and retain the precomputed fast path only when the effective density is unchanged. A sources-off decay check with nonzero uniform iaw_density would expose the missing density dependence.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5a56eb0. CombinedSolver.__call__ now computes the combined field's collisional damping and the evolved pump's absorption from the sub-step's effective density, alongside the detuning:

  • combined field: n/n_env + dn f_R;
  • pump: n/nc (1 + dn f_0 n_env/n).

Each wave uses its own iaw_feedback_factor, so the terms.iaw.perturbs switches apply, as in RamanLight / CoupledLight. Without an IAW density the precomputed factors are used as before.

Test: test_combined_absorption_follows_the_evolved_ion_density. A uniform iaw_density = 0.05 (n = 0.15 nc, n_env = 0.2) makes every density-dependent factor a uniform scalar. One EPW step with it, divided by the same step without it, is therefore exactly exp(-nu dt (n_eff^2 - n^2)) in modulus, for a transverse E1 plane wave and for a pump plane wave (rtol 1e-10, fixed before the run). Before the fix both ratios were 1, which is 1.2 % off.

Disclosure: my first version compared one step's absolute interior decay with the absorption law and missed by 3e-4. The absorbing x layers act in x-space, and the spectral propagator carries their deficit into the interior within the step. The with/without ratio cancels them exactly, at the same tolerance.

Large diffs are not rendered by default.

542 changes: 453 additions & 89 deletions adept/_lpse2d/core/epw.py

Large diffs are not rendered by default.

197 changes: 197 additions & 0 deletions adept/_lpse2d/core/fd_combined.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Restrict the small-step approximation by magnitude for signed Landau rates

In FDCombinedSolver.langmuir_step, jnp.where(rate_dt < 1.0e-3, rate_dt, 1.0 - jnp.exp(-rate_dt)) selects the linear approximation for every negative rate, however large its magnitude. This is reachable through the new QLE Landau feedback and HPE allow_growth: __call__ passes their signed y["gamma_L"] straight into this method. Inverse-Landau modes therefore receive the first-order growth factor instead of the exponential outside the small-step regime, systematically understating their amplification. The neighboring CombinedSolver.propagate_combined uses exponential propagation, and both epw_dissipation and HPE expected_wave_loss use exponential energy accounting, so FD-combined growth can also disagree with its reported exchange. Test the approximation threshold against abs(rate_dt), or use -jnp.expm1(-rate_dt) directly, and cover a retained longitudinal mode with a negative rate outside the small-step regime.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 85f29cc. FDCombinedSolver.langmuir_step now uses LPSE's linearised coefficient only for |γ dt| < 1e-3, and -expm1(-γ dt) otherwise. Small damping keeps LPSE's coefficient, so test_022 and similar decks are unchanged; a negative rate of any size outside that regime grows exponentially. LPSE tests the signed rate, so this departs from it for large negative rates.

Regression: test_fd_combined.py::test_langmuir_step_grows_negative_rates_exponentially, on a retained longitudinal mode at γ dt = −0.1, −5e-4, 5e-4 and 0.1, compared with exp or linear at rtol 1e-12. Before the fix the −0.1 case gave 1.1 instead of e^0.1 (0.47 % low). Merged into #387 (827531b).

Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
"""LPSE's combined TPD + SRS solver with the finite-difference light propagator (inventory A15).

With ``lw.solver = combined`` and ``{laser|raman}.solver = fd`` LPSE advances the combined field --
the Raman class, carrier ``wpe``, whose transverse part is the Raman light and whose longitudinal
part is the EPW -- with its FD Schroedinger step, the grad-div coefficient carrying the Bohm-Gross
term (``SchrodingerSolver3::step_2d``, ``gradDivCoeff = dt 3 Ve^2 / (2 wpe) - Cdt``):

dE1/dt = i c^2/(2 wp0) [lap E1 - grad div E1] + i 3 vte^2/(2 wp0) grad div E1
+ i wp0/2 (1 - n/n_env) E1 + unified source,

the pump with the same FD step, and the sources from FD derivatives (``LightSolver::calculateSources``:
``divergence`` / ``gradE`` with the solver's stencil). Landau damping and the EPW noise act on the
longitudinal part in k-space once per Langmuir-wave step (``LwSolver::advanceLW_combinedTPDandSRS_FD``);
the spectral combined solver (``combined.py``) does both per light sub-step, as LPSE's spectral path.

This class is ``CoupledLight`` (the FD pump, its injectors and absorbers, the staggered sub-steps)
with the combined field's operator and the unified couplings of ``CombinedSolver`` in FD form.
"""

import numpy as np
from jax import Array
from jax import numpy as jnp

from adept._lpse2d.core.combined import CombinedSolver
from adept._lpse2d.core.light import CoupledLight
from adept._lpse2d.core.vector import fft2c, ifft2c, k_dot


class FDCombinedSolver(CoupledLight):
"""The combined field E1 and the evolved pump E0 on the FD light propagator."""

def __init__(self, cfg: dict):
if not cfg["terms"].get("light", {}).get("pump_depletion", False):
raise ValueError(
"terms.epw.solver: combined with terms.light.solver: fd needs terms.light.pump_depletion "
"(LPSE's FD combined decks evolve the pump; the static pump runs on the spectral combined solver)"
)
super().__init__(cfg)
# the k-space pieces of the combined formulation: band, potential, Landau rate, noise
self.spectral = CombinedSolver(cfg)
# the separate SRS / TPD couplings of CoupledLight are replaced by the unified ones
self.srs_enabled = False
self.tpd_enabled = False
self.sources_on = self.spectral.sources_on
derived = cfg["units"]["derived"]
vte_sq = derived["vte_sq"]
# the combined field: carrier wp0, Bohm-Gross longitudinal dispersion
self.diffraction_coeff = 1j * self.c**2 / (2.0 * self.wp0)
self.grad_div_coeff = 1j * 3.0 * vte_sq / (2.0 * self.wp0)
self.linear_coeff = 1j * self.wp0 / 2.0 * (1.0 - self.n_over_env)
self.n_over_nc1 = self.n_over_env # the Raman class's critical density is n_env here
self.w_carrier1 = self.wp0
# LPSE's double-exponential layer on the combined field (setupAbsorbingBoundaries_doubleExponential)
grid = cfg["grid"]
self.sub_boundary = grid.get("combined_absorbing_boundaries", grid["absorbing_boundaries"]) ** (
1.0 / self.n_sub
)
self.delta_w = self.w0 - 2.0 * self.wp0
self.source_coeff = self.spectral.source_coeff # -i e / (4 me w0)
self.depletion_coeff = self.spectral.depletion_coeff if hasattr(self.spectral, "depletion_coeff") else None
if self.depletion_coeff is None:
self.depletion_coeff = 1j * self.e / (2.0 * self.me * self.w0)
self.rho_factor = 1.0 - self.w0 / self.wp0
# the unified source's EPW-side restrictions, as CombinedSolver.unified_source applies them: the
# x-space EPW source mask (terms.epw.source_window and the EPW's absorber / injector-row
# suppression) and the k-filter on the pump entering it (terms.epw.source.srs_k_filter, LPSE
# lw.kFilter in LwSolver::make_E0E1, on the FD path as on the spectral one)
self.epw_source_mask = self.spectral.source_mask
e0_filter = np.asarray(self.spectral.E0_filter)
self.E0_filter = None if bool(np.all(e0_filter == 1.0)) else jnp.asarray(e0_filter)
# Landau damping / noise once per EPW step (the LW step)
self.noise_enabled = self.spectral.noise_enabled
if self.noise_enabled:
from adept._lpse2d.core.epw import noise_kick_spectrum

self.noise_kick_lw = jnp.asarray(noise_kick_spectrum(cfg, dt=self.dt, nu_coll=self.spectral.nu_raman))
print("combined solver on the FD light propagator (LPSE lw.solver = combined, raman.solver = fd)")

def seed_carrier(self) -> float:
"""The seed (``drivers.E1``) enters the combined field at its carrier ``wp0``: LPSE's Raman
class injects at ``W1 = wpe`` in combined mode (``LightSolver.cpp:2404-2406``), as the
spectral ``CombinedSolver`` does."""
return self.wp0

# ------------------------------------------------------------ FD pieces --

def _div(self, E: Array) -> Array:
return self._dx(E[..., 0]) + self._dy(E[..., 1])

def _grad_div(self, E: Array) -> list[Array]:
ex, ey = E[..., 0], E[..., 1]
out = [self._d2x(ex) + self._dxdy(ey), self._dxdy(ex) + self._d2y(ey)]
if E.shape[-1] == 3:
out.append(jnp.zeros_like(ex))
return out

def unified_source_fd(self, t: float, E0: Array, E1: Array) -> Array:
"""``-i e/(4 me w0) e^{-i dw t} [grad(E0 . E1*) + (1 - w0/wp0) E0 (div E1)*]`` with the FD
stencils (LPSE ``gradE`` / ``divergence``), the pump k-filtered and the result multiplied by
the EPW source mask as in ``CombinedSolver.unified_source``."""
if self.E0_filter is not None:
E0 = ifft2c(fft2c(E0) * self.E0_filter[..., None])
scalar = jnp.sum(E0 * jnp.conj(E1), axis=-1)
grad = [self._dx(scalar), self._dy(scalar)]
if E1.shape[-1] == 3:
grad.append(jnp.zeros_like(scalar))
div1 = self._div(E1)
term = jnp.stack(grad, axis=-1) + self.rho_factor * E0 * jnp.conj(div1)[..., None]
if self.epw_source_mask is not None:
term = term * self.epw_source_mask[..., None]
return self.source_coeff * jnp.exp(-1j * self.delta_w * t) * term

def unified_depletion_fd(self, t: float, E1: Array) -> Array:
"""``i e/(2 me w0) e^{+i dw t} E1 div E1`` with the FD divergence."""
return self.depletion_coeff * jnp.exp(1j * self.delta_w * t) * E1 * self._div(E1)[..., None]

def coupled_rhs(
self,
t: float,
E0: Array,
E1: Array,
laplacian_phi: Array,
pump_args: dict,
seed_args: dict | None,
iaw_density: Array | None = None,
phi_k: Array | None = None,
couple: bool = True,
patterns: list[Array] | None = None,
) -> tuple[Array, Array]:
# pump: CoupledLight's propagation, detuning and injector rows (its SRS / TPD terms are off)
k_e0 = self.pump_rhs(t, E0, E1, laplacian_phi, pump_args, iaw_density, None, couple=False, patterns=patterns)
linear_coeff = self.linear_coeff
if iaw_density is not None:
linear_coeff = linear_coeff - 1j * self.wp0 / 2.0 * iaw_density * self.iaw_feedback
comps = [E1[..., i] for i in range(E1.shape[-1])]
k_e1 = [
self.diffraction_coeff * cc + self.grad_div_coeff * gd + linear_coeff * e
for cc, gd, e in zip(self.curl_curl(E1), self._grad_div(E1), comps, strict=True)
]
if seed_args is not None:
# the FD injector rows of the separate Raman solver, at the combined carrier wp0
k_e1 = self.add_seed_rows(k_e1, self.calc_seed_source(t, seed_args))
k_e1 = jnp.stack(k_e1, axis=-1)
if self.sources_on:
depletion = self.unified_depletion_fd(t, E1)
source = self.unified_source_fd(t, E0, E1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Apply the EPW source window on the FD combined path

With terms.epw.solver: combined and terms.light.solver: fd, a configured terms.epw.source_window never reaches the coupling update. CombinedSolver(cfg) builds it as self.spectral.source_mask, but unified_source_fd does not apply it, and this RHS only applies source_mask1. That light mask covers the partner injector/absorber, not the EPW range restriction (helpers.source_mask only reads source_window for epw/iaw). Consequently, even a window entirely outside the box still allows TPD/SRS drive wherever the pump and seeded/noise-generated E1 overlap; switching from the spectral combined solver changes the interaction region, not just the discretization. Apply the configured EPW mask to the assembled FD unified source, as CombinedSolver.unified_source does, and cover an all-zero and a partial window on the FD combined path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in adec784. unified_source_fd now multiplies the assembled term by the spectral solver's EPW source mask (grid.epw_source_mask: terms.epw.source_window with the EPW's absorber / injector-row suppression), as CombinedSolver.unified_source does.

Same class, same function: it also ignored terms.epw.source.srs_k_filter, which CombinedSolver.unified_source applies to the pump (E0_filter); the FD path now filters E0 the same way. LPSE applies lw.kFilter on its FD combined path too (LwSolver::make_E0E1).

Measured on the pre-fix code: a window entirely outside the box left the FD unified source at full amplitude (max ratio 1.0), and srs_k_filter: true changed it by 0.0. After the fix: 0.0 and 0.87 of max.

Test test_fd_combined.py::test_fd_unified_source_takes_the_epw_window_and_the_pump_k_filter (1e-12, fixed in advance):

  • partial window: the source equals the mask × the unwindowed source;
  • outside window: coupled_rhs's k_e1 is identical to its value with the sources off;
  • filter: the source equals the unfiltered solver's source of the filtered pump.

The only FD combined gate deck, test_022, has no window, no seed and the filter off. For it epw_source_mask × light_source_mask1 = light_source_mask1 exactly (both binary, the same 4 injector rows; checked on its config), so its source is unchanged.

Two LPSE observations, recorded in the source_window / srs_k_filter docs rows and not changed here:

  1. LPSE's combined path (LightSolver::calculateSources combined branch, make_E0E1) applies no restrictRange. On a combined deck source_window is therefore an adept extension on both light solvers.
  2. make_E0E1 filters only the E0·E1* product (E1 too, at its own cutoff) and leaves the (1 − ω₀/ωp₀) E0 (∇·E1)* term unfiltered. Adept filters E0 in both terms and leaves E1 unfiltered.

Also d24121d (docs only): the terms.epw.solver row still said tpd_projection projects the combined depletion. It has been unprojected since dcbfa93, as the tpd_projection row already says. Merged into #387 (5f7b67b, b28e711).

if not isinstance(self.source_mask0, float):
depletion = depletion * self.source_mask0[..., None]
if not isinstance(self.source_mask1, float):
source = source * self.source_mask1[..., None]
k_e0 = k_e0 + depletion
k_e1 = k_e1 + source
return k_e0, k_e1

# ------------------------------------------------------- the LW step --

def langmuir_step(self, t: float, E1: Array, gamma_landau) -> Array:
"""Landau damping and noise on the longitudinal part, once per EPW step (LwSolver::
advanceLW_combinedTPDandSRS_FD): ``E_L -= E_L (1 - exp(-gamma dt))`` (``gamma dt`` below 1e-3)
and ``E += -i k phi_noise`` on the retained band."""
import jax

band = self.spectral.band
e_k = fft2c(E1)
rate_dt = gamma_landau * self.dt
# LPSE's linearised coefficient only for |gamma dt| < 1e-3: a negative (inverse-Landau) rate of
# any size takes the exponential, -expm1(-gamma dt) (LPSE tests the signed rate, PR #378 review)
coeff = jnp.where(jnp.abs(rate_dt) < 1.0e-3, rate_dt, -jnp.expm1(-rate_dt)) * band
kx, ky = self.spectral.kx[:, None], self.spectral.ky[None, :]
k_dot_e = k_dot(e_k, self.spectral.kx, self.spectral.ky)
e_long = [kx * k_dot_e * self.spectral.one_over_k_sq, ky * k_dot_e * self.spectral.one_over_k_sq]
comps = [e_k[..., 0] - coeff * e_long[0], e_k[..., 1] - coeff * e_long[1]]
if self.noise_enabled:
step = jnp.round(t / self.dt).astype(int)
key = jax.random.fold_in(self.spectral.noise_key, step)
phases = 2.0 * np.pi * jax.random.uniform(key, (self.nx, self.ny))
phi_kick = self.noise_kick_lw * jnp.exp(1j * phases) * band
comps = [comps[0] - 1j * kx * phi_kick, comps[1] - 1j * ky * phi_kick]
if e_k.shape[-1] == 3:
comps.append(e_k[..., 2])
return ifft2c(jnp.stack(comps, axis=-1))

def __call__(self, t: float, y: dict, driver_args: dict, E0_fn=None) -> tuple[Array, Array, Array]:
"""One EPW step: the LW step on the longitudinal part, then the light sub-steps. Returns
``(E0, E1, phi_k)`` like ``CombinedSolver``."""
if self.spectral.evolved_landau:
gamma_landau = y["gamma_L"]
elif self.spectral.landau_enabled:
gamma_landau = self.spectral.landau_rate
else:
gamma_landau = jnp.zeros_like(self.spectral.k_sq)
E1 = self.langmuir_step(t, y["E1"], gamma_landau)
zero_phi = jnp.zeros((self.nx, self.ny), dtype=jnp.complex128)
E0, E1 = CoupledLight.__call__(
self, t, y["E0"], E1, zero_phi, driver_args["E0"], driver_args.get("E1"), y.get("iaw_density")
)
return E0, E1, self.spectral.potential(E1)
Loading
Loading