Add a 3D Taylor-expanded wakefield model (TaylorWakefield), ported from ocelot#148
Draft
ChristopherMayes wants to merge 3 commits into
Draft
Add a 3D Taylor-expanded wakefield model (TaylorWakefield), ported from ocelot#148ChristopherMayes wants to merge 3 commits into
TaylorWakefield), ported from ocelot#148ChristopherMayes wants to merge 3 commits into
Conversation
What changed, per finding: 1. wakefield_plot crash — beamphysics/plot.py now raises a clear TypeError up front when given a wakefield without particle_kicks, pointing the user to apply_wakefield for 3D wakes. 2. Unvalidated n_points — _project_current now rejects n_points < 3, negative filter_order, and empty distributions with explicit messages (this also fixed the below-cap cryptic empty-array error). 3. Silently-dropped (2,2)/(4,4) components — TaylorWakefield.__init__ now rejects any component outside the 13 supported index pairs, so a table term can never be silently ignored. 4. Array aliasing — TaylorWakeComponent.__post_init__ now copies its input arrays (np.array instead of np.asarray), so components own their buffers; mutating one can no longer corrupt another. 5. **kwargs typo swallowing — the 1D branch of apply_wakefield now raises TypeError on unexpected keyword arguments, restoring the old strictness. 6. include_self_kick silently ignored — passing include_self_kick=False with a 3D wake now raises ValueError explaining the half self-term is always included. 7. wake_potential grid assumptions — the input profile is now validated: shape (n, 2) with n ≥ 2, strictly increasing z, and uniform spacing. 8. Missing factor knob — particle_kicks_3d gained a factor=1.0 parameter (equivalent to ocelot's Wake.factor), reachable through P.apply_wakefield(wake, factor=5). 9. Z0 duplication — taylor.py now imports Z0 from beamphysics.units (8e-14 relative difference; the regression values and benchmark are unaffected). 10. Stray .dat file — deleted the leftover, and the notebook now writes its round-trip table into a tempfile.mkdtemp() directory and removes it with shutil.rmtree, so interrupted executions can't litter the docs tree. Below-cap items also fixed: the test file now imports c_light instead of hardcoding 299792458.0, and apply_wakefield regained a proper type annotation (WakefieldBase | TaylorWakefield). I left the orientation dual-vocabulary, the ocelot-mirroring code structure, and the minor index-precomputation optimization as-is, and PR_NOTES.md in place since it's your staging area for the PR body — it's been updated to describe the new validation behavior, the factor parameter, the units.Z0 sourcing, and the new test count.
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
This PR adds a full 3D wakefield kick model to
beamphysics.wakefields, ported from ocelot'sWakephysics process (ocelot.cpbd.wake3D). The model represents the longitudinal point-charge wake function through a second-order Taylor expansion in the transverse coordinates of the source and witness particles, following I. Zagorodnov, K. Bane, and G. Stupakov, Phys. Rev. ST Accel. Beams 18, 104401 (2015). Transverse wakes are obtained from the longitudinal expansion through the Panofsky-Wenzel theorem, so the model produces longitudinal and transverse (dipole and quadrupole) kicks. This is a genuinely new capability for the package: all existing wakefield classes are purely longitudinal.The implementation was benchmarked head-to-head against ocelot using identical particle distributions and identical wake tables. Per-particle kicks agree to machine precision (about 1e-14 relative) for file-based wake tables, and to about 1.5e-10 relative for the analytic table generators, where the residual is entirely explained by ocelot hardcoding a slightly different value of the free-space impedance than the CODATA value provided by scipy.
The model
The longitudinal wake for a source particle at transverse position (x_s, y_s) and a witness particle at (x_w, y_w), separated longitudinally by s >= 0, is expanded as
so the full 3D wake is represented by a set of one-dimensional components$h_{ab}(s)$ . The index meaning is 0: constant, 1: source x, 2: source y, 3: witness x, and 4: witness y. For example, (0, 0) is the monopole longitudinal wake, (0, 4) is the vertical dipole wake, and (3, 3) and (2, 4) are quadrupole-like terms. To compute kicks, the particle charges (and transverse-moment-weighted "generalized currents") are deposited onto a smoothed longitudinal grid, each component is convolved with the appropriate current, and the transverse kicks are accumulated through the Panofsky-Wenzel integral.
Unlike the 1D wakefields in this package, the wake amplitudes are in V/C for the whole structure, because the structure length is baked into the wake table. Kicks are therefore returned in eV/c rather than eV/m.
What is added
beamphysics/wakefields/taylor.pyprovides the new module.TaylorWakeComponentis a dataclass holding one componentTaylorWakefieldholds the component set and computes kicks viaparticle_kicks_3d(x, y, z, weight, n_points=500, filter_order=20, factor=1.0), which returns(dpx, dpy, dpz)in eV/c using beamphysics conventions (the bunch head is at larger z). Thefactorargument scales all kicks and is equivalent to ocelot'sWake.factor, for example to represent several identical structures.TaylorWakefield.from_fileandTaylorWakefield.to_fileread and write the ocelot/Zagorodnov numeric wake table format, so tables can be exchanged with ocelot and with the ECHO family of codes (for example, the European XFEL*_WAKE_TAYLOR.dattables).TaylorWakefield.parallel_plateis an analytic generator for corrugated parallel-plate (dechirper) structures with the beam possibly offset from the center, ported from ocelot'sWakeTableParallelPlate. Thedecay=Falseoption reproduces ocelot's zeroth-orderWakeTableParallelPlate_originvariant. It is based on K. Bane, G. Stupakov, and I. Zagorodnov, Phys. Rev. Accel. Beams 19, 084401 (2016).TaylorWakefield.dechirper_off_axisis a mode-sum generator for a beam near a single corrugated plate of finite width, ported from ocelot'sWakeTableDechirperOffAxisand based on https://doi.org/10.1016/j.nima.2016.09.001.TaylorWakefield.wake_potentialconvolves a single component with a current profile, applying the Panofsky-Wenzel integral for transverse witness components, andTaylorWakefield.plotdisplays the tabulated components.half_gap,plate_distance,corrugation_gap,corrugation_period,length,sigma,orientation) in place of ocelot's single-letter names (a,b,t,p), with the correspondence documented in the docstrings.ParticleGroup.apply_wakefieldinbeamphysics/particles.pywas extended to support the new model. Thelengthargument is now optional: it remains required for 1D longitudinal wakefields, and it must be omitted forTaylorWakefieldobjects because the structure length is part of the wake table. For 3D wakefields,px,py, andpzare all updated, and extra keyword arguments such asn_points,filter_order, andfactorare forwarded toparticle_kicks_3d. Usage is simplyP2 = P.apply_wakefield(wake). The argument handling is strict in both directions: unexpected keyword arguments for a 1D wakefield raise aTypeErrorinstead of being silently ignored, and passinginclude_self_kick=Falsewith a 3D wakefield raises aValueErrorbecause the half self-term is always included in the Taylor convolution.ParticleGroup.wakefield_plotraises a clearTypeErrorwhen given a 3DTaylorWakefield, directing the user to apply the wake and plot the momentum changes directly, instead of failing with an obscureAttributeErrorinside the plotting internals.beamphysics/wakefields/__init__.pyexportsTaylorWakefieldandTaylorWakeComponent.Benchmark against ocelot
The script
scripts/benchmark_taylor_wakefield_vs_ocelot.pypushes identical 20,000-particle Gaussian bunches through ocelot'sWake.applyand through this implementation, and compares the per-particle kicks Px, Py, and Pz. The only requirement is the ocelot package from PyPI (pip install ocelot-collab); no ocelot source checkout is needed. The benchmark was run against ocelot-collab 26.6.1.For the file-based cases, the script writes a synthetic wake table with
TaylorWakefield.to_fileand reads it back through ocelot's ownWakeTableparser. The synthetic table contains monopole, dipole, and quadrupole-like components and deliberately exercises every term of the convolution: the tabulated wake W0, the derivative-coupled term W1, and the lumped R, L, and 1/C circuit terms. The benchmark covers nine cases and the results are as follows.get_long_wakeandget_dipole_wakedecay=False)The 1.5e-10 residual in the analytic-generator cases comes from a single constant: ocelot hardcodes the free-space impedance as 376.7303134695850 Ohm, while this implementation uses the package's own
beamphysics.units.Z0(equal to mu_0 times c, matching the scipy CODATA value to 8e-14 relative). The file-based cases, which share no such constant, agree to machine precision.Intentional differences from ocelot
H[n, m] > 0, which cannot distinguish a missing component from a component stored at index 0. As a consequence, ocelot'sget_dipole_wakesilently convolves the wrong component when a table has no (0, 4) term. This implementation raises aKeyErrorinstead.np.bincountimplementation, so the package gains no new dependency and no optional-dependency code path. The summation-order difference contributes only at the 1e-14 level.px,py, andpzin eV/c, whereas ocelot divides by the reference energy to update its dimensionless coordinates.TaylorWakefieldwith a component outside the 13 supported index pairs (for example (2, 2) or (4, 4), which ocelot would silently ignore) raises aValueError.particle_kicks_3dvalidatesn_pointsandfilter_orderand rejects empty or zero-length distributions with clear messages, andwake_potentialverifies that the supplied current profile is on a strictly increasing, uniform z grid instead of silently returning wrong results.TaylorWakeComponentcopies its input arrays, so components never share buffers. In ocelot's analytic generators (and in a direct port), the (0, 2) and (0, 4) components of the parallel-plate table alias the same array, and modifying one in place would silently corrupt the other.Tests
The new file
tests/test_wakefields_taylor.pycontains 29 tests covering component construction and validation, file round trips including the lumped R, L, and 1/C terms, physics checks (causality, net energy loss, dipole kick direction for an offset beam, quadrupole antisymmetry for a centered beam, exact horizontal/vertical orientation symmetry, and linear scaling with charge), a statistical regression against values generated after the ocelot benchmark was verified, and theParticleGroup.apply_wakefieldintegration including its argument validation. The validation and safety behaviors added after code review (unsupported component keys, buffer independence,n_pointsand grid validation, thefactorscaling, strict keyword handling, and thewakefield_plotguard) each have dedicated tests. The full test suite passes with 1,651 tests, and the changed files are clean under ruff check and ruff format.Documentation
docs/examples/wakefields/taylor_wakefield_3d.ipynbbuilds a dechirper wake table, plots the components and the wake potentials for a Gaussian current profile, applies the wake to aParticleGroupand shows the induced energy chirp and transverse kick, demonstrates the ocelot-compatible file round trip, and shows the single-plate mode-sum table. It is registered in themkdocs.ymlnavigation and executes cleanly.docs/api/wakefields.mdgains entries forTaylorWakefieldandTaylorWakeComponent.Scope and follow-ups
This PR covers ocelot's second-order
WakeandWakeTable, which is the main 3D model, together with the parallel-plate and off-axis dechirper table generators. Ocelot's third-orderWake3andWakeTable3variant is a natural follow-up, and the component design extends directly to index triples (a, b, c).🤖 Generated with Claude Code