Repository navigation
Particles5D: draw mu with the local |B_0|, B0="equil" default (fixes #841) - #878
Merged
Merged
Conversation
LoadingParameters.B0 and GyroMaxwellian2D(B0=...) accept a number, a callable of stacked logical positions (3, N) or "equil" (new default for both), the local field strength |B_0| of the species' equilibrium. - draw_markers draws mu = v_perp^2/(2 B0) with B0 at the final marker positions (disc inversion and pole clamp now before velocity sampling; specific markers with a prescribed position keep their drawn v_perp) - Particles5D.loading_B0 and the sampling density s0 share one B0 (GyroMaxwellian2D.eval_B0), so draw and weights are consistent - KineticBackground.attach_equil (forwarded by sums/scalar multiples); Particles5D attaches its equilibrium to background and initial condition - regression baselines of the 5D models updated (default HomogenSlab has |B_0| = 1 instead of the old constant 2.0) - examples: GC tokamak/screw pinch and DKCC lowmode_tae use the default instead of constant B0 workarounds Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Equilibrium attachment mutates shared distributions, causing inconsistent particle weights across populations, and the B0 cache returns stale field values after callable replacement.
2 open issues
What changed in this PR
This PR addresses #841 by drawing Particles5D magnetic moments using the local equilibrium field strength, keeping marker loading consistent with the sampling density.
Changes:
- Adds constant, callable, and default
"equil"field options, with equilibrium attachment and cached evaluation. - Updates marker-loading order, adds validation and statistical tests, and refreshes five model regression baselines.
- Removes example workarounds and documents the new defaults.
| File | Description |
|---|---|
src/struphy/pic/tests/test_show_distribution.py |
Adjusts magnetic-moment bins and statistical tolerance. |
src/struphy/pic/tests/test_loading_B0.py |
Tests local-field loading, weights, validation, and broadcasting. |
src/struphy/pic/particles.py |
Connects distributions and loading to the species equilibrium. |
src/struphy/pic/base.py |
Finalizes positions before velocity sampling and rescales prescribed markers. |
src/struphy/particles/parameters.py |
Defines and validates the expanded B0 options. |
src/struphy/models/tests/regression/baselines/ToyDrift.json |
Updates magnetic-moment regression values. |
src/struphy/models/tests/regression/baselines/LinearMHDDriftkineticCC.json |
Updates energies, coupled fields, and marker baselines. |
src/struphy/models/tests/regression/baselines/GuidingCenter.json |
Updates perpendicular and total energy baselines. |
src/struphy/models/tests/regression/baselines/DriftKineticMaxwell.json |
Updates energy, field, and marker baselines. |
src/struphy/models/tests/regression/baselines/DriftKineticElectrostaticAdiabatic.json |
Updates magnetic-moment and energy baselines. |
src/struphy/kinetic_background/tests/test_maxwellians.py |
Supplies explicit B0 for a standalone distribution test. |
src/struphy/kinetic_background/maxwellians.py |
Adds equilibrium binding, caching, and spatial meshgrid evaluation. |
src/struphy/kinetic_background/base.py |
Adds shared field helpers and forwards equilibrium attachment through compositions. |
src/struphy/agent_guides/skills/setup-simulation/SKILL.md |
Documents local-field defaults and matching loading options. |
examples/ToyDrift/diocotron_instability/params_diocotron.py |
Removes stale B0 comments only. |
examples/LinearMHDDriftkineticCC/lowmode_tae/params_lowmode_tae.py |
Replaces constant-field loading with the equilibrium default. |
examples/GuidingCenter/tokamak_equilibrium/params_tokamak_equilibrium.py |
Removes explicit loading and background field overrides. |
examples/GuidingCenter/screw_pinch_equilibrium/params_screw_pinch_equilibrium.py |
Uses equilibrium defaults for loading and background. |
examples/DriftKineticElectrostaticAdiabatic/itg_cylindre/params_drift_kinetic.py |
Removes stale B0 comments only. |
examples/DriftKineticElectrostaticAdiabatic/cyclone/params_cyclone.py |
Removes stale B0 comments only. |
Review effort: Balanced
…he key Review of #878: - KineticBackground.with_equil(equil, memo) replaces attach_equil: it returns a bound copy (GyroMaxwellian2D with B0="equil", and sums/scalar multiples of such) instead of changing the given object, so a background shared by species with different equilibria stays consistent per species. One memoizing with_equil in the base class (memo keeps the originals alive), subclasses implement _bind_equil; aliases are kept (initial condition = background, shared summands). - Particles binds background and initial condition in a _bind_backgrounds hook right after setting them in __init__ (Particles5D: to B0_equil), so nothing sees unbound objects; the sampling density is bound too. - PICVariable refers to the bound copies of its particles after allocation, so var.backgrounds can be evaluated after the run; the given objects stay unbound. - GyroMaxwellian2D.eval_B0: the cache key includes the B0 option and the bound equilibrium (by identity), so replacing params["B0"] invalidates it. - Tests: shared background with two equilibria, bound sums, cache invalidation, var.backgrounds after allocation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ached Reverts the copy-on-bind design of 15612b6 (with_equil, _bind_equil, _bind_backgrounds hook, PICVariable re-pointing): within a simulation there is only one equilibrium, and attaching keeps the user's background the object the particles evaluate. Kept from the review: - GyroMaxwellian2D.eval_B0 cache key includes the B0 option and the attached equilibrium, so replacing params["B0"] invalidates it; - attach_equil logs a warning when a different equilibrium is attached to the same background (shared by two simulations set up before running), instead of silently switching. Tests: cache invalidation, warning, var.backgrounds attached after allocation. 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.


Fixes #841.
Particles5Ddrew the magnetic moment asmu = -log(1-r) * vth_perp**2 / B0with the constantLoadingParameters.B0(default 2.0). In a tokamak the markers were therefore not Maxwellian inv_perpat their position, which added δf noise. Meanwhile the sampling densitys0already accepted a callableB0, so passing one made the draw and the weights inconsistent.What changes
B0option:LoadingParameters.B0andGyroMaxwellian2D(B0=...)take a positive number (also NumPy/CuPy scalars), a callable of stacked logical positions(3, N), or"equil"."equil"is the new default for both: it is the local|B_0|of the species' equilibrium.draw_markers):muis drawn withB0at the final marker positions. The disc inversion and the pole clamp now run before velocity sampling. A specific marker with a prescribed position but a drawnmukeeps its drawnv_perp.B0for the draw ands0:Particles5D.loading_B0issampling_density.eval_B0, sof_init/s0doesn't depend onmuwhen the moments match.KineticBackground.attach_equil(forwarded by sums and scalar multiples):Particles5Dattaches its equilibrium to the background, the initial condition and the sampling density. Attaching a different equilibrium to a background that already has one (one object shared by two simulations) logs a warning. The class-level default background is deep-copied per species.GyroMaxwellian2D.eval_B0caches the last positions, sof0andvelocity_jacobian_detat the same markers cost one|B_0|evaluation. On a phase-space meshgrid,B0is evaluated on the spatial points only and broadcast. A constantB0is not evaluated at all.lowmode_taeexample dropped their constant-B0workarounds (B0=B_min,B0=1.0,B0=equil.absB0) and use the default. The stale# B0=equil.absB0comments in the cyclone, ITG and diocotron examples are gone.setup-simulationskill.Why the numbers change (regression baselines)
The baselines of GuidingCenter, DriftKineticElectrostaticAdiabatic, DriftKineticMaxwell, LinearMHDDriftkineticCC and ToyDrift were updated for 1 and 2 ranks. The default params use
HomogenSlab, whose|B_0| = 1replaces the old constantB0 = 2.0. Somu(andsum_w_v2) roughly doubles, and the hybrid'smu-dependent current coupling changes accordingly. All other model baselines are unchanged (all 20 model regressions pass on 1 and 2 ranks).Behaviour changes to note
GyroMaxwellian2Dwith the defaultB0="equil"that is evaluated on its own (e.g.background.plot(...)before the simulation, outside aParticles5Dspecies) raises a clearValueError. Pass a number or callable there.GyroMaxwellian2Dbackground now evaluates the analytic|B_0|once per weight update. Measured with 2e5 markers, serial, ad hoc torus, markers moving:update_weightstook 32 ms with a constantB0and 174 ms with"equil"; it was 349 ms before the cache. Runs that already passedB0=equil.absB0paid this before. A possible follow-up is to evaluate the projectedabsB0_hwith the marker kernels instead.muis drawn with the analytic|B_0|, the same asf_init, while the pushers use the projectedabsB0_h. The difference is the usualO(h^p)projection error.Tests
src/struphy/pic/tests/test_loading_B0.py, on 1 and 4 ranks:mu |B_0(x)| / vth**2has mean 1 (Exp(1)) for markers both below and above the median field strength in a hollow torus, for pseudo-random/uniform and Sobol/disc loading. With a constantB0the means are off.|det DF| / s_eta / Npup to 1e-10; a mismatch gives velocity-dependent weights.B0options, scalar broadcast and meshgrid broadcast.--test-run(2 ranks).test_show_distribution: the(v_par, mu)bins now reachmu = 5(mean mu = 1with|B_0| = 1), and the noise-dominated(eta1, eta2)tolerance went from 0.31 to 0.33. That error was already 0.308 withB0 = 2.0.🤖 Generated with Claude Code