Space-Charge Effect (SpaceCharge)
Introduction
The SpaceCharge command applies a transverse 2.5-D thin kick using
PIC, frozen analytic profiles, or quasi-frozen analytic profiles. It consumes
bunch-local slice data supplied by the user. All methods include electric and
magnetic self-force cancellation through \(1/\gamma^2\) and do not
calculate a longitudinal force.
Code location:
PASS/commands/space_charge.pyClass name:
SpaceCharge, registered name"SpaceCharge"Schema location:
PASS/para/schema/space_charge.pyExecution backend: CPU only
Main features:
named, reusable space-charge configurations at the top level of each beam input;
independent longitudinal slicing, mesh, deposition, and field-solver choices;
CIC or TSC charge deposition with the matching field-gather method;
conducting or free-space boundary models supplied by three field solvers;
optional HDF5 snapshots of charge density, potential, and field.
Physical Model
Macroparticle charge and transverse source
For one bunch, each live macroparticle represents bunch.ratio real
particles. Its signed source charge is
where \(R\) is bunch.ratio, \(Z\) is the signed charge number
bunch.num_charge, and \(e\) is the elementary charge. For slice
\(k\), the deposition step constructs the longitudinally integrated
transverse charge density
with units C/m2. CIC uses four grid nodes and TSC uses nine.
Lost particles and particles assigned slice ID -1 do not contribute. Other
invalid slice IDs are errors. The command first marks particles on or outside
its aperture as lost. Surviving participating PIC particles outside the grid
are a configuration error.
Integrated potential and field
The field solver treats every slice as a two-dimensional Poisson problem,
Here \(\Psi_k\) is the longitudinally integrated potential in V m and
\(\boldsymbol{\mathcal E}_{\perp,k}\) is the integrated transverse field
in V. After gathering the grid field to each particle, SpaceCharge uses
the corresponding SliceSet.delta_z only to obtain the average field,
Consequently, the field solver itself does not need delta_z. See
Transverse Field Solvers for the discretization, boundary conditions, and solver
interfaces.
Relativistic transverse kick
PASS stores normalized transverse momenta \(p_x=P_x/P_0\) and \(p_y=P_y/P_0\). For an effective interaction length \(L_{\mathrm{sc}}\), the command applies
The electric field already contains the signed source charge. The additional \(\operatorname{sgn}(Z)\) is the sign of the force on the tracked particle. The \(1/\gamma^2\) term represents the cancellation between the transverse electric and magnetic self-forces of a co-moving beam.
The kick updates only px and py, leaving x, y, z and dp
unchanged. Each SC command may set its own Aperture type and Aperture value.
This local loss check precedes the field calculation: it updates tags, loss
positions and turns, excluding newly lost particles from both sources and kicks.
Later points preserve existing loss records. Upstream element apertures such as
Marker may also handle losses. Participating PIC particles surviving the
loss check must still lie inside the grid, or an error is raised. Only strict
aperture interiors survive; touching any wall is sufficient for loss.
A missing aperture (or default) becomes the configuration’s grid rectangle
for every method. With an explicit aperture or off, analytic tracking is
independent of diagnostic grid extent; sampling does not truncate the source.
Execution Workflow
The user controls slicing and command ordering. SpaceCharge does not check
Slicer presence, execution history, turn, position or particle-state versions.
It consumes slice_id and slice_table.delta_z as supplied and does not
reassign particles based on z. Only data validity is checked: one integer ID
per bunch particle, IDs -1 or in range, and finite strictly positive widths.
No automatic slicing is performed. Existing same-s command priorities are
unchanged.
point-local particle loss aperture check
-> supplied slice data + current x, y, updated live tags
-> PIC deposition/solve/gather OR analytic per-particle evaluation
-> divide by slice delta_z
-> shared transverse kick and optional snapshot
Tracking Methods and Analytic Profiles
Method is pic (default), frozen, or quasi-frozen. Solver
specifies both the field algorithm/profile and its boundary condition:
Method |
Solver |
Model |
|---|---|---|
|
|
Open-boundary Green-function PIC. |
|
|
Zero-potential conductor; supported continuous chamber geometries. |
|
|
Zero-potential conductor; full grid-aligned rectangle only. |
|
|
Round Gaussian or elliptic Gaussian (Bassetti–Erskine). |
|
|
Uniform disk or ellipse, including the exterior field. |
In frozen, all slices using one configuration share fixed transverse
center, sizes and orientation. Defaults for omitted center and angle are zero;
the solver-specific sizes are required. Different lattice positions may use
different configurations. Slice charge and delta_z remain current inputs:
the transverse shape is frozen, not the complete electromagnetic field.
In quasi-frozen, each kick recomputes the centroid and population covariance
of each slice’s current live, assigned particles. Moments use denominator
\(N\), not \(N-1\); all macroparticles within a bunch have the same
physical weight. The source charge is \(Q_k=N_k R Z e\).
For an elliptic profile, covariance eigenvectors define the principal axes. Gaussian sizes are the square roots of the eigenvalues; uniform semi-axes are twice these RMS sizes. The first reported principal size is the larger. The counterclockwise major-axis angle is reported modulo pi in \([-\pi/2,\pi/2)\). Fields are evaluated after translation/rotation and rotated back to the original transverse axes.
For a round profile the moment-matched radius rule is
gaussian_round_free_space uses sigma; uniform_round_free_space uses R.
This preserves the centroid-relative radial second moment. It is a deliberate
round approximation for non-round populations, not an exact reconstruction of
their field. Choosing a uniform profile does not turn the particles into a KV
distribution. No automatic profile switching occurs.
Empty slices produce zero field and charge. Nonempty quasi-frozen round slices
require at least two particles and positive radial variance; elliptic slices
require at least three particles and a nondegenerate covariance. Specifically,
the smaller eigenvalue must exceed 64 * float64_epsilon * larger_eigenvalue.
Invalid sizes or covariance produce a slice-specific error, without silently
skipping a charged slice or substituting a minimum size. This is a numerical
validity check, not a guarantee of good statistical sampling.
Only free-space analytic profiles and transverse 2.5-D forces are implemented. There is no conducting-wall analytic correction or longitudinal space-charge force in these methods. Significant halo, multiple peaks or non-Gaussian structure generally cannot be represented by their few transverse moments.
For example, named analytic configurations can be written as:
{
"fixed_gaussian": {
"Method": "frozen",
"Solver": "gaussian_ellipse_free_space",
"Slice set": "space_charge",
"Center X (m)": 0.001,
"Center Y (m)": 0.0,
"Sigma X (m)": 0.004,
"Sigma Y (m)": 0.002,
"Angle (rad)": 0.2
},
"updated_gaussian": {
"Method": "quasi-frozen",
"Solver": "gaussian_ellipse_free_space",
"Slice set": "space_charge"
}
}
These objects belong inside Space charge.Configurations. An ordinary
SpaceCharge sequence command selects one by Configuration. The analytic
particle kick does not deposit charge or interpolate fields. Grid geometry
defines the default loss aperture and optional field/density sampling.
With an explicit aperture or off, changing diagnostic grid extent cannot
change tracking; changing the default aperture can change particle losses.
Configuration Example
The configurations are declared once in the top-level Space charge block.
Sequence commands refer to one configuration by name:
{
"Backend (gpu/cpu)": "cpu",
"Space charge": {
"Enabled": true,
"Configurations": {
"round_pipe": {
"Slice set": "space_charge",
"Nx": 129,
"Ny": 129,
"Grid Width X (m)": 0.08,
"Grid Width Y (m)": 0.08,
"Method": "pic",
"Solver": "fd_dirichlet",
"Particle Deposition Method": "CIC"
}
}
},
"Sequence": {
"sc_slicer": {
"S (m)": 10.0,
"Command": "Slicer",
"Slice set": "space_charge",
"Slice model": "equal_length",
"Number of slices": 64,
"Z range mode": "auto"
},
"sc_kick": {
"S (m)": 10.0,
"Command": "SpaceCharge",
"Configuration": "round_pipe",
"SC length (m)": 0.10,
"Aperture type": "circle",
"Aperture value": [0.035],
"Save field": false,
"Save potential": false,
"Save density": false,
"Save turns": []
}
}
}
Only configurations referenced by a SpaceCharge sequence entry are built.
Within one beam, commands that reference the same configuration name share
one grid. Dirichlet solver resources are cached by configuration and resolved
command aperture: equal walls reuse the factorization, different walls require
separate resources. FFT kernels are shared regardless of the loss aperture.
Different configuration names own independent resources even when their
values are identical. Configurations no longer contain Chamber.
Each command defines its own aperture. For example:
{
"sc_wide": {
"Command": "SpaceCharge", "S (m)": 2.5,
"Configuration": "default", "SC length (m)": 0.1,
"Aperture type": "circle", "Aperture value": [0.006]
},
"sc_narrow": {
"Command": "SpaceCharge", "S (m)": 7.5,
"Configuration": "default", "SC length (m)": 0.1,
"Aperture type": "circle", "Aperture value": [0.003]
}
}
This Sequence fragment references one already defined default configuration
at both points, with radii of 6 mm and 3 mm. For fd_dirichlet these are
also different conducting walls, so two solvers share the same grid. For
fft_free_space or analytic methods they affect losses only. These circular
apertures are invalid for dst_dirichlet.
Interface Parameters
Top-level Space charge block
Parameter |
Key |
Type |
Default / required |
Description |
|---|---|---|---|---|
|
|
bool |
|
Enables space charge for this beam input. When false, configuration contents and sequence commands are ignored and no resources are built. |
|
|
object |
|
Mapping from a non-empty user-defined name to one
|
Named resource configuration
Parameter |
Key |
Type |
Default |
Description |
|---|---|---|---|---|
|
|
str |
|
Name of supplied bunch-local SliceSet; no Slicer sequence/history check. |
|
|
str |
|
pic, frozen, or quasi-frozen. |
|
|
str |
|
One supported method/solver combination listed above. |
|
|
int |
|
PIC or diagnostic grid nodes, each at least 3. |
|
|
float or null |
|
Positive full widths, centered at zero; supply both axes together. |
|
|
float or null |
|
Positive half widths; alternative to the full-width pair, never mixed. |
|
|
str or null |
|
PIC only: null selects CIC; CIC or TSC with matching gather. |
|
|
float or null |
|
Frozen only; null means zero. Fixed for all slices. |
|
|
float or null |
|
Frozen ellipse only: counterclockwise local-x axis angle; null means zero. Round requires zero or null. |
|
|
float or null |
|
Required positive RMS size for frozen round Gaussian. |
|
|
float or null |
|
Required positive principal RMS sizes for frozen elliptic Gaussian. |
|
|
float or null |
|
Required positive radius for frozen uniform disk. |
|
|
float or null |
|
Required positive semi-axes for frozen uniform ellipse. |
SpaceCharge sequence command
Parameter |
Key |
Type |
Default / required |
Description |
|---|---|---|---|---|
|
|
str |
|
Selects the space-charge command implementation. |
|
|
float |
0.0 m |
Longitudinal machine position of the thin kick; must be finite. |
|
sequence object key |
str |
Required as sequence key |
Command instance name used in logging and the output path. |
|
|
str |
Required |
Exact name in the top-level |
|
|
float |
0.0 m |
Non-negative effective interaction length. Zero disables the kick at this command and produces no snapshot. An enabled local loss aperture is still checked without requiring slices. |
|
|
str |
|
Default resolves to the grid rectangle. Supports off, circle, rectangle, ellipse, rectcircle, rectellipse, racetrack, octagon and polygon. For Dirichlet solvers this also defines the conducting wall; off is rejected. |
|
|
list |
|
Dimensions in meters: [radius] for circle, [half-width, half-height] for rectangle, [a, b] for ellipse, or a vertex list for polygon. See Aperture. |
|
|
bool |
|
Saves |
|
|
bool |
|
PIC only. Analytic methods reject this request at initialization. |
|
|
bool |
|
Saves deposited PIC density or sampled analytic model density. |
|
|
list of int lists or int list |
|
Snapshot selections |
Grid Extent and Aperture Validation
Supply either Grid Width X/Y (m) or Grid Half Width X/Y (m) as a complete
pair; the unused pair must be absent or null. All widths must be positive and
finite. Omitting both pairs selects full widths of 0.02 m. Spacing inputs
Dx/Dy and explicit-bound mapping inputs are rejected. The grid is
centered at zero. For half widths 0.04 m and 0.02 m with 129 nodes per axis,
the ranges are [-0.04, 0.04] m and [-0.02, 0.02] m, and the nodal spacings
are 0.000625 m and 0.0003125 m. SpaceCharge.print() reports both ranges,
node counts, spacings, and the resolved aperture and its role.
Initialization checks every referenced command before tracking:
fd_dirichlet: a finite supported aperture must fit completely in the grid.dst_dirichlet: the aperture must be an axis-aligned rectangle identical to the complete grid. A smaller rectangle or any other shape is rejected.fft_free_space: a finite loss aperture must fit in the grid;offis allowed, but participating particles still must lie in the grid.Analytic solvers: an explicit loss aperture is independent of diagnostic grid extent;
offis allowed. The default still resolves to the grid rectangle.
A PIC aperture containing no active nodes is rejected during initialization. During deposition, a surviving participant with no active stencil node raises a mesh-resolution error rather than silently losing source charge.
Configuration Rules
Omitting
Space chargeis equivalent to"Enabled": false.When disabled, nested configuration contents are intentionally not validated; all
SpaceChargesequence entries are inert.Undefined configurations are rejected during initialization. Missing or structurally invalid SliceSet data are rejected when the kick consumes them.
Unreferenced configurations produce a warning and allocate no resources.
Configuration names and command references must be non-empty and may not contain surrounding whitespace.
Legacy root keys
Is space chargeandSpace-charge simulation parametersare rejected. Mesh and solver fields are not accepted inline in aSpaceChargesequence command.GPU execution with an enabled module and either nonzero length or an active loss aperture raises an error; use
"Backend (gpu/cpu)": "cpu".
Diagnostic Output
A snapshot is written only after a successful command execution when at least
one save flag is true and the current turn matches Save turns. An empty
Save turns writes nothing. Files are placed under
<output_dir>/space_charge/<command_name>/turn_NNNNNN/
Each bunch is stored in a separate HDF5 file. Grid datasets use array order
(slice, y, x).
Dataset |
Shape |
Unit |
Meaning |
|---|---|---|---|
|
|
m |
Horizontal and vertical grid-node coordinates. |
|
|
Stored slice indices. |
|
|
|
m |
Slice widths used later to convert integrated fields to average fields. |
|
|
C |
Deposited PIC charge or current analytic source charge in each slice. |
|
|
C/m2 |
Deposited PIC source or sampled analytic density when requested. |
|
|
V m |
Integrated potential; present when |
|
|
V |
Integrated fields; present when |
File attributes record the solver and deposition method, grid bounds and
spacing, turn, beam and bunch identity, harmonic metadata, command position
and length, charge per macroparticle, particle counts, precision, and random
seed. aperture_type and the JSON-encoded aperture_value record the local
resolved geometry. aperture_role is loss_and_conductor for Dirichlet
or loss_only for free-space methods. potential_gauge is boundary_zero for fd_dirichlet and
dst_dirichlet and kernel_reference for fft_free_space.
Validation Layout
The integration-test names state both the source and boundary model. The
*_free_space_fft workflows cover round/elliptic Gaussian and KV sources
with the open-boundary Green-function solver. The corresponding
*_rectangular_fd_dst workflows regenerate the same four fixed-seed
sources and solve the same deposited charge with FD and DST on a 257 by 257
grounded rectangular grid. Their analysis directories contain a three-way
free_space_fft_vs_rectangular_fd_dst_field.png comparison, a CSV table,
and fd_dst_relative_error_same_plot.png with both directions of the
pointwise FD/DST relative difference. Differences from FFT include the
physical boundary-condition change and are not an FD/DST discretization error.
FD-only aperture tests are named by their physical boundary. They include a
round KV beam inside a larger circular conductor, a KV beam filling an
elliptic conductor, and Gaussian plus KV-uniform projected sources across all
apertures supported by PASS.utils.aperture (including ellipse, racetrack,
octagon, and user polygon). DST is not applied to those curved or irregular
domains because dst_dirichlet diagonalizes only the full rectangular
Dirichlet grid.
Running the Validation Suite
All space-charge integration cases run automatically, including the ten full generated-input workflows. From the repository root, run:
python -m tests.integration.space_charge
python -m tests.integration.space_charge regression
The first command selects all 25 integration tests. The second explicitly runs
the related unit and local Codex regression files, including the restored
slice-isolation and slice-width/interaction-length scaling checks. The local
Codex files must be present; they are not part of default pytest discovery.
Repository-wide pytest discovers tests/unit and tests/integration.
Use analytic, fft, rectangle, aperture, checks, or workflows instead of
the default all to select a category. Repeat --case <case_name> to
select individual full workflows. --collect-only lists the selected pytest
items. No complex integration case is skipped by default.
Execution is serial. Rectangle comparisons automatically prepare and reuse the
matching FFT workflow within the same batch. Each new integration run creates
a unique directory under tests/codex/space_charge_runs; its path is printed
in the terminal summary. --output-dir selects a new or empty batch directory.
Existing results are never deleted by the runner. --mode ana --output-dir
<existing_batch> reruns analysis of saved snapshots and updates analysis
products without tracking. Non-test modes operate only on full workflows;
they do not run numerical-only checks.
The normal python -m pytest tests/integration/space_charge command executes
the same integration cases. Original per-module sim, ana, simana
and --run-dir interfaces remain available. See
tests/integration/space_charge/README.md for the group table, single-case
commands, output layout, and the distinction between batch and per-case paths.
Analytic Diagnostics and Validation
HDF5 schema_version is 3. Attributes include method, the full
solver name and grid_role (tracking or diagnostic). Analytic
snapshots additionally store macro_count, center_x, center_y,
size_x, size_y and angle per slice. size_convention is
principal_rms for Gaussian profiles or uniform_semi_axes for uniform
profiles. parameter_source is configuration or
current_slice_population_moments. Empty-slice parameters are NaN; their
charge and sampled fields/density are zero. A frozen ellipse retains the user’s
axis order, while quasi-frozen ellipses report the major axis first.
Analytic Save potential is not implemented and is rejected even if no save
turns are selected; potential_gauge is not_computed. The density grid is
an analytic sample, not deposited charge: its finite-grid quadrature need not
equal slice_charge. Diagnostics never alter the source or particle kick.
Run python -m tests.integration.space_charge analytic for four generated-input
comparisons (180,000 particles each, three slices, all three methods) and two
repeated-kick parameter-evolution tests. Independent Gaussian/uniform field
integrals verify the actual momentum increments with relative L2 tolerance
2e-8. PIC comparisons use 0.06 relative L2 in a resolved central region;
finite-particle noise and discretization are included. Charge comparison uses
a particle-count-dependent floating-summation bound. The evolution tests use
prescribed affine transport; they do not establish long-term ring stability.
Outputs include input JSON, HDF5, particle-kick NPZ, CSV/JSON measurements,
pic_frozen_quasi_frozen_field_comparison.png,
analytic_kick_vs_independent_integral.png and
frozen_vs_quasi_frozen_parameter_evolution.png. For generated analytic
cases, call analyse(run_dir) from
tests.integration.space_charge.test_analytic_free_space_tracking to update
plots from saved artifacts without tracking. This analytic test group currently
uses --mode test; the older batch non-test workflow selection is unchanged.
The old resource keys Field solver, Aperture and Chamber and old
public solver values are rejected. Use Method and Solver in the
configuration and Aperture type/value in each command. Fixed transverse
parameters are accepted only by frozen and must match the selected profile;
deposition settings are PIC-only.