Implementation¶
Workflow¶
A multipacting analysis proceeds through three objects:
from pymultipact.domain import Project, Domain
proj = Project()
proj.create_project('TESLA')
domain = Domain(proj) # geometry + mesh
domain.compute_fields() # Maxwell eigenvalue problem
domain.analyse_multipacting() # particle tracking sweep (parallel)
domain.plot_cf(launchable_norm=True) # counter function
domain.plot_Ef() # final impact energy + SEY lines
domain.plot_ef() # enhanced counter function
domain.plot_df(epk_i=42) # d20 map at one field level
Geometry and mesh¶
The cavity contour is a polyline read from a .n geometry file (or written
by geometry_writer for parametrised elliptical cavities). Because netgen
anchors a mesh node to every polyline point, dense contours would force a
needlessly fine surface mesh: the contour is therefore resampled to
Domain(n_boundary_points=250) points by arc-length decimation. Only
original profile points are kept (none are invented), and the endpoints plus
the extreme-coordinate points that define the bounding box always survive.
n_boundary_points=None keeps the file’s full resolution; the original
TESLA mid-cell contour is preserved in sample_domains/tesla_mid_cell_fine.n.
MEVP solver¶
Domain.compute_fields(order=3) assembles the r-weighted curl-curl and mass
forms (see Multipacting) on an \(H(\operatorname{curl})\) space of
third-order Nédélec elements and solves the generalised eigenproblem with
NGSolve’s preconditioned inverse iteration (PINVIT), wrapped in a
divergence-free projector built from the discrete gradient to suppress the
gradient kernel. The magnetic field follows from Faraday’s law,
\(\mathbf{H} = \frac{j}{\mu_0\,\omega}\nabla\times\mathbf{E}\).
Particle tracking¶
Integrators.rk4 advances all particles simultaneously (vectorised over the
population) with the classical RK4 scheme. Field values along trajectories are
exact FEM point evaluations — a structured-grid field cache was investigated
and rejected because surviving electrons live in a sub-millimetre layer at the
wall where interpolation errors compound over the ~20 impacts.
Boundary handling is exception-driven: evaluating the field at a point outside the meshed domain raises, which triggers the collision handler for the affected sub-step. A particle that nevertheless escapes (e.g. re-emitted marginally outside) is dropped as lost by a guard at the next step instead of aborting the run.
Collision detection¶
For every particle within one light-step of the wall, the segment between its
previous and current position is intersected with candidate wall segments
reconstructed from the HIT_NEIGHBOURS (default 100) nearest surface points,
found with a k-d tree over the mesh boundary vertices. On intersection, the
impact time, surface normal and impact energy are computed and the particle is
either re-emitted or absorbed according to the loss_model
(Multipacting, Collision Detection and Resolution).
When a particle reaches 20 counted impacts it is archived to the bright set together with its full impact history (energies, secondary yields, initial site and phase), from which all multipacting metrics are computed.
Parallel field-level sweep¶
The Epk sweep is embarrassingly parallel and analyse_multipacting runs it
on multiple processes by default (proc_count=None picks
min(cpu_count - 1, n_epks); proc_count=1 runs in-process). Workers
receive the mesh and modal fields via pickle and return per-field-level
results that are reassembled in sweep order. On Windows, scripts (not
notebooks) must call it under if __name__ == '__main__':.
Testing¶
The test suite lives in tests/ (pytest; full tracking regressions are
marked slow):
python -m pytest -q # everything (~2 min)
python -m pytest -q -m "not slow" # fast unit tests only (~2 s)