Steady ion beams in a self-consistent field: extraction of positive or
negative ions from a plasma, transport through electrodes and solenoids,
and charge changes of a fast beam in a gas or a plasma, in planar or
cylindrical (x, r) geometry. BeamOptics converges the beam by a
Vlasov iteration (solve the field, trace the beam, deposit its space
charge, repeat); beamtracer.neutralizer couples the traced beam to a
global model of a beam-driven plasma neutralizer
(wellbalanced). The field
gather and the charge deposit are Pyccel, NumPy, CUDA and Metal kernels
through cunumpy.
beamtracer.optimization searches over the parameters of a design
(SciPy’s differential evolution, or a scan) in parallel processes, with
constraints, penalised failures and a resumable record of every
evaluation; its problems are an einzel lens
(beamtracer.optimization.einzel) and a negative-ion beamlet
(beamtracer.optimization.beamlet).
Documentation: https://max-models.github.io/beamtracer/
With uv:
make install # uv sync --extra dev, plus the pre-commit hooksOr create and activate a Python environment (3.10 or newer) and install with pip:
python -m venv env
source env/bin/activate
pip install --upgrade pip
pip install -e .The neutralizer extra installs wellbalanced, examples the plotting
libraries of the examples, and test, docs and dev the test runner,
the documentation tooling and the linters:
pip install -e ".[dev]"import astropy.units as u
from beamtracer import (
BeamOptics,
BeamOpticsConfig,
BeamSpecies,
FieldBCs,
FieldBoundaryCondition,
GridParams,
)
neumann = FieldBoundaryCondition.neumann(0.0)
config = BeamOpticsConfig(
grid=GridParams(num_gridpoints=(41, 21), upper_bound=(10 * u.mm, 5 * u.mm)),
bcs_field=FieldBCs(
x_lower=FieldBoundaryCondition.dirichlet(0.0),
x_upper=FieldBoundaryCondition.dirichlet(-1 * u.kV),
y_lower=neumann,
y_upper=neumann,
),
beams=[
BeamSpecies(
"H+",
10 * u.A / u.m**2,
(0.0, 0.0),
(0.0, 5 * u.mm),
energy="10 meV",
num_particles=40,
)
],
diagnostic_planes=["9.9 mm"],
)
model = BeamOptics(config).run()
print(model.beam_moments(9.9e-3).current)Every physical setting takes an astropy Quantity (5 * u.mm), a string
astropy parses ("5 mm", as in a configuration file) or a bare number
in its documented unit: SI, with energies and temperatures in eV (or K),
masses in u and charges in e. It is converted when the setting is made,
so the bare numbers upper_bound=(10e-3, 5e-3), -1000.0 and
energy=0.01 are the same diode. A quantity of the wrong dimension
raises.
The examples (python -m beamtracer.examples.<name>) are a planar
proton extraction, a round H⁻ beamlet with its co-extracted electrons,
and a D⁻ beam through a neutralizer channel.
Formatting and linting use ruff, type checking pyright, run by pre-commit and in CI:
make lint # ruff check, ruff format --check, pyright
make test # pytest with coverageCommit messages follow Conventional Commits; see CONTRIBUTING.md.
The documentation in docs/ is an Astro + Starlight site: hand-written
pages, the notebooks in tutorials/ executed and published as pages,
and the API reference generated from the docstrings with
starlight-pydocs. It needs
Node 22 or newer.
make docs-install # npm packages and the Python docs extra
make docs-notebooks # execute tutorials/*.ipynb and convert them to pages
make docs-dev # live preview at http://localhost:4321/beamtracer/
make docs-build # the static site in docs/distREADME.md is rendered from README.qmd with
Quarto:
make readmeBefore merging a release to main, update the version in
pyproject.toml, src/beamtracer/__init__.py and CITATION.cff
(including its release date), and add the release notes to
CHANGELOG.md. The push to main creates a GitHub release with a
vX.Y.Z tag and publishes the package to PyPI with trusted publishing
(OIDC). The one-time PyPI and GitHub configuration is described in the
publishing
tutorial.

