Repository navigation
New model VlasovPoissonElectrostaticAdiabatic (full-orbit ions, adiabatic electrons) and fixes of the full-f Poisson background - #800
Merged
Conversation
…diabatic electrons The full-orbit (6D) counterpart of DriftKineticElectrostaticAdiabatic: Vlasov ions in phi and a static B0, electrons through the linearized Boltzmann response, optionally with the flux-surface average phi - <phi> (zonal correction, which_geometry) as in the drift-kinetic model, and optionally with the vacuum term -(eps/alpha^2) Delta phi (quasi_neutral=False). - AdiabaticPoissonSolve (subclass of ImplicitDiffusion) for the field equation. - ImplicitDiffusion: stab_mat "M0ad_withT" (weight n0/T0) and diffusion_mat=None (no diffusion term, a weighted L2 projection), with option checks. - Propagator order push_eta, poisson, push_v_E, push_vxb: with Strang splitting both E-kicks use the potential of the current marker positions (leapfrog). - Tests: manufactured solutions for the Poisson solve (incl. zonal average), ion-acoustic Landau damping along B0 (verification, quasi-neutral and with vacuum term). - Example slab_drift_wave: drift wave in a density gradient with the exact equilibrium (Maxwellian shifted by the ion diamagnetic velocity); the measured frequency 0.262 matches the full-orbit dispersion relation (drift-kinetic: 0.287). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For a full-f particle source (no control variate), verify_rhs adds the neutralizing background with the coefficient of the accumulator. The H1 kernels (charge_density_0form, gc_density_0form) deposit the particle density (the weights) and the charge number and sign are in that coefficient (alpha^2/epsilon with epsilon ~ 1/Z), so the background must be -n0. With -Z n0 the right-hand side was n - Z n0: wrong for Z != 1 and for electrons (Z = -1 gave n + n0). All existing examples and tests use Z = 1. New test: a uniform full-f plasma with Z = 2 in VlasovPoissonElectrostaticAdiabatic must have phi at noise level (the old background gave phi = -2 everywhere). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VlasovAmpereOneSpecies, VlasovMaxwellOneSpecies and ColdPlasmaVlasov called update_weights() before the initial Poisson solve and restored the full-f weights afterwards. Without the control variate the solve then deposited the delta-f weights (n - n0) and ImplicitDiffusion added its full-f neutralizing background (-n0) on top: the right-hand side was n - 2 n0. In the periodic examples the compatibility projection of PoissonSolve removed the constant offset, but with Dirichlet boundaries or a non-uniform n0 the initial phi was wrong. The solve handles both cases itself (update_weights with the control variate, full-f weights plus the background without it), so the weight juggling is removed. The Gauss-law diagnostic of VlasovMaxwellOneSpecies now deposits the same right-hand side (including the background source for full-f). New test: the initial potential of a neutral full-f plasma with phi = 0 at the walls is at noise level (before: phi = -x(1-x)/2, rms 0.09). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Options: stab_mat allows "M0ad_withT" and defaults to it (documented as "Id"), it is the mass matrix of the adiabatic term (not multiplied by a "stab_eps"); epsilon, Z, rho and rho_coeffs are constructor arguments, not options; "defaults=" typo. - Class docstring: the solved equation includes the flux-surface average phi - <phi> and the factor 1/epsilon on the right-hand side; the matrix is M0ad_withT (I - A); the parameters are those of the constructor (no phi, stab_eps, stab_mat or solver arguments); "adabatic" typo. Docstrings only, no change in behavior. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rokineticPoissonSolve, phi of the old Z n0 background Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The same physical setup as the slab_drift_wave example of VlasovPoissonElectrostaticAdiabatic (box, epsilon = 0.1, exponential density gradient kappa_n = 1, T_i = T_e = 1, initial mode k = (pi/2, 3, 0.05)), with drift-kinetic ions: guiding-center Maxwellian n0(X) M (an exact equilibrium), explicit guiding-center pushers, dt = 0.25 (no cyclotron motion to resolve). In the slab the flux surfaces are x = const, so the electrons do not respond to the average over y and z (which_geometry="toroidal"). With the default "cylindrical" (average over z only), all modes with k_z = 0 lose the electron response; their ion convective cells (omega ~ k_y kappa_n / (eps k_perp^2) ~ 3) made the explicit push unstable at this dt. The measured frequency of the mode is 0.291 (drift-kinetic dispersion relation 0.287; the full-orbit example gives 0.262 for 0.262). The pproc overlays the mode of a full-orbit run with --compare <its output folder>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stead of pointing to the uncommitted design note Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With which_geometry=None there is no average (<phi> = 0, stab_average=None): the electrons respond adiabatically to all modes, as in the full-orbit model VlasovPoissonElectrostaticAdiabatic without its zonal correction. The default stays "cylindrical". New test: manufactured solution independent of eta3, with and without the average over eta3 (with the average the adiabatic term vanishes for it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e average The drift-kinetic slab example now uses which_geometry=None like the full-orbit example (Boltzmann response for all modes), instead of the average over y and z. In the shearless slab the physical choice would be the average along the field lines (over z, "cylindrical"), which removes the electron response of the k_z = 0 modes; it is not possible in the quasi-neutral full-orbit model and not needed for the studied mode (k_z != 0). Measured omega 0.291 (drift-kinetic theory 0.287). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ackground for delta-f markers Correction of 7c2a3fc, which removed the delta-f weights from the initial Poisson solve of full-f models (to avoid subtracting n0 twice). The delta-f deposit has much less Monte-Carlo noise than the full-f weights minus the background n0 (that of f - f0 instead of f): for a 1% density perturbation the relative error of the initial phi is 1% instead of 400% (100 markers per cell). - ImplicitDiffusion / PoissonSolve: new constructor argument delta_f_source. The particle sources are then deposited with the delta-f weights also for full-f markers (update_weights before, full-f weights restored after) and no background source is added. - VlasovAmpereOneSpecies, VlasovMaxwellOneSpecies, ColdPlasmaVlasov: initial_poisson with delta_f_source=True; the Gauss-law diagnostic deposits the delta-f weights again. - DeltaFParticles6D (LinearVlasovAmpere/MaxwellOneSpecies): the weights are delta-f already, so no background -n0 is added any more (it was, since these markers force control_variate=False; hidden by the compatibility projection in periodic runs). New tests: initial phi of a perturbed full-f plasma against the exact solution (fails with the full-f deposit: error 4.1), and the initial phi of unperturbed delta-f markers with phi = 0 at the walls (fails with the background: rms 0.09). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kers The initial Poisson solve deposits the delta-f weights (delta_f_source), while the time stepping of full-f markers conserves the charge of the full-f weights. The diagnostic deposited the delta-f weights at every save, so for t > 0 its residual contained the changing Monte-Carlo noise of the f0 deposit (about 3 times the field term after 20 steps in the test case). Now post_allocate caches the offset alpha^2/eps (rho_full(0) - rho_delta_f(0)) for full-f markers, and the diagnostic deposits the full-f weights minus this offset: it vanishes at t = 0 and then measures the Gauss-law violation of the time stepping (about 0.1 of the field term after 20 steps, similar to the control-variate case). With the control variate nothing changes. Test: test_initial_gauss_error parametrized over control variate and end time; the full-f case after 20 steps fails with the old diagnostic (0.29 vs field term 0.11). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
spossann
added a commit
that referenced
this pull request
Oct 21, 2026
…804) Renames the model `VlasovPoissonElectrostaticAdiabatic` (added in #800) to `VlasovPoissonAdiabatic`: the Poisson equation already says that the model is electrostatic. - model module `vlasov_poisson_adiabatic.py`, class, example folder `examples/VlasovPoissonAdiabatic/` and verification test `test_verif_VlasovPoissonAdiabatic.py` - references in `models-all.rst`, `struphy.api.models`, the generated `models/__init__.py` and the drift-kinetic slab example - no deprecated alias (the old name was never released) Tests: the verification test, the default-params model test on 4 ranks, `doc/check_models_doc.py` and both slab drift-wave examples (and pprocs) with `--test-run`. The new model `VlasovPoissonAdiabaticPauli` is stacked on this branch: #805. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
spossann
added a commit
that referenced
this pull request
Oct 21, 2026
…atic electrons) and fixes of the full-f Poisson background (#800) ## Summary ### New model `VlasovPoissonElectrostaticAdiabatic` The full-orbit (6D) counterpart of `DriftKineticElectrostaticAdiabatic`: Vlasov ions in the electrostatic potential and a static B0, electrons through the linearized Boltzmann response. df/dt + v . grad f + (1/eps) (-grad phi + v x B0) . df/dv = 0 -(eps/alpha^2) Delta phi + n0/(Z eps T0) (phi - <phi>) = int f d^3v - n0 - New propagator `AdiabaticPoissonSolve` (subclass of `ImplicitDiffusion`): - `quasi_neutral=True` (default) drops the vacuum term, so the solve is a weighted mass-matrix solve; - `which_geometry` adds the optional flux-surface average `phi - <phi>`, as in the drift-kinetic model; - `stab_mat="M0ad_withT"` (weight n0/T0). - `ImplicitDiffusion` now allows `stab_mat="M0ad_withT"` and `diffusion_mat=None` (no diffusion term), with option checks. - Propagator order `push_eta, poisson, push_v_E, push_vxb`: with Strang splitting both E-kicks use the potential of the current marker positions (drift-kick-kick-drift). This is the same reasoning as #799 for the drift-kinetic model. - Tests: - manufactured solutions for the Poisson solve, including the zonal average; - option checks; - verification: ion-acoustic Landau damping along B0, quasi-neutral (gamma -0.168 vs -0.153) and with the vacuum term (-0.179 vs -0.194). - Example `slab_drift_wave`: a drift wave in a density gradient. - **Equilibrium:** the initial state must be the exact Vlasov equilibrium f0(x + eps v_y), here a Maxwellian shifted by the ion diamagnetic velocity. - **Time step:** in the quasi-neutral limit the explicit splitting needs `dt c_s pi/h <~ 2`, since the ion-acoustic branch has no Debye cutoff. - **Result:** measured omega = 0.262, against 0.262 from the full-orbit dispersion relation and 0.287 from the drift-kinetic one (the O(k_perp^2 rho_i^2) gap). ### Example `slab_drift_wave` for `DriftKineticElectrostaticAdiabatic` The same physical setup as the full-orbit example (box, eps = 0.1, kappa_n = 1, T_i = T_e = 1, mode k = (pi/2, 3, 0.05)), for a direct comparison: - drift-kinetic ions with the guiding-center Maxwellian n0(X) M (an exact equilibrium); - explicit guiding-center pushers, dt = 0.25; - `LoadingParameters` and `GyroMaxwellian2D` with `B0 = 1` (both default to 2); - Same electron model as the full-orbit example: Boltzmann response for all modes, i.e. no flux-surface average. For this, `GyrokineticPoissonSolve` gets the new option `which_geometry=None`; the default stays `"cylindrical"`. - In the shearless slab the physical choice would be the average along the field lines (over z, `"cylindrical"`), which removes the electron response of the k_z = 0 modes. - That is not possible in the quasi-neutral full-orbit model, where the field equation has no polarization term to determine <phi>, and it is not needed for the studied mode, which has k_z != 0. - With `"cylindrical"`, the ion convective cells of the k_z = 0 modes (omega ~ 3) also made the explicit push unstable at dt = 0.25. - **Result:** measured omega = 0.291 against 0.287 from the drift-kinetic dispersion relation; the full-orbit example gives 0.262 for a predicted 0.262. `pproc_slab_drift_wave.py --compare <full-orbit run>` overlays both modes. ### Fixes in the full-f Poisson right-hand side (separate commits) 1. **`ImplicitDiffusion`: the full-f neutralizing background is `-n0`, not `-Z n0`.** - The H1 kernels deposit the particle density, and the charge (number and sign) is in the coefficient (`alpha^2/eps`, with eps ~ 1/Z). - With `-Z n0`, the right-hand side was `n - Z n0`, which is wrong for Z != 1. Electrons (e.g. `ColdPlasmaVlasov`) got `n + n0`. - All existing examples and tests use Z = 1. 2. **Initial Poisson solve: delta-f deposit without double subtraction.** - `VlasovAmpereOneSpecies`, `VlasovMaxwellOneSpecies` and `ColdPlasmaVlasov` deposited the delta-f weights for the initial solve, and `ImplicitDiffusion` added the full-f background on top: `n - 2 n0`. In periodic examples the compatibility projection hid the offset; with Dirichlet BCs or a non-uniform n0 the initial phi was wrong. - New constructor argument `ImplicitDiffusion(..., delta_f_source=True)` (also `PoissonSolve`): particle sources are deposited with delta-f weights also for full-f markers (weights restored afterwards), and no background source is added. The three models use it for `initial_poisson`. - The delta-f deposit has far less noise than full-f weights minus n0: for a 1 % perturbation the initial phi has a relative error of 1 % instead of 400 % (100 markers per cell). - `DeltaFParticles6D` (`LinearVlasovAmpere/MaxwellOneSpecies`) no longer gets the `-n0` background: their weights are delta-f already. - `VlasovMaxwellOneSpecies` `gauss_error` for full-f markers: the diagnostic deposits the full-f weights (whose charge the time stepping conserves) minus a cached t = 0 offset `alpha^2/eps (rho_full(0) - rho_delta_f(0))`. After 20 steps the residual is about 0.1 of the field term instead of about 3 times it (f0 deposit noise). 3. **`GyrokineticPoissonSolve` docstrings corrected:** - `stab_mat` default `"M0ad_withT"`, not `"Id"`; - the solved equation includes `phi - <phi>`, `1/eps` and the `rho_coeffs`; - the parameters are those of the constructor. Each of fixes 1 and 2 comes with tests that fail on the old code: phi_rms 2.0 (Z = 2 background), 0.09 (double subtraction, background for delta-f markers), relative error 4.1 (full-f deposit). ## Tests (local) - `test_adiabatic_poisson.py` (serial, 2 ranks); `test_poisson.py`, `test_gyrokinetic_poisson.py` (2 ranks, 140 passed) - verification tests of `VlasovPoissonElectrostaticAdiabatic`, `VlasovAmpereOneSpecies`, `VlasovMaxwellOneSpecies` (2 ranks) - default-params model tests of the new model, `DriftKineticElectrostaticAdiabatic`, `VlasovAmpereOneSpecies`, `VlasovMaxwellOneSpecies`, `ColdPlasmaVlasov` (4 ranks) - example `--test-run` + pproc, ruff, `doc/check_models_doc.py`, `doc/check_propagator_doc.py` ## Follow-ups (not in this PR) - In the slab drift-wave example the kinetic energy of f - f0 drifts by 1-2 % of the thermal energy over t = 15 (unexplained). Related: #799 (propagator order of `DriftKineticElectrostaticAdiabatic`, merged). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
spossann
added a commit
that referenced
this pull request
Oct 21, 2026
…804) Renames the model `VlasovPoissonElectrostaticAdiabatic` (added in #800) to `VlasovPoissonAdiabatic`: the Poisson equation already says that the model is electrostatic. - model module `vlasov_poisson_adiabatic.py`, class, example folder `examples/VlasovPoissonAdiabatic/` and verification test `test_verif_VlasovPoissonAdiabatic.py` - references in `models-all.rst`, `struphy.api.models`, the generated `models/__init__.py` and the drift-kinetic slab example - no deprecated alias (the old name was never released) Tests: the verification test, the default-params model test on 4 ranks, `doc/check_models_doc.py` and both slab drift-wave examples (and pprocs) with `--test-run`. The new model `VlasovPoissonAdiabaticPauli` is stacked on this branch: #805. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.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
New model
VlasovPoissonElectrostaticAdiabaticThe full-orbit (6D) counterpart of
DriftKineticElectrostaticAdiabatic: Vlasov ions in the electrostatic potential and a static B0, electrons through the linearized Boltzmann response.AdiabaticPoissonSolve(subclass ofImplicitDiffusion):quasi_neutral=True(default) drops the vacuum term, so the solve is a weighted mass-matrix solve;which_geometryadds the optional flux-surface averagephi - <phi>, as in the drift-kinetic model;stab_mat="M0ad_withT"(weight n0/T0).ImplicitDiffusionnow allowsstab_mat="M0ad_withT"anddiffusion_mat=None(no diffusion term), with option checks.push_eta, poisson, push_v_E, push_vxb: with Strang splitting both E-kicks use the potential of the current marker positions (drift-kick-kick-drift). This is the same reasoning as DriftKineticElectrostaticAdiabatic: Poisson solve between the two guiding-center pushers #799 for the drift-kinetic model.slab_drift_wave: a drift wave in a density gradient.dt c_s pi/h <~ 2, since the ion-acoustic branch has no Debye cutoff.Example
slab_drift_waveforDriftKineticElectrostaticAdiabaticThe same physical setup as the full-orbit example (box, eps = 0.1, kappa_n = 1, T_i = T_e = 1, mode k = (pi/2, 3, 0.05)), for a direct comparison:
LoadingParametersandGyroMaxwellian2DwithB0 = 1(both default to 2);GyrokineticPoissonSolvegets the new optionwhich_geometry=None; the default stays"cylindrical"."cylindrical"), which removes the electron response of the k_z = 0 modes."cylindrical", the ion convective cells of the k_z = 0 modes (omega ~ 3) also made the explicit push unstable at dt = 0.25.pproc_slab_drift_wave.py --compare <full-orbit run>overlays both modes.Fixes in the full-f Poisson right-hand side (separate commits)
ImplicitDiffusion: the full-f neutralizing background is-n0, not-Z n0.alpha^2/eps, with eps ~ 1/Z).-Z n0, the right-hand side wasn - Z n0, which is wrong for Z != 1. Electrons (e.g.ColdPlasmaVlasov) gotn + n0.VlasovAmpereOneSpecies,VlasovMaxwellOneSpeciesandColdPlasmaVlasovdeposited the delta-f weights for the initial solve, andImplicitDiffusionadded the full-f background on top:n - 2 n0. In periodic examples the compatibility projection hid the offset; with Dirichlet BCs or a non-uniform n0 the initial phi was wrong.ImplicitDiffusion(..., delta_f_source=True)(alsoPoissonSolve): particle sources are deposited with delta-f weights also for full-f markers (weights restored afterwards), and no background source is added. The three models use it forinitial_poisson.DeltaFParticles6D(LinearVlasovAmpere/MaxwellOneSpecies) no longer gets the-n0background: their weights are delta-f already.VlasovMaxwellOneSpeciesgauss_errorfor full-f markers: the diagnostic deposits the full-f weights (whose charge the time stepping conserves) minus a cached t = 0 offsetalpha^2/eps (rho_full(0) - rho_delta_f(0)). After 20 steps the residual is about 0.1 of the field term instead of about 3 times it (f0 deposit noise).GyrokineticPoissonSolvedocstrings corrected:stab_matdefault"M0ad_withT", not"Id";phi - <phi>,1/epsand therho_coeffs;Each of fixes 1 and 2 comes with tests that fail on the old code: phi_rms 2.0 (Z = 2 background), 0.09 (double subtraction, background for delta-f markers), relative error 4.1 (full-f deposit).
Tests (local)
test_adiabatic_poisson.py(serial, 2 ranks);test_poisson.py,test_gyrokinetic_poisson.py(2 ranks, 140 passed)VlasovPoissonElectrostaticAdiabatic,VlasovAmpereOneSpecies,VlasovMaxwellOneSpecies(2 ranks)DriftKineticElectrostaticAdiabatic,VlasovAmpereOneSpecies,VlasovMaxwellOneSpecies,ColdPlasmaVlasov(4 ranks)--test-run+ pproc, ruff,doc/check_models_doc.py,doc/check_propagator_doc.pyFollow-ups (not in this PR)
Related: #799 (propagator order of
DriftKineticElectrostaticAdiabatic, merged).🤖 Generated with Claude Code