First run: 5 keV B into Si
This walkthrough runs the example
examples/b_5keV_si.toml:
10 000 boron ions of 5 keV into amorphous silicon, 7° off the surface
normal. Every block of command output below is taken from an actual run of
that file: the blocks are generated by validation/book_walkthrough.py, and
CI reruns the example and fails if they no longer match. Run the commands
from the root of the repository, with lindhard built as in
Installing (or replace lindhard with
cargo run --release -p lindhard-cli --).
1. The input
# 5 keV boron into amorphous silicon, 7 degrees off normal.
#
# Run: lindhard run examples/b_5keV_si.toml --out out/b_5keV_si
# Check: lindhard check examples/b_5keV_si.toml
#
# Schema: docs/cli.md. Energies are in eV, lengths in nm, angles in degrees.
[beam]
ion = "B"
energy_ev = 5000.0
tilt_deg = 7.0
[target]
# An element symbol names the pure element at its tabulated density
# (lindhard/src/elements.rs).
substrate = "Si"
[physics]
potential = "zbl"
stopping = "lindhard-scharff"
free_path = "constant"
primary_cutoff_ev = 5.0
recoil_cutoff_ev = 2.0
# Si has no default displacement energy in the element table, so the input
# must choose one. 15 eV is an illustrative model parameter, not a
# recommendation: choose E_d for your problem.
[physics.energies.Si]
e_d_ev = 15.0
[run]
ions = 10000
seed = 1
[tally]
depth_bin_nm = 0.5
depth_bins = 200
# Fit a dual-Pearson profile to the depth histogram (off by default).
dual_pearson = true
Section by section:
[beam]: the projectile by element symbol (its mass defaults to the standard atomic weight), its energy in eV and its tilt from the surface normal in degrees.[target]:substrate = "Si"names pure silicon at its tabulated density as a semi-infinite substrate. There are no finite layers, so nothing can be transmitted.[physics]: the model choices, spelled out even where they are the defaults: the ZBL universal potential, Lindhard-Scharff electronic stopping, and the constant free path. The two cutoffs are required: the beam ion is dropped below 5 eV, recoils below 2 eV (kept below the surface binding energy of Si, so that sputtering is not cut off).[physics.energies.Si]: the displacement energy \( E_d \) of Si. The element table has no default for Si, so the input must choose one; 15 eV here is an illustrative value, not a recommendation. Si’s surface binding energy \( E_s \) comes from the element table and its lattice binding \( E_b \) defaults to 0.[run]: the number of histories and the seed. Together with the input and the binary, the seed fixes every bit of the output.[tally]: a depth grid of 200 bins of 0.5 nm, and a dual-Pearson fit of the depth profile, which is off by default.
The full list of keys is in the TOML input reference.
2. Check the input
lindhard check examples/b_5keV_si.toml
examples/b_5keV_si.toml: OK
beam: B at 5000 eV, tilt 7 deg, azimuth 0 deg; 10000 ions, seed 1
layer 0: Si (Si 1.0000), semi-infinite, 4.9940e22 atoms/cm^3
transport: amorphous-bca
screening function: zbl-universal
screening length: universal
scattering angle: gauss-mehler-quadrature-table
electronic stopping: lindhard-scharff
compound stopping: bragg-additivity
free path: constant
displacement criterion: e_d-e_b
surface barrier: planar
random numbers: chacha8-per-history-stream
check parses and validates the input without transporting anything. It
prints the resolved beam and target (the atom density comes from the
tabulated mass density of Si) and every model the run will use. An invalid
input exits non-zero with a message naming the offending key, for example
target.layers[0].thickness_nm: -5 nm must be finite and positive.
3. Run it
lindhard run examples/b_5keV_si.toml --out out/b_5keV_si
10000 ions: 9441 stopped, 559 backscattered, 0 transmitted, 2534 sputtered atoms; wrote out/b_5keV_si
The one-line summary counts the fates of the 10 000 primaries and the
target atoms that left through the front face. The run takes a few seconds
in a release build. Add --threads N to choose the number of worker
threads; the results are bit-identical whatever N is.
The output directory holds:
damage_profile.csv
depth_profile.csv
escape_spectra.csv
ions.csv
lateral_profile.csv
summary.json
4. Read the summary
summary.json starts with the format and software version, then the input
exactly as it was run (defaults filled in, so the run can be reproduced from
its own header), then the physics and the results. A few parts of it follow;
the Output files page describes every key.
The models used. physics.models names every model with its citation
(the citations are omitted here):
{
"models": [
{
"role": "transport",
"name": "amorphous-bca"
},
{
"role": "screening function",
"name": "zbl-universal"
},
{
"role": "screening length",
"name": "universal"
},
{
"role": "scattering angle",
"name": "gauss-mehler-quadrature-table"
},
{
"role": "electronic stopping",
"name": "lindhard-scharff"
},
{
"role": "compound stopping",
"name": "bragg-additivity"
},
{
"role": "free path",
"name": "constant"
},
{
"role": "displacement criterion",
"name": "e_d-e_b"
},
{
"role": "surface barrier",
"name": "planar"
},
{
"role": "random numbers",
"name": "chacha8-per-history-stream"
}
]
}
Where the primaries went. results.primaries:
{
"primaries": {
"stopped": 9441,
"backscattered": 559,
"transmitted": 0,
"stopped_depth_mean_nm": 24.389957255951394,
"stopped_depth_std_nm": 12.728934527954353
}
}
A few percent of the boron ions backscatter; the rest stop in the silicon.
The range distribution. results.range has the moments of the depth at
which the stopped primaries came to rest, with their standard errors.
mean_nm is the projected range \( R_p \) and std_dev_nm the straggle
\( \Delta R_p \); the kurtosis is 3 for a Gaussian.
{
"depth": {
"n": 9441,
"mean_nm": 24.389957255951394,
"std_dev_nm": 12.728934527954353,
"skewness": 0.3718620249214205,
"kurtosis": 2.713342344381711,
"mean_std_err_nm": 0.1310035467755083,
"std_dev_std_err_nm": 0.08573835216188522,
"skewness_std_err": 0.02024523266795846,
"kurtosis_std_err": 0.04670779974658438
},
"pearson_iv": null,
"pearson_iv_error": "moments outside the Pearson IV region: skewness = 0.3718620249214205, kurtosis = 2.713342344381711 (needs kurtosis > Some(3.2610741629133053))"
}
When the moments lie outside the region of the Pearson IV family, as they
do here, pearson_iv is null and pearson_iv_error gives the reason
instead of a silent fallback. The dual-Pearson fit requested in
[tally] (results.range.dual_pearson, shortened here) splits the profile
into a near-surface head and a tail:
{
"head_fraction": 0.19614973489188495,
"head": {
"mean_nm": 14.200851032315631,
"std_dev_nm": 10.282737064212109
},
"tail": {
"mean_nm": 26.299554956251598,
"std_dev_nm": 12.449831675138565
},
"chi_square": 142.04173850533618
}
docs/validation.md
(level 3) compares the projected range of this problem with a SIMS
measurement of B in amorphous Si. With these default models the computed
range runs long, and the page discusses why.
Damage. results.damage.per_ion puts the two kinds of damage figure
side by side (Displacement damage): the NRT and
Kinchin-Pease estimates from the damage energy of the primary knock-on
atoms, and the vacancies, interstitials and replacements counted in the
simulated cascades. They are different quantities and are not expected to
agree.
{
"per_ion": {
"pka": 21.3841,
"pka_energy_ev": 3375.943722511579,
"damage_energy_ev": 2727.1474995691347,
"nrt_displacements": 75.1806883174286,
"kinchin_pease_displacements": 92.1413018627793,
"displacements": 124.9028,
"replacements": 8.3302,
"vacancies": 116.5726,
"interstitials": 116.3192
}
}
Sputtering and backscatter.
{
"sputtering": {
"yield_per_ion": 0.2534
},
"escapes": {
"backscatter_coefficient": 0.0559,
"transmission_coefficient": 0.0,
"energy_reflection_coefficient": 0.009828116927787605
}
}
Where the energy went. Every history’s energy is accounted for, per
ion: electronic loss, energy left in the lattice (sub-threshold transfers;
\( E_b = 0 \) here), work against the surface barrier, energy carried out
by backscattered ions and sputtered atoms, and the kinetic energy of
particles when they fell below their cutoff (rest). The largest relative
bookkeeping residual of any history is at the level of rounding.
{
"energy_budget_ev_per_ion": {
"incident": 5000.0,
"electronic_nonlocal": 2172.168730014593,
"electronic_local": 0.0,
"lattice": 2628.475261767412,
"surface_barrier": 1.1732420000000003,
"backscattered": 49.14058463893802,
"sputtered": 14.90822822514642,
"transmitted": 0.0,
"rest": 134.13395335390996,
"max_relative_residual": 3.819877747446298e-15
}
}
5. The profiles
The CSV files hold the profiles on the grids set in [tally]. The first
rows of depth_profile.csv, the stopped primaries per 0.5 nm bin and that
count per incident ion per nm:
depth_lo_nm,depth_hi_nm,stopped_primaries,fraction_per_nm
0.0,0.5,13,0.0026
0.5,1.0,31,0.0062
1.0,1.5,38,0.0076
1.5,2.0,36,0.0072
2.0,2.5,50,0.01
2.5,3.0,46,0.0092
3.0,3.5,68,0.0136
The last row has an upper edge of inf and collects everything deeper than
the grid, so nothing is dropped. damage_profile.csv gives the cascade
defects on the same grid, lateral_profile.csv the lateral and radial
spread of the stopped primaries, escape_spectra.csv the energy and angle
spectra of everything that left the target, and ions.csv the final state
of every primary. Their columns are described in Output files.
6. Change something
Some edits to try, each a one-line change to the input:
potential = "kr-c"andscreening_length = "lindhard": another potential and screening length (Interatomic potentials).stopping = "equipartition-ls-or": half of the electronic loss taken locally at the collisions (Oen-Robinson);electronic_localin the energy budget becomes non-zero.weak_collisions = 3under[physics]: weak collisions beyond \( p_\mathrm{max} \) (BCA transport).- A finite layer in front of the substrate, as in
examples/as_50keV_si_sio2.toml.
lindhard check shows the effect of each edit on the model list before you
spend time on a run.