Transverse Field Solvers
Introduction
PASS.commands.solver provides the numerical layer used by SpaceCharge.
It is independent of Simulation and PASS particle classes: PIC callers supply
transverse particle arrays, slice IDs, a uniform grid, and charge per
macroparticle; analytic fields use coordinates, charge, and profile parameters. The same API can therefore be used by commands, tests, and
standalone field studies.
Code location:
PASS/commands/solver/PIC entry point:
PASS/commands/solver/pic.pyLow-level PIC solver identifiers:
fd,dst_rectangle,fft_free_spaceAnalytic tracking entry point:
PASS/commands/solver/analytic.pyArray order:
(slice, y, x)for batched grid dataExecution backend: NumPy/SciPy on CPU
The numerical package solves only the transverse field problem. It does not know the interaction length, bunch rigidity, relativistic kick factor, or simulation turn. Those responsibilities belong to Space-Charge Effect (SpaceCharge).
Public configuration uses Method and Solver. For Method="pic",
fd_dirichlet, dst_dirichlet and fft_free_space dispatch to the
low-level identifiers fd, dst_rectangle and fft_free_space respectively.
The identifiers in the numerical API sections below are arguments to
build_pic_resources. fft_free_space is shared with the public JSON
Solver value; the FD and DST identifiers differ between these interfaces.
frozen and quasi-frozen select the analytic profiles described below.
PIC Data Flow
One call follows this sequence:
particle x, y, tag and slice_id
|
v
CIC or TSC charge deposition
|
v
Sigma[slice, y, x] in C/m^2
|
v
batched Poisson field solve
|
v
Psi in V m, integrated Ex/Ey in V
|
v
matching CIC or TSC field gather
All longitudinal slices are stored as leading right-hand sides and are solved
together. Geometry-dependent masks, sparse factorizations, spectral
eigenvalues, or FFT kernels are built once in PICResources and reused.
Grid and Source Definition
Uniform nodal grid
GridGeometry describes a uniform node-centered rectangle. For the
top-level space-charge input, full widths \(W_x\) and \(W_y\) define
Nx and Ny are node counts, not cell counts, and must each be at least
3. Both configuration inputs and build_grid_geometry accept a complete
full-width pair or a complete Grid Half Width X/Y (m) pair. Half widths
\(H_x,H_y\) give \(W_x=2H_x,W_y=2H_y\). Mixing pairs, spacing inputs
Dx/Dy, and explicit-bound mapping inputs are rejected. Direct
GridGeometry(...) construction remains available for numerical code.
Charge deposition
For each live particle with a valid slice ID, the deposition method distributes the signed charge onto active field nodes:
Method |
Nodes per particle |
Matching gather |
Characteristics |
|---|---|---|---|
|
2 x 2 |
bilinear |
Piecewise-linear weights from the particle’s containing grid cell. |
|
3 x 3 |
quadratic |
Wider quadratic stencil with smoother particle-grid coupling. |
Conductor and boundary nodes are removed from a deposition stencil. Remaining weights are normalized per particle so the retained stencil conserves that particle’s charge. The same active-node normalization is used during gather. A warning is emitted when a boundary changes the stencil. If an in-domain particle has no active node, it is ignored by that PIC call and gathers zero field; its PASS particle tag is not changed.
Particles with tag <= 0, invalid slice IDs, or coordinates outside the grid
or physical aperture are also ignored. Grid and aperture dimensions should
therefore cover the intended tracked distribution.
These standalone PIC functions do not modify particle tags. SpaceCharge
first applies its command aperture using the shared particle-loss function;
wall and outside particles are lost before deposition. Surviving participating
particles outside the grid, or without active stencil nodes, raise an error.
See Space-Charge Effect (SpaceCharge) for initialization checks and the input contract.
Poisson Equation and Units
Each solver consumes the deposited surface density \(\Sigma_k\) in C/m2 and solves
Because the slice charge has already been integrated longitudinally,
\(\Psi\) has units V m and \(\mathcal E_x,\mathcal E_y\) have
units V. These are integrated fields, not V/m average fields.
SpaceCharge divides a gathered field by that slice’s delta_z before
calculating the kick.
Field-Solver Selection
Low-level |
Boundary model |
Allowed aperture |
Numerical method and intended use |
|---|---|---|---|
|
Zero Dirichlet conductor |
Full grid rectangle or any supported continuous aperture |
Cached sparse LU solve. Uses the regular five-point stencil on a full rectangle and Shortley–Weller distances near a curved or oblique wall. |
|
Zero Dirichlet conductor |
Complete grid-aligned rectangle only |
Direct type-I discrete sine transform using cached eigenvalues. It is
a specialized rectangular-chamber alternative to |
|
Open free space |
Complete grid only; no conducting aperture |
Zero-padded Hockney-style convolution with cached Green-function kernels. Use when image charges from a conducting chamber are not wanted. |
Finite difference: fd
For a full rectangular domain, the regular five-point discretization is
The outer grid nodes are held at \(\Psi=0\). An explicit aperture equal to the full grid uses the same rectangular solver. Other continuous apertures use the Shortley–Weller solver. Where a neighboring node lies outside the aperture, the regular spacing is replaced by the actual distance from the active node to the grid-line/wall intersection. The physical wall is therefore not approximated merely by the visible stair-step node mask.
The sparse matrix and LU factorization are constructed once. Every slice is passed to the same factorization as one dense multi-column right-hand side.
Sine transform: dst_rectangle
dst_rectangle imposes zero potential on all four outer grid edges. A
type-I discrete sine transform diagonalizes the same rectangular
finite-difference operator. For horizontal mode \(m\) and vertical mode
\(n\), its eigenvalue is
The solver transforms only the two transverse axes, preserving the leading slice axis. It cannot represent a curved or smaller internal conductor.
Free-space Green function: fft_free_space
fft_free_space performs a zero-padded linear convolution, avoiding the periodic
wrap-around of an unpadded FFT. Away from the self cell, the kernels are
The self-cell entries are set to zero. The potential therefore uses a kernel reference and is meaningful only up to an additive constant; the transverse fields are the physical outputs. This solver models open free space, not a grounded beam pipe.
The source uses one forward real FFT. Each requested output uses one inverse
FFT: two for the electric fields and a third for the potential. By default,
FFTFreeSpaceSolver.solve returns all three outputs. With
compute_potential=False, it returns potential=None and skips the
potential transform. The potential kernel is built lazily on its first use.
The inverse transforms share a scratch spectrum; returned arrays own only
the physical grid, releasing the larger padded arrays.
The same optional keyword is available in solve_pic, pic_cpu, and
solve_poisson_fft_free_space. FD and DST still calculate potential because
their fields require its gradient. The SpaceCharge command requests FFT
potential only on selected turns when Save potential is enabled.
Field gradients
fd on a full rectangle and dst_rectangle obtain fields with
E = -grad(Psi) using grid finite differences. Shortley–Weller fd
uses unequal-distance derivative coefficients at active nodes near the wall.
fft_free_space convolves directly with the analytic field kernels.
Aperture Interface
The SC command owns both loss geometry and, for FD/DST, the conducting wall.
The configuration has no Chamber field. For example, a command contains:
"Aperture type": "ellipse",
"Aperture value": [0.04, 0.02]
Internally, FD receives {"Type": "ellipse", "Value": [0.04, 0.02]} as its
aperture argument. FFT receives no conducting aperture; its command aperture
is applied only to particle losses. The following table describes the shared
low-level geometry builder, with all dimensions in metres. Its generic
default differs from SC: SC resolves default to the actual grid rectangle
before calling the builder and rejects off for Dirichlet solvers.
Type |
|
Geometry |
|---|---|---|
|
omitted |
No separate physical aperture. For |
|
omitted |
Default tracking rectangle \(|x|\leq1\), \(|y|\leq1\). |
|
|
Circle of radius \(R\). |
|
|
Rectangle \(|x|\leq A\), \(|y|\leq B\). |
|
|
Ellipse \(x^2/A^2+y^2/B^2\leq1\). |
|
|
Intersection of the rectangle \((W,H)\) and circle \(R\). |
|
|
Intersection of the rectangle \((W,H)\) and ellipse \((A,B)\). |
|
|
Central half-width/half-height \((W,H)\) with horizontal elliptic end caps of semi-axes \((A,B)\). |
|
|
Symmetric octagon satisfying \(|x|\leq W\), \(|y|\leq H\), and \(|x|+|y|\leq W+H-D\). |
|
|
Polygon with at least three finite vertices and nonzero area. |
circular, elliptic and rectangular are accepted lower-level
aliases. Named parameters are also supported by the Python aperture builder,
but generated input files should use the table above.
For dst_rectangle and fft_free_space, the aperture must resolve exactly to
the full grid-aligned rectangle; null is the normal input. For fd, a
physical aperture should be resolved within the selected grid. The high-level
SC initializer rejects any finite PIC aperture extending beyond the grid,
and rejects a DST aperture different from the full rectangle. Thus an oversized
command aperture never silently becomes a truncated conductor.
Python Interfaces
Geometry and PIC pipeline
Interface |
Main arguments |
Result and behavior |
|---|---|---|
|
|
Immutable uniform-grid description with |
|
node counts plus one complete full-width or half-width pair |
Builds a |
|
grid and continuous aperture mapping |
Returns the boolean nodal membership mask. |
|
|
Returns reusable |
|
particles, |
Dispatches to CIC or TSC and returns |
|
particles, |
Deposits and solves all slices, returning |
|
field, particles, geometry, resources, |
Gathers one or many fields with CIC weights. |
|
field, particles, geometry, resources, |
Gathers one or many fields with TSC weights. |
|
arrays |
Convenience array API around |
particles may be a mapping or an object exposing equal-shaped x and
y arrays and, optionally, tag. charge_per_macro may be a finite
scalar or an array broadcastable to the particle shape. slice_id must be
an integer array with one entry per particle. Supplying num_slices keeps
trailing empty slices in the output.
Solver builders and one-shot wrappers
Builder |
Reusable solver |
One-shot wrapper |
|---|---|---|
|
|
|
|
|
|
|
|
call |
|
|
|
For repeated calculations, prefer a builder plus solver.solve so cached
resources are reused.
Result Objects
Object / field |
Shape |
Unit |
Description |
|---|---|---|---|
|
|
C/m2 |
Deposited charge density. |
|
|
C |
Charge retained in each slice. |
|
|
Number of deposited macroparticles per slice. |
|
|
2-D, 3-D, or |
V m |
Integrated potential; |
|
2-D or 3-D |
V |
Integrated transverse fields; |
|
batched |
mixed |
Combines density, potential, fields, geometry, deposited charge, and deposition diagnostics. |
A field solver accepts either (ny, nx) for one slice or
(n_slice, ny, nx) for a batch. All values must be finite and the trailing
dimensions must match the solver geometry.
Analytic Tracking and Reference Fields
The formula_* modules provide free-space analytic integrated fields. They
are used by the frozen and quasi-frozen tracking methods and remain
available for reference calculations. Their public solver names are
gaussian_round_free_space, gaussian_ellipse_free_space,
uniform_round_free_space and uniform_ellipse_free_space. These formulas
are evaluated directly at particle positions, outside the PIC pipeline.
Source charge, coordinates, and units
All four profiles solve the transverse free-space problem for one charge slice. Let Q denote its signed total charge in C, and let (u, v) be coordinates in the source’s principal frame. The command obtains Q from the current live, assigned population after aperture losses, then translates and rotates:
Here R is bunch.ratio, Z the signed charge number, and N_k the slice’s
live macroparticle count. The densities below are longitudinally integrated
surface densities in C/m2; their integral over the entire transverse
plane is Q. The fields are integrated fields in V. SpaceCharge divides
them by delta_z to obtain V/m and applies the relativistic kick separately.
The formula functions themselves apply neither delta_z nor 1/gamma².
The source center and profile sizes describe the beam, not the vacuum chamber. These free-space formulas contain no conducting-wall image fields. A command aperture handles particle losses only; its default is the grid rectangle. Diagnostic sampling does not clip or renormalize the analytic model. Even when losses occur, a frozen Gaussian retains its specified full Gaussian shape with the updated Q; it is not an exact field of a Gaussian truncated by the aperture.
Round Gaussian: gaussian_round_free_space
formula_gaussian_round.gaussian_round_field uses the single-axis RMS
size sigma (Sigma (m) in a frozen configuration):
At the origin both components are zero. The scalar factor multiplying (u, v)
has the limit Q/(4 pi epsilon_0 sigma²), so the central force is linear.
The implementation uses -expm1(-r²/(2 sigma²)) to avoid subtracting nearly
equal numbers. Far from the source the signed radial field tends to
Q/(2 pi epsilon_0 r), with its sign set by Q.
Elliptic Gaussian: gaussian_ellipse_free_space
formula_gaussian_ellipse.gaussian_elliptic_field uses the principal
single-axis RMS widths sigma_u and sigma_v (frozen Sigma X/Y (m)):
For sigma_u > sigma_v, PASS evaluates the Bassetti–Erskine formula in the
first quadrant using the Faddeeva function scipy.special.wofz:
When sigma_u < sigma_v, coordinates, widths and returned components are
exchanged. Equal widths reduce to the round Gaussian; numerically PASS uses
np.isclose(sigma_u, sigma_v, rtol=const.eps, atol=0). At small amplitude the
linear terms are
The implementation includes cubic corrections near the center; numerical stability details follow the Python interface table below.
Uniform disk: uniform_round_free_space
formula_uniform_round.uniform_round_field uses the physical outer beam
radius R_b (Radius (m)), not an RMS size:
The interior field is linear, the exterior field decreases as 1/r, and the field is continuous at r=R_b. The per-axis RMS size of this uniform disk is R_b/2. A source edge is distinct from a command’s physical aperture wall; evaluating the formula on the source edge does not itself mark a particle lost.
Uniform ellipse: uniform_ellipse_free_space
formula_uniform_ellipse.uniform_elliptic_field uses beam semi-axes a and b
(Semi-axis A/B (m)), with per-axis RMS sizes a/2 and b/2:
Inside the source A=a and B=b, giving linear fields. Outside, lambda is the positive root of the confocal-ellipse equation. The implementation computes that root with a cancellation-resistant quadratic expression and uses the rationalized field above to remain stable near a=b. Fields are continuous across the source edge; a=b reduces to the uniform disk.
Frozen and quasi-frozen parameter selection
frozen uses one fixed center, orientation and set of profile sizes for
all slices referencing a configuration. Center and angle default to zero;
solver-specific sizes are required. Q and delta_z remain current, so
freezing the transverse profile does not freeze its field amplitude.
quasi-frozen recomputes every slice’s population moments at each kick:
The eigenvalues satisfy nu_1 >= nu_2; the eigenvector for nu_1 determines the major-axis angle. Moments use denominator N_k, not N_k-1. The round rule preserves the radial second moment even if the particles are not round; it does not reconstruct a non-round source’s exact field. Uniform profiles approximate a uniform projected density, rather than making the tracked population a KV distribution.
Empty slices return zero field. Nonempty quasi-frozen round slices require
at least two particles and positive radial variance. Elliptic slices require
at least three particles and nu_2 > 64 * float64_epsilon * nu_1.
Invalid moments raise an error. The supplied slice IDs and widths remain
under user control; no Slicer execution or turn-history check is added.
Direct formula example
This standalone example evaluates the integrated field and then converts it to the slice-average field. Coordinates are already in the source frame:
import numpy as np
from PASS.commands.solver.formula_gaussian_ellipse import gaussian_elliptic_field
x = np.linspace(-0.02, 0.02, 201) # m, relative to the source center
ex_integrated, ey_integrated = gaussian_elliptic_field(
x, np.zeros_like(x), slice_charge=1e-9,
sigma_x=0.003, sigma_y=0.002,
)
delta_z = 0.01 # m
ex_average = ex_integrated / delta_z # V/m
For tracking, select the corresponding public Solver together with
Method="frozen" or "quasi-frozen"; see Space-Charge Effect (SpaceCharge) for JSON
examples and command aperture defaults. Validation cases in
tests/integration/space_charge/test_analytic_free_space_tracking.py compare
actual particle kicks against independent field integrals. Run
python -m tests.integration.space_charge analytic for these comparisons
and repeated-kick parameter-evolution checks, including generated plots.
Python interfaces and numerical stability
solve_analytic(x, y, slice_id, valid, num_slices, charge_per_macro,
configuration) groups assigned live particles by slice. Frozen parameters
come from the configuration; quasi-frozen parameters come from each slice’s
current population moments. AnalyticResult contains particle-sized
integrated_ex/integrated_ey, slice charges and counts, and a
(n_slice, 5) parameter array with columns center-x, center-y, size-x, size-y,
angle. Sizes are Gaussian RMS widths or uniform semi-axes; empty-slice
parameters are NaN and their field/charge is zero. No simulation turn or Slicer
execution metadata is read. See Space-Charge Effect (SpaceCharge) for the exact moment rules.
sample_analytic_grid(result, configuration, geometry) evaluates diagnostic
density and fields on a grid after particle fields have been obtained.
It returns a grid result with potential=None; analytic potential output is
currently rejected by the command. The sampling grid does not determine
particle kicks or truncate the analytic charge distribution.
Function |
Distribution parameters |
Return value |
|---|---|---|
|
|
Round-Gaussian integrated |
|
|
Bassetti–Erskine integrated field in V;
|
|
|
Uniform round-slice field inside and outside the beam. |
|
|
Uniform elliptic-slice field inside and outside the beam. |
|
real-particle count, signed charge number |
Signed physical charge in C. |
All analytic functions accept scalar or broadcastable coordinate arrays,
require positive finite size parameters, and use epsilon_0 from PASS
constants unless explicitly overridden.
The uniform-ellipse field uses confocal semi-axes \(A=\sqrt{a^2+\lambda}\), \(B=\sqrt{b^2+\lambda}\) and the equivalent expressions \(\mathcal E_x=Qx/[\pi\epsilon_0 A(A+B)]\), \(\mathcal E_y=Qy/[\pi\epsilon_0 B(A+B)]\). Here lambda is zero inside the ellipse and the nonnegative confocal parameter outside. This rationalized form avoids cancellation as the two source semi-axes approach equality, including almost isotropic quasi-frozen slices.
The elliptic Gaussian formula is evaluated in the first quadrant and its
field signs are restored by reflection symmetry. This avoids subtracting
exponentially large Faddeeva-function values in the lower half-plane,
particularly for nearly round beams. The width difference is factored as
(sigma_x - sigma_y) * (sigma_x + sigma_y); the existing round-beam limit
and axis-exchange convention are retained.
Near the beam center, where the stable-half-plane terms also nearly cancel,
a cubic field expansion is used when
(x/sigma_x)**2 + (y/sigma_y)**2 <= 1e-6. Its relative truncation error is
of order the square of this normalized squared radius.
Efficient Grid Sizes
Here N counts nodes, including both endpoints: a width W has spacing
h = W/(N-1). Choose the physical extent and required resolution first.
The following are convenient starting sizes near each nominal scale, not
hardware-independent timing optima; apply the rule separately to each axis.
Nominal scale |
FD baseline |
DST nodes |
FFT nodes |
FFT padded size |
|---|---|---|---|---|
128 |
About 128 |
129 |
128 |
256 |
256 |
About 256 |
257 |
256 |
512 |
512 |
About 512 |
513 |
512 |
1024 |
1024 |
About 1024 |
1025 |
1024 |
2048 |
2048 |
About 2048 |
2049 |
2048 |
4096 |
For DST-I the interior length is N-2 and the logical transform length is
2*(N-1). Therefore N = 2**k + 1 is a convenient family. More generally,
small prime factors in N-1 are favorable; powers of two are not the only
fast lengths.
For FFT Green convolution each axis is padded to
P = scipy.fft.next_fast_len(2*N-1). Choosing N = 2**k gives
P = 2**(k+1) at the listed sizes. Nearby node counts can also be fast;
benchmark candidates at comparable resolution. Padding prevents circular
wrap-around and does not increase the physical grid extent.
FD uses sparse factorization and has no special power-of-two advantage.
Choose the smallest size satisfying geometry and convergence requirements,
for example N >= ceil(W/h_max) + 1. An odd size can be useful to place a
node on the centerline. The FD column is only a resolution baseline: a
2048-by-2048 sparse factorization can require substantial memory. For a full
grounded rectangle, DST solves the same discrete Poisson system without
sparse LU factors. PASS keeps the explicitly configured node counts.
Selection Guidance and Limitations
Use
fdfor a grounded chamber, especially a curved, polygonal, or compound aperture.Use
dst_rectanglefor a grounded chamber exactly aligned with the full rectangular grid.Use
fft_free_spacefor an open-boundary approximation without image charges.Increase the grid extent until an open-boundary field is insensitive to truncation, and increase resolution until field and kick observables converge.
CIC is cheaper and more local; TSC gives smoother coupling but uses a wider stencil. The deposition and gather methods must remain paired.
The current solver package is CPU-only. A nonzero enabled
SpaceChargecommand on the GPU backend is not supported.