TOML input reference
An input file is TOML with these tables: [beam], [materials] (optional),
[target], [physics], [stopping] (optional), [run] and [tally]
(optional). An electron run has an [electron] table instead of [beam],
[physics] and [tally] (section “Electron runs” below). The physics behind each [physics] choice is in the
physics manual: potentials
and screening lengths,
electronic stopping, the
free-path conventions and weak collisions, and
user stopping tables. The
first run walks through a complete file, and the
repository’s
examples/
directory has more.
This page is the input section of
docs/cli.md,
included here so there is one copy.
Units are in the key names: energies in eV (_ev), lengths in nm (_nm),
angles in degrees (_deg), densities in g/cm³. Every table rejects unknown
keys. The schema types are lindhard::input (shared with future front ends).
[beam]
| Key | Default | Meaning |
|---|---|---|
ion | required | Element symbol of the projectile (case-sensitive, "As") |
mass_amu | standard atomic weight | Projectile mass, u |
energy_ev | required | Incident energy, eV |
tilt_deg | 0 | Polar angle from the surface normal, [0, 90) |
azimuth_deg | 0 | Azimuth of the incidence plane |
[materials.<name>]
Named materials, in the form of lindhard::material::MaterialSpec:
[materials.SiO2]
density_g_cm3 = 2.2 # required for compounds
elements = [
{ symbol = "Si", atom_fraction = 1.0 },
{ symbol = "O", atom_fraction = 2.0, e_d_ev = 20.0, e_s_ev = 2.0 },
]
Each element takes symbol or z, atom_fraction or mass_fraction
(relative weights, normalised), and optional e_d_ev, e_b_ev, e_s_ev.
Defaults for the energies come from the element table where one is
tabulated; see lindhard/src/material.rs.
[target]
[target]
substrate = "Si" # optional semi-infinite substrate
[[target.layers]] # finite layers, front to back
material = "SiO2"
thickness_nm = 10.0
A material (in a layer or as the substrate) is either a key of
[materials], an element symbol (the pure element at its tabulated density),
or an inline material table with the same keys as [materials.<name>]. A
[materials] key wins over an element symbol of the same name. Without a
substrate the target has a back face and particles can be transmitted.
[physics]
| Key | Default | Choices |
|---|---|---|
potential | "zbl" | zbl, kr-c, moliere, lenz-jensen |
screening_length | paired with the potential | universal, firsov, lindhard |
stopping | "lindhard-scharff" | lindhard-scharff, bethe-bloch, equipartition-ls-or |
free_path | "constant" | constant, energy-dependent |
min_cm_angle_deg | none | Required with, and only with, energy-dependent |
weak_collisions | 0 | 0 to 3: weak collisions beyond p_max per collision step (Moller and Eckstein, IPP 9/64 (1988)); constant free path only. See the ion::bca docs, “Weak collisions” |
primary_cutoff_ev | required | The primary stops below this energy |
recoil_cutoff_ev | required | Recoils stop below this; keep it below the smallest E_s |
follow_recoils | true | Full cascades |
primary_surface_binding_ev | 0 | Surface barrier for the beam species |
tuning | "none" | Opt-in phenomenological calibration: the name of a versioned factor set (see below) |
[physics.energies.<symbol>] sets e_d_ev, e_b_ev and/or e_s_ev for that
element in every layer that contains it, after (so overriding) the
material’s own values. An element with no tabulated default and no value set
is an error naming the layer and the key to set.
tuning (phenomenological calibration, not a published model choice).
"none" or omission leaves the physics and the echoed input exactly as
without the key. A named set scales the surface binding energy E_s by a
per-element factor fitted to measured data. Rules: the factor multiplies the
resolved E_s (an explicit [physics.energies] or material value if given,
else the elemental default), once per layer, after overrides; the global
element table and the collision algorithm are untouched. The pilot supports
static ion runs on single-element layers only, with a beam species the set
was fitted for and target elements the set lists; compounds, [dynamic]
targets, other beams, unlisted elements and unknown set names are rejected
with a physics.tuning error. A beam energy outside the set’s fitted range,
or a tilted beam, runs with a warning (an extrapolation). summary.json then
has physics.tuning with the set, its version and provenance, and per layer
the original E_s, the factor and the effective E_s. Tuned results must be
reported next to, never in place of, untuned ones.
Shipped sets (fit record and held-out scores: docs/data-provenance.md,
“Tuning factor sets”; a new version ships under a new name):
| Set | Beam | Elements | Fitted energies | Fitted under |
|---|---|---|---|---|
es-sputter-ar-v1 | Ar | Si, Cu, Ag, Au | 196 to 10020 eV, normal incidence | the matched level-3 sputter settings (docs/validation.md, section 3) |
The factors are a calibration of yields under those settings; other settings
(potential, E_d, cutoffs, weak collisions) were not part of the fit, and
the set does not claim better accuracy for them.
[stopping]
Optional. Supplies user stopping tables for the electronic stopping of particular (ion, target element) pairs.
[stopping]
tables = ["tables/b_in_si.toml", "tables/p_in_si.toml"]
| Key | Default | Meaning |
|---|---|---|
tables | none | Paths of table files, one per pair |
Absent, the input means what it always meant (format.version is unchanged
and nothing is echoed).
Table file. The lindhard::ion::stopping::table::StoppingTable format:
provenance = "Author, Journal vol, page (year), Table N" # required
ion_z = 5
ion_mass_amu = 11.0093 # optional; default: standard atomic weight
target_z = 14
energy_ev = [1.0e2, 1.0e3, 1.0e4] # strictly increasing, eV
stopping_ev_1e15_cm2 = [10.0, 30.0, 60.0] # eV 1e-15 cm^2 per atom
Interpolation is piecewise linear in ln S versus ln E. provenance is
mandatory (data without an origin is not admitted). Do not use SRIM- or
ICRU-derived tables in anything committed to a repository; a table is the
user’s own data and its terms are the user’s concern.
Paths. A relative path resolves against the directory of the input file (not the current directory). The echoed input keeps the path as written.
Composition with [physics] stopping. A table replaces the
[physics] stopping model for exactly the pair it declares (ion_z,
target_z), including recoils of that species when follow_recoils is on.
Every other pair uses the [physics] stopping model. A pair with a table is
never silently served by the model: a query outside the table’s energy range,
or for a different ion mass, is an error that stops the run. Nothing is
extrapolated. Declare each pair once. A table for a pair that cannot occur
in the run (including a table for a target element’s recoils when
follow_recoils = false) is accepted with a warning that it is unused.
Recoil species. Tables are keyed by (ion_z, target_z) only, so with
follow_recoils = true a table whose ion_z is a target element also serves
every recoil of that element. Recoils carry the standard atomic weight and are
followed down to physics.recoil_cutoff_ev, so such a table is checked up
front against both: its ion_mass_amu must be the standard weight, and it
must start at or below recoil_cutoff_ev; either failure is an error. A
consequence is that an isotopic self-ion beam (e.g. beam.mass_amu = 27.9769
for Si into Si) cannot take a table for its own pair while recoils are
followed: drop the table, use the standard weight, or set
follow_recoils = false. A recoil-species table that ends below the largest
energy the beam can transfer to that element warns. Tables cannot be combined with
stopping = "equipartition-ls-or" (that mode carries its own
Lindhard-Scharff/Oen-Robinson loss and would ignore them).
Errors name the field (stopping.tables[0]): an unknown key in
[stopping], a missing or unreadable file, invalid table contents (including
a missing provenance), a duplicate pair, a table whose ion mass differs from
the beam ion’s, a table whose range does not contain the beam energy, and the
recoil-species checks above. A
table that starts above physics.primary_cutoff_ev warns, because the run
fails if a projectile slows below it.
Provenance in the output. Tables are user data, so the run records them.
summary.json has physics.stopping_tables, one entry per table: path (as
written), resolved_path (absolute where possible), sha256 of the file’s
bytes, the table’s provenance string, ion_z, ion_mass_amu, target_z
and the energy range. physics.models lists each as user-table with the path
and provenance as its source. The key is absent without [stopping].
[[crystal]] (optional)
Optional. Runs the named stack layers on an explicit cubic lattice (the
engine’s crystal flight model, Bca::with_crystal) instead of the amorphous
random-target model. Every layer not named stays amorphous; absent, the input
means what it always meant and nothing is echoed. One entry per crystal;
several entries give different lattices or orientations in different layers.
[beam]
ion = "B"
energy_ev = 5000.0
tilt_deg = 7.0 # the crystal's tilt
azimuth_deg = 22.0 # the crystal's twist, from `reference` towards n x r
[[crystal]]
layers = [0] # stack layer indices; the substrate is the last one
preset = "Si" # "Si", "Ge", "GaAs" or "3C-SiC"
normal = [0, 0, 1] # (hkl) of the surface; the inward normal
reference = [0, 1, 0] # [uvw] in the surface plane: the zero of the azimuth
wafer_rotation_deg = 0.0
[crystal.thermal] # optional; absent: a static lattice
temperature_k = 300.0
# debye_temperature_k = 640.0
| Key | Default | Meaning |
|---|---|---|
layers | required | Indices into the stack, front layer first, the substrate last (the index of physics.target). Each layer may belong to one crystal |
preset | required | Cubic lattice with a cited lattice constant (docs/crystal-orientation.md, docs/data-provenance.md) |
normal | required | Miller indices (hkl) of the surface plane (inward normal), integers, not all zero |
reference | required | Direction [uvw] in the surface plane (h u + k v + l w = 0). It is lab +y at zero wafer rotation; there is no hidden default zero for the azimuth |
wafer_rotation_deg | 0 | Rotation of the wafer about the surface normal |
thermal.temperature_k | none | Debye-model vibration at this temperature; sets the vibration amplitude only (no thermal expansion) |
thermal.debye_temperature_k | the preset’s cited value | Debye temperature |
p_max_nm, q_max_nm, search_length_nm | engine defaults | Search parameters (ion::bca::crystal, “Choice of the search parameters”): largest partner impact parameter (nearest-neighbour distance), simultaneous-collision window (5 % of it) and search segment length (lattice constant; speed only) |
Angles. beam.tilt_deg and beam.azimuth_deg are the engine’s tilt and
twist; there is no second beam direction. Tilt is the polar angle from the
inward surface normal, azimuth is measured in the surface plane from
reference towards n x reference; docs/crystal-orientation.md states the
full convention and a worked example.
Checks (lindhard check and run, before transport). Errors name the
key (crystal[i].normal, .reference, .layers, .preset,
.thermal.temperature_k, …): a zero or non-integer index triple, a
reference not in the surface plane, a layer that does not exist or is already
assigned, a preset whose elements are not in the layer’s material or whose
atom density differs from the lattice’s by more than 5 %, and a non-positive
search length. Unsupported combinations are errors, never a silent amorphous
fallback: a [dynamic] target, an [electron] run, physics.free_path other
than "constant", physics.weak_collisions > 0, and physics.tuning (the
only set was fitted for amorphous runs). physics.stopping = "equipartition-ls-or" is allowed with a warning: its local Oen-Robinson loss
has constants that are not verified against the paper.
Output. summary.json gains physics.crystal, one object per crystal in
input order, exactly as the engine reports it (Bca::crystal_metadata):
regions, the lattice constant and its temperature, normal_hkl,
reference_uvw, tilt_rad, twist_rad, wafer_rotation_rad, the resolved
search parameters p_max_m, q_max_m, search_length_m (metres), thermal
(its input, the per-species RMS displacement, the search margin and the
sampling rule) and, when it applies, electronic_constants_unverified: true.
physics.models gains a crystal transport entry. An amorphous input gets
neither. format.version stays 1: the key is an addition (see “Compatibility
and extension”).
Scope and caveats. Static, layered ion targets with the cubic presets
only; hexagonal lattices, custom cells, beam divergence and dose-dependent
crystal damage are not exposed. This is an interface to the existing engine,
not a validation: the known deviations of its channeled ranges are tracked
separately (issues #225 and #250) and nothing here claims they are resolved.
Examples: b_5keV_si_crystal.toml and
as_50keV_sio2_on_si_crystal.toml
(an amorphous oxide over a crystal substrate); neither is a validated
prediction.
[dynamic] (optional)
Makes the run fluence-dependent: the target composition is updated as the
fluence builds up (sputter erosion, build-up of implanted atoms). Without the
table nothing changes: the run, its output files and its bytes are those of a
static run. The model, its conventions and its limits are in
lindhard::ion::dynamic and the book chapter on dynamic composition.
run.ions is the number of histories of the whole run and
fluence_cm2 the fluence they represent, so each ion stands for
fluence_cm2 / run.ions ions/cm². The ions are delivered in steps; after each
step the grid is updated from that step’s events (a recoil is subtracted where
it is created and added where it stops, a stopped beam ion is added, an
escaped atom is a loss; the substrate is an immutable reservoir) and relaxed.
Primary i of the run always uses the random stream (seed, i), whatever the
step sizes and thread count.
| Key | Default | Meaning |
|---|---|---|
fluence_cm2 | required | Total fluence of the run, ions/cm² |
ions_per_step | required | Ions per step; with max_change, the largest and first step |
max_change | absent (fixed steps) | Adaptive steps: largest relative composition change of a slab per step, the largest absolute change in atoms/m² of one element in one slab, divided by that slab’s atoms/m². A larger step is discarded and retried from the same first ion with fewer ions, which consumes no ions of the run; the step doubles again after a step below half the bound |
min_ions_per_step | 1 | Adaptive only: the smallest step. At this size a step is accepted whatever its change, and a removal beyond what a slab holds is capped at what it holds (clamped column). A fixed run that removes more than a slab holds fails, naming the slab: use smaller steps or max_change |
slab_nm | one slab per layer | Split each finite layer into equal slabs at most this thick; the composition is tracked per slab |
relaxation | "ideal-mixing" | How thickness follows inventory: "ideal-mixing" (additive atomic volumes) or "fixed-number-density" |
number_density_cm3 | none | Total atom density, atoms/cm³; required with "fixed-number-density" |
atomic_volume_nm3.<Sym> | elemental solid volume from the element table | Atomic volume, nm³/atom, per element (ideal mixing). Required for an element with no tabulated solid density (a gas) |
energies.<Sym> | [physics.energies.<Sym>], then element defaults | e_d_ev, e_b_ev, e_s_ev of an element that enters the target during the run (the beam species, for example). Elements already in a layer keep that layer’s energies |
erosion | false | Sputter erosion: sputtered atoms are removed from the front of the target (slab 0 first, then deeper slabs) instead of from the slab where they were displaced, and the surface recedes. Must be a boolean |
With erosion = false the front surface stays at x = 0: swelling moves the
interior interfaces and the back face of the slabs, not the front surface (the
surface_nm column is that fixed frame, always 0). With erosion = true the
lost thickness is removed from the front and the grid is re-anchored so the
current surface is again x = 0; surface_nm is then the cumulative recession
R in nm and a depth x in the output is x + R in the original frame. The
recession of a step is the volume of the removed atoms per area under the
chosen relaxation (sum Z removed_Z v_Z, or sum removed / n), and removal
equals the sputtered counts per element, so no atom is created or lost. If a
step sputters more of an element than the slabs hold, the excess is not
removed and the element is counted in the clamped column. With a substrate,
atoms sputtered from the substrate remove nothing from the slabs, so the
recession falls short of Y F / n once the film is thin.
dynamic_summary.json totals gain recession_nm only with erosion on. Depths
in the output are measured from the front surface of that step. A dynamic run
needs E_d for every element that can occur, including the beam species. The Python bindings run static inputs only.
[run]
| Key | Default | Meaning |
|---|---|---|
ions | required | Number of primary histories |
seed | required | Run seed |
threads | all cores | Worker threads; does not affect results and is not echoed |
[tally]
| Key | Default | Meaning |
|---|---|---|
depth_bin_nm | 1 | Bin width of the stopped-primary depth profile |
depth_bins | 1000 | Number of bins; the last also collects everything deeper |
per_ion | true | Write ions.csv |
lateral_bin_nm | 1 | Bin width of the lateral and radial profiles of stopped primaries |
lateral_bins | 100 | Bins per side of the beam axis: y and z span [-lateral_bins * lateral_bin_nm, +lateral_bins * lateral_bin_nm) nm, the radial distance [0, lateral_bins * lateral_bin_nm) nm |
escape_energy_max_ev | beam energy | Upper edge of the escape-energy spectra, eV (from 0) |
escape_energy_bins | 100 | Escape-energy bins |
escape_polar_bins | 30 | Polar-angle bins over [0, 90) degrees from the outward surface normal |
dual_pearson | false | Also fit a dual-Pearson profile to the depth histogram |
The depth grid (depth_bin_nm, depth_bins, from the front face) is shared by
the range histogram, the dual-Pearson fit and the defect profiles. Particles
outside any grid are counted in explicit underflow and overflow entries, never
dropped. Every count and per-ion value is for the same incident ions.
Electron runs ([electron])
An input with an [electron] table runs the low-energy electron engine
(lindhard::electron::transport) instead of the ion BCA, with the full
electron tally (lindhard::tally::FullElectronTally). It exposes what the
library does and adds no physics; every choice and every data provenance is
written to the output, so a result can be reproduced from its own header. The
schema types are lindhard::input::electron. [materials] and [target]
are the ion run’s tables (above); [beam], [physics], [stopping],
[tally] and [dynamic] are not accepted. Example:
../examples/electron/e_10keV_si.toml.
[electron.beam]
energy_ev = 10000.0
[electron.transport]
cutoff_ev = 1.0
cutoff_reference = "vacuum-level"
secondaries = "kieft-bosch"
boundary = "step-barrier"
[electron.elastic]
potential = "thomas-fermi-yukawa"
[electron.inelastic]
model = "penn-single-pole"
[electron.materials.Si]
optical_elf = "si_elf.toml"
band = { kind = "insulator", valence_band_width_ev = 10.0, band_gap_ev = 2.0, affinity_ev = 3.0, provenance = "..." }
[target]
substrate = "Si"
[run]
histories = 1000
seed = 1
[electron.beam]
| Key | Default | Meaning |
|---|---|---|
energy_ev | required | Kinetic energy of the primaries, eV: the vacuum energy with boundary = "step-barrier", the energy inside the first layer otherwise |
tilt_deg | 0 | Polar angle from the surface normal, [0, 90) |
azimuth_deg | 0 | Azimuth of the incidence plane |
Primaries start on the front face at y = z = 0 (just outside it with the
step barrier).
[electron.transport] (lindhard::electron::transport::TransportConfig)
| Key | Default | Choices |
|---|---|---|
cutoff_ev | required | An electron stops below this energy |
cutoff_reference | "band-bottom" | band-bottom; vacuum-level (the threshold is U + cutoff: electrons that can no longer leave are not followed) |
escape_rule | "both-faces" | both-faces; front-only (the back face absorbs) |
max_events | 10000000 | Collision and reflection cap per electron |
secondaries | "off" | off; kieft-bosch (Kieft and Bosch 2008) |
instantaneous_momentum, momentum_conservation | true | Options of kieft-bosch; an error with off |
boundary | "transparent" | transparent; step-barrier (inner-potential step with quantum transmission and refraction) |
quantum_transmission, refraction | true | Options of step-barrier; an error with transparent |
[electron.elastic]
| Key | Default | Choices |
|---|---|---|
model | "mott" | mott: Mott cross sections from radial-Dirac partial waves, independent-atom additivity (electron::elastic::table) |
potential | required | thomas-fermi-yukawa: the Thomas-Fermi Yukawa stand-in; salvat-dhfs: the Salvat et al. (1987) DHFS potentials (Table I coefficients, Z = 1..92; data-provenance.md) |
exchange | false | Furness-McCarthy exchange correction |
correlation_polarization | absent (off) | A table: polarizability.<Sym> = { bohr3 = ..., source = "..." } for every target element (the source is required), optional b_pol_squared (absent: Seltzer’s rule, which needs every table energy above 50 eV) and outer_radius_bohr (50) |
The corrections are solved per grid energy with the chosen potential’s own
Poisson density: the stand-in’s, or the DHFS density of Salvat et al. (1987)
Eq. (12) (AtomicElastic::compute_corrected); the elastic table’s model and
provenance strings name them and every polarizability with its source.
[electron.inelastic]
| Key | Default | Choices |
|---|---|---|
model | "penn-single-pole" | penn-single-pole, penn-full, mermin-melf (electron::inelastic::PennAlgorithm). The full Penn and Mermin models integrate numerically and build tables far more slowly. The single-pole model’s mean free path is much longer than the other two below about 30 eV (Al: up to 23 times), which inflates the secondary yield; see electron::inelastic::penn, “Low energies” (#173) |
fermi_energy_ev | 0 | Fermi energy of the model, eV. It is not the band’s: the transport reads table rows at the electron’s energy above the band bottom, so setting it to the band’s Fermi energy counts that energy twice; see electron::transport, “Energy reference of the inelastic table” (#173) |
[electron.tables]: one log-spaced energy grid shared by the elastic and
inelastic tables of every material.
| Key | Default | Meaning |
|---|---|---|
min_energy_ev | 10 | Lowest grid energy, eV |
max_energy_ev | the beam energy (plus the largest inner potential with the step barrier) | Highest grid energy, eV |
points_per_decade | 20 | Minimum points per decade |
The transport holds the rates of the first and last rows beyond the grid; a grid that starts above the lowest stopping threshold or ends below the largest possible energy warns.
[electron.materials.<name>]: the electron data of each material the
target uses, keyed by the name the target gives it (a [materials] key or an
element symbol; an inline target material is an error in an electron run).
Every name the target uses needs an entry; an unused entry warns.
| Key | Default | Meaning |
|---|---|---|
optical_elf | required | Path of an optical ELF file, relative to the input file’s directory, in the lindhard::electron::data::OpticalElf TOML form (material, provenance, energy_ev, elf). It is read with that type’s loader, so a file without a provenance is refused, as is any invalid table |
band | none | Band parameters, required with kieft-bosch, step-barrier or vacuum-level: { kind = "metal", fermi_ev, work_function_ev, provenance }, { kind = "insulator", valence_band_width_ev, band_gap_ev, affinity_ev, provenance } or { kind = "free-electron-metal", valence_electrons_per_atom, work_function_ev, provenance } (lindhard::electron::boundary::BandStructure; a blank provenance is refused) |
phonon | none (off) | Fröhlich LO-phonon channel, polar insulators only: { hbar_omega_ev, eps_static, eps_high_frequency, temperature_k, provenance }, or { preset = "sio2-63mev" | "sio2-153mev", temperature_k } (the library’s cited SiO₂ values) |
polaron | none (off) | Polaron trapping C exp(-γE): { c_per_nm, gamma_per_ev, provenance } |
No optical or band data of any real material is committed
(data-provenance.md); the data files are the user’s,
and their terms are the user’s concern. Subshell binding-energy tables
(SubshellBindingTable) have no key yet: the transport loop does not use
inner-shell channels, so there is nothing to feed them to (see “extending”
below).
[electron.tally] (lindhard::tally::ElectronTallyConfig)
| Key | Default | Meaning |
|---|---|---|
se_bse_split_ev | 50 | Escaping electrons below it are slow (secondary), at or above it fast (backscattered) |
escape_energy_max_ev | beam energy | Upper edge of the escape-energy spectra (from 0), eV |
escape_energy_bins | 100 | Escape-energy bins |
escape_polar_max_deg | 90 | Upper edge of the polar-angle spectra (from 0, at most 180), degrees from the outward normal |
escape_polar_bins | 18 | Polar-angle bins |
cartesian | none | Deposition grid { x, y, z }, each { lo_nm, hi_nm, bins } (x is depth) |
cylindrical | none | Deposition grid { r, depth } about the beam axis, each { lo_nm, hi_nm, bins } (r.lo_nm >= 0) |
[run] of an electron run: histories (required), seed (required) and
threads (all cores; not echoed, never changes results).