Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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]

KeyDefaultMeaning
ionrequiredElement symbol of the projectile (case-sensitive, "As")
mass_amustandard atomic weightProjectile mass, u
energy_evrequiredIncident energy, eV
tilt_deg0Polar angle from the surface normal, [0, 90)
azimuth_deg0Azimuth 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]

KeyDefaultChoices
potential"zbl"zbl, kr-c, moliere, lenz-jensen
screening_lengthpaired with the potentialuniversal, firsov, lindhard
stopping"lindhard-scharff"lindhard-scharff, bethe-bloch, equipartition-ls-or
free_path"constant"constant, energy-dependent
min_cm_angle_degnoneRequired with, and only with, energy-dependent
weak_collisions00 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_evrequiredThe primary stops below this energy
recoil_cutoff_evrequiredRecoils stop below this; keep it below the smallest E_s
follow_recoilstrueFull cascades
primary_surface_binding_ev0Surface 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):

SetBeamElementsFitted energiesFitted under
es-sputter-ar-v1ArSi, Cu, Ag, Au196 to 10020 eV, normal incidencethe 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"]
KeyDefaultMeaning
tablesnonePaths 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
KeyDefaultMeaning
layersrequiredIndices into the stack, front layer first, the substrate last (the index of physics.target). Each layer may belong to one crystal
presetrequiredCubic lattice with a cited lattice constant (docs/crystal-orientation.md, docs/data-provenance.md)
normalrequiredMiller indices (hkl) of the surface plane (inward normal), integers, not all zero
referencerequiredDirection [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_deg0Rotation of the wafer about the surface normal
thermal.temperature_knoneDebye-model vibration at this temperature; sets the vibration amplitude only (no thermal expansion)
thermal.debye_temperature_kthe preset’s cited valueDebye temperature
p_max_nm, q_max_nm, search_length_nmengine defaultsSearch 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.

KeyDefaultMeaning
fluence_cm2requiredTotal fluence of the run, ions/cm²
ions_per_steprequiredIons per step; with max_change, the largest and first step
max_changeabsent (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_step1Adaptive 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_nmone slab per layerSplit 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_cm3noneTotal atom density, atoms/cm³; required with "fixed-number-density"
atomic_volume_nm3.<Sym>elemental solid volume from the element tableAtomic 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 defaultse_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
erosionfalseSputter 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]

KeyDefaultMeaning
ionsrequiredNumber of primary histories
seedrequiredRun seed
threadsall coresWorker threads; does not affect results and is not echoed

[tally]

KeyDefaultMeaning
depth_bin_nm1Bin width of the stopped-primary depth profile
depth_bins1000Number of bins; the last also collects everything deeper
per_iontrueWrite ions.csv
lateral_bin_nm1Bin width of the lateral and radial profiles of stopped primaries
lateral_bins100Bins 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_evbeam energyUpper edge of the escape-energy spectra, eV (from 0)
escape_energy_bins100Escape-energy bins
escape_polar_bins30Polar-angle bins over [0, 90) degrees from the outward surface normal
dual_pearsonfalseAlso 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]

KeyDefaultMeaning
energy_evrequiredKinetic energy of the primaries, eV: the vacuum energy with boundary = "step-barrier", the energy inside the first layer otherwise
tilt_deg0Polar angle from the surface normal, [0, 90)
azimuth_deg0Azimuth 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)

KeyDefaultChoices
cutoff_evrequiredAn 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_events10000000Collision and reflection cap per electron
secondaries"off"off; kieft-bosch (Kieft and Bosch 2008)
instantaneous_momentum, momentum_conservationtrueOptions of kieft-bosch; an error with off
boundary"transparent"transparent; step-barrier (inner-potential step with quantum transmission and refraction)
quantum_transmission, refractiontrueOptions of step-barrier; an error with transparent

[electron.elastic]

KeyDefaultChoices
model"mott"mott: Mott cross sections from radial-Dirac partial waves, independent-atom additivity (electron::elastic::table)
potentialrequiredthomas-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)
exchangefalseFurness-McCarthy exchange correction
correlation_polarizationabsent (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]

KeyDefaultChoices
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_ev0Fermi 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.

KeyDefaultMeaning
min_energy_ev10Lowest grid energy, eV
max_energy_evthe beam energy (plus the largest inner potential with the step barrier)Highest grid energy, eV
points_per_decade20Minimum 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.

KeyDefaultMeaning
optical_elfrequiredPath 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
bandnoneBand 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)
phononnone (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)
polaronnone (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)

KeyDefaultMeaning
se_bse_split_ev50Escaping electrons below it are slow (secondary), at or above it fast (backscattered)
escape_energy_max_evbeam energyUpper edge of the escape-energy spectra (from 0), eV
escape_energy_bins100Escape-energy bins
escape_polar_max_deg90Upper edge of the polar-angle spectra (from 0, at most 180), degrees from the outward normal
escape_polar_bins18Polar-angle bins
cartesiannoneDeposition grid { x, y, z }, each { lo_nm, hi_nm, bins } (x is depth)
cylindricalnoneDeposition 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).