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

Output files

lindhard run input.toml --out dir/ writes summary.json and up to five CSV profiles into dir/. The first run shows excerpts of each from a real run. The models behind the damage figures are on Displacement damage.

This page is the output section of docs/cli.md, included here so there is one copy.

summary.json

KeyContent
format{"name": "lindhard-summary", "version": 1}
softwareCrate version and git_describe of the binary
inputThe input as run: defaults filled in, CLI overrides applied, run.threads removed. Deserializes back to the same lindhard::input::Input, so a run can be reproduced from its own header
physics.modelsEvery model in use: role, name, citation
physics.crystalOnly with [[crystal]]: the engine’s metadata of each crystal (see [[crystal]])
physics.stopping_tablesOnly with [stopping]: path, SHA-256, provenance and range of each user table (see [stopping])
physics.engineCutoffs, free path, weak collisions, electronic-loss mode, seed, chunk size as passed to the engine
physics.scattering_tableAngle-table grid and its measured interpolation error
physics.targetEach layer: extent (nm; back_nm is null for a substrate), atom density, and the fully resolved material (every E_d, E_b, E_s)
results.historiesPrimaries run
results.primariesstopped, backscattered, transmitted; mean and standard deviation of the stopped-primary depth, nm (stopped_depth_mean_nm, stopped_depth_std_nm; the same numbers as results.range.depth.mean_nm and std_dev_nm, the projected range and straggle, kept under their original keys)
results.recoilsAtoms displaced, sputtered (left through the front face), transmitted
results.yieldsThe above per incident ion
results.energy_budget_ev_per_ionWhere the incident energy went, per ion, and the largest per-history relative bookkeeping residual
results.rangeWhere the beam particles came to rest, lengths in nm. depth: n, mean_nm (projected range Rp), std_dev_nm (straggle), skewness, kurtosis (beta, Gaussian 3) and the standard error of each (null below two stopped primaries). pearson_iv: the Pearson IV density with those moments (m, nu, a_nm, lambda_nm), or null with the reason in pearson_iv_error. dual_pearson (or dual_pearson_error): only with tally.dual_pearson = true; head fraction, head and tail components, chi-square of the fit and of the single Pearson IV. lateral_y, lateral_z, radial: moments of the lateral positions. layers: stopped and depth moments by the layer where the particle stopped
results.damagenrt: Norgett-Robinson-Torrens and Kinchin-Pease displacement estimates from the primary knock-on atom damage energies (pka_count, pka_energy_ev, damage_energy_ev, nrt_displacements, kinchin_pease_displacements). cascade: defects counted event by event in the simulated cascades (displacements, replacements, vacancies, interstitials). The two are different quantities and are reported separately; see lindhard::ion::damage for the conventions, including the approximation used for compounds. Totals over all ions, with per_ion and a layers breakdown
results.sputteringyield_per_ion and by_element: target atoms leaving the front face, with count, per_ion and mean_energy_ev for each element
results.escapesbackscatter_coefficient, transmission_coefficient, energy_reflection_coefficient (energy carried out of the front face by the beam particles, as a fraction of the incident energy), and species: every species (beam first) through the front and back face, with count, per_ion and mean_energy_ev
filesNames of the other files written (null if not written)
runthreads, table_build_s, transport_s, ions_per_s

depth_profile.csv

depth_lo_nm,depth_hi_nm,stopped_primaries,fraction_per_nm: stopped primaries per depth bin, and that count per incident ion per nm. The last row’s upper edge is inf (overflow) and its density is empty.

lateral_profile.csv

quantity,lo_nm,hi_nm,count,per_ion_per_nm: stopped primaries by y, z and radial distance (quantity is y, z or radial), for the grid set by lateral_bin_nm and lateral_bins. Each quantity ends with an underflow row (lo_nm = -inf) and an overflow row (hi_nm = inf) with empty density.

damage_profile.csv

depth_lo_nm,depth_hi_nm,vacancies,interstitials,replacements, then the same three per incident ion per nm: the cascade defect counts by depth, on the depth grid. vacancies is displacements minus replacements. The last row, with depth_hi_nm = inf, holds everything deeper than the grid (empty densities). The totals equal results.damage.cascade.

escape_spectra.csv

species_z,symbol,beam,face,spectrum,lo,hi,count,per_ion_per_unit: the energy (spectrum = energy_ev, lo/hi in eV) and polar-angle (polar_deg, degrees from the outward normal) spectra of every species leaving each face (front or back). Each spectrum ends with -inf and inf rows for entries outside the grid, with empty densities. The density is per incident ion per eV or per degree.

ions.csv

index,fate,x_nm,y_nm,z_nm,energy_ev,dir_x,dir_y,dir_z,layer: the final state of every primary, by history index. fate is stopped, backscattered or transmitted; for escaped primaries the position is on the face and the energy and direction are outside the target. x is depth.

Dynamic runs ([dynamic])

A run with a [dynamic] table writes dynamic_summary.json, dynamic_steps.csv and dynamic_composition.csv instead of the static files.

dynamic_summary.json (format lindhard-dynamic-summary): the echoed input (with dynamic), physics-model list models, the species, totals (ions, steps, rejected attempts, fluence, slab count and thickness before and after, yields per ion), files and the timing run object.

dynamic_steps.csv: one row per accepted step; step 0 is the initial target. step, first_index (global index of the step’s first ion), ions, ions_done, fluence_cm2 (delivered so far), attempts (more than 1 if the adaptive bound rejected the step), max_change, clamped, removed_slabs (slabs that emptied), n_slabs, surface_nm (cumulative surface recession in nm; always 0 with erosion = false), thickness_nm (total of the finite slabs), then cumulative counts since the start: cum_backscattered, cum_transmitted, cum_stopped_in_target, cum_stopped_in_substrate, cum_sputtered, sputter_yield (atoms per ion so far), cum_recoils_transmitted and cum_sputtered_<Sym> per element.

dynamic_composition.csv: the slab profile after every step (step 0 is the initial target), one row per step and slab: step, slab (0 is the front), front_nm, back_nm, thickness_nm, then per element atoms_per_cm2_<Sym> and fraction_<Sym> (atom fraction). Slabs that emptied are gone from later steps.

Electron runs: electron_summary.json and electron_*.csv

An electron run writes these instead of the ion files.

electron_summary.json (format {"name": "lindhard-electron-summary", "version": 1}):

KeyContent
softwareAs for an ion run
inputThe input as run: defaults filled in (including tables.max_energy_ev, tally.escape_energy_max_ev and the secondary and barrier options), CLI overrides applied, run.threads removed. Deserializes to lindhard::input::electron::ElectronInput
physics.modelsEvery model in use: role, name, citation (transport loop, elastic model and potential, corrections, inelastic model, secondaries, barrier, phonon and polaron channels, SE/BSE split)
physics.transportThe engine’s RunMetadata: cutoff and its reference, escape rule, event cap, secondary and boundary models, seed, histories, chunk size, the primary, and per layer its extent (m), the model and provenance strings of both tables, the band parameters, phonon and polaron channels with their provenance
physics.targetEach layer: extent (nm), atom density and the resolved material
physics.materialsEach material: the ELF file (path, resolved_path, sha256, its material and provenance, energy range and point count), band, phonon, polaron, and for elastic_table and inelastic_table their model, material, provenance, cache format_version, energy range and grid sizes, source ("built" or "cache") and cache (null without --table-cache, else the table file’s path, sha256 and key_sha256)
resultsThe ElectronReport (lindhard::tally::ElectronReport), lengths in m and energies in eV, summed over all histories unless named per primary: histories, metadata (split and its source, cutoff, stopping thresholds, tally settings), fates of the primaries, event_caps (see below), budget (the energy balance and its relative_imbalance; deposits are measured from the band bottom, so with secondaries in a layer with a Fermi energy deposited_ev includes the Fermi-sea energy of liberated conduction electrons and can exceed the energy imparted, which is incident_ev - escaped_ev = deposited_ev + trapped_ev + barrier_ev - fermi_sea_ev - phonon_absorbed_ev), yields (backscatter_eta, secondary_delta, total_sigma, transmitted), front and back (counts, energies, slow and fast classes), deposition (per_layer_ev; for each grid its binning, inside_ev and outside_ev), generation_volume, stopping_points (all electrons that fell below the stopping threshold, and under primaries the primaries alone: the penetration depth of stopped primaries), table_coverage (see below). The histograms and grid cells are in the CSV files, not here
filesNames of the CSV files (null if not written)
runthreads, table_build_s, transport_s, histories_per_s

results.table_coverage is a numerical diagnostic: one entry per layer (layer), and for its elastic and inelastic table the grid bounds energy_min_ev and energy_max_ev and the counts below (E < energy_min_ev: the first row’s rate and distribution were used), within (both bounds included: interpolated, or a row read exactly) and above (E > energy_max_ev: the last row’s were used), over primaries and secondaries. They count rate evaluations, not collisions: the transport evaluates both tables of the electron’s layer once before every free flight, including flights cut short at a layer face, the flight after a face reflection and flights with zero total rate, so the totals exceed the number of elastic and inelastic events, and a layer’s elastic and inelastic totals are equal. They are not fractions of path length or of deposited energy either. Nonzero below or above counts say that part of the transport used the constant continuation of a table beyond its grid ([electron.tables]); they do not say how much that changed the result. Summaries written before the key existed lack it.

results.event_caps is another numerical diagnostic, of the collision cap ([electron.transport] max_events): secondary_tracks is the number of secondary electrons the cap cut off (one per capped track) and affected_histories the number of primary histories in which the primary or at least one secondary was cut off (each history counted once, however many of its electrons were capped). Both are counts, not energies; the energy the capped electrons still carried is budget.event_cap_ev. The primaries’ own caps stay in fates.event_capped, so fates.event_capped = 0 alone does not show that the secondary cascades ran to completion: check event_caps.affected_histories. A capped track is a truncated one, not a physical fate. Summaries written before the key existed read back with both counts zero.

electron_escape_spectra.csv: face,spectrum,class,lo,hi,count,per_primary_per_unit. For each face (front, back): the energy spectrum of all escaping electrons (spectrum = energy_ev, class = all, eV) and the polar-angle spectrum of each class (polar_deg, slow or fast, degrees from the outward normal). Each spectrum ends with -inf and inf rows for entries outside the grid, with empty densities. The density is per primary per eV or per degree.

electron_deposition_cylindrical.csv (with tally.cylindrical): ir,ix,r_lo_nm,r_hi_nm,depth_lo_nm,depth_hi_nm,energy_ev,ev_per_primary_per_nm3, one row per cell. electron_deposition_cartesian.csv (with tally.cartesian): ix,iy,iz,x_lo_nm,x_hi_nm,y_lo_nm,y_hi_nm,z_lo_nm,z_hi_nm,energy_ev,ev_per_primary_per_nm3. Energy deposited outside a grid is outside_ev in the summary. Like budget.deposited_ev, the cell energies are measured from the band bottom and, with secondaries, include Fermi-sea energy the beam did not supply.

electron_tables.csv: material,energy_ev,elastic_inverse_mfp_per_nm,inelastic_inverse_mfp_per_nm,inelastic_mean_loss_ev,inelastic_stopping_ev_per_nm, the tables the run used, per material and grid energy (the stopping power is λ⁻¹ ⟨W⟩ of the stored loss distribution).

Cross-section table cache (--table-cache)

Building the elastic and inelastic tables is the slow part of a short electron run (tens of seconds with penn-single-pole, far longer with penn-full; see docs/validation.md, “Electron oracles”). With --table-cache DIR, lindhard run looks each table up in DIR (created if missing) and builds and stores only the ones it does not find, so a series of runs that differ only in seed, history count, tallies or transport settings builds its tables once.

Each entry is two files, named by the SHA-256 of a key document: <kind>-<sha256>.toml, the table in the versioned cache form of lindhard::electron::data::CrossSectionTable, read back with that type’s loader, and <kind>-<sha256>.key.json, the key itself. The key spells out every input the table depends on:

  • the key schema version and the table cache format_version;
  • the build: crate version, git describe --always --dirty, and the SHA-256 of the running lindhard executable, so any rebuild that changes the code (an uncommitted edit included) misses, and a rebuilt binary never reuses an older binary’s table;
  • the table kind and the exact energy grid (electron.tables, after the defaults are filled in);
  • the material’s name, composition and density;
  • elastic: the potential, the exchange and correlation-polarization corrections with all their inputs, the starting probability grid and the refinement tolerance;
  • inelastic: the model (electron.inelastic.model), its Fermi energy, the SHA-256 and provenance of the optical ELF file, and the material’s band parameters.

Every f64 is written in shortest round-trip form, so a change in the last bit of any number is a different key. A lookup must find the stored key equal, byte for byte, to the run’s own (a mismatch under the same hash means the file was edited, and is an error naming the differing field); the table file is then loaded and validated by the library, and its axis and energy grid are checked against the run. A file found under the run’s key that fails any of these checks is an error, never a silent rebuild. A table of another cache format_version can never be found, since the version is part of the key. Writes go to a temporary file renamed into place, the table before its key.

A cached table is the built one bit for bit (the TOML cache form round-trips every f64), so outputs do not depend on whether the tables were built or read, nor on the thread count. Only physics.materials.*_table.source and .cache, and the timings in run, differ. lindhard-cli/tests/examples.rs checks a building run, a storing run and reading runs on 1 and 8 threads against each other. Remove the directory to reclaim space; nothing else prunes it.

Reusing an output directory

--out may name an existing directory; the run overwrites the files it writes and creates the directory if needed. The CLI also owns the reserved optional file names of the run’s mode. After a successful run, an optional file the run did not produce is removed if present: ions.csv (without tally.per_ion), and electron_deposition_cartesian.csv or electron_deposition_cylindrical.csv (without the matching deposition grid). A missing file is not an error; a failed removal is, and names the path. The summary is written last and lists only files that exist. Other files in the directory are never touched, and no cleanup happens between ion, electron and dynamic runs. Do not keep your own data under a reserved name.

Reproducibility

Everything in summary.json except the trailing run object, and every CSV file, is a function of the input and the binary only: byte-identical for the same input and seed at any thread count (for a dynamic run: the same three files, with run the only thread-dependent part of dynamic_summary.json). For an electron run the same holds for electron_summary.json (apart from run) and every electron_*.csv: the tables are built bit-identically on any thread count, and histories run in chunks of a fixed size (16, recorded as physics.transport.chunk_size) merged in chunk order. lindhard-cli/tests/examples.rs checks this on 1 and 4 threads (1, 2 and 8 for the dynamic example; 1 and 4 for the electron example, and with --table-cache 1 and 8). Floats are written in shortest round-trip form.

Compatibility and extension

Consumers must ignore keys they do not know. New results are added as new keys, never by changing existing ones:

  • New tallies become new objects under results, with their settings as new keys under [tally] and their profiles as new CSV files listed under files. results.range, results.damage, results.sputtering and results.escapes were added this way, without a version bump.
  • physics.crystal (the [[crystal]] metadata) was added this way, without a version bump; an amorphous run does not carry it.
  • New model choices become new values of the existing [physics] keys, or new keys with defaults, so existing inputs keep their meaning.
  • format.version is bumped only when an existing key is removed or changes meaning.

The electron schema and its output follow the same rules, with lindhard-electron-summary counting its versions separately:

  • A new library model becomes a new value of an existing key (electron.inelastic.model, electron.elastic.potential, electron.transport.secondaries, electron.transport.boundary, a band kind, a phonon preset) and a new physics.models entry; existing values keep their meaning and defaults never change.
  • New per-material data (for example a subshell binding-energy table, once inner-shell channels reach the transport loop, or a precomputed cross-section cache) becomes a new optional key of [electron.materials.<name>] that names a file. It must be read with the lindhard::electron::data loader of its type, so data without a provenance is refused, and recorded under physics.materials with its path, SHA-256 and provenance.
  • The cross-section table cache is the one exception to the rule above, by decision (#168): it is a command-line flag (--table-cache DIR), not an input key, because it never changes a result (as with --threads, the input and its echo stay the same whether or not tables are reused), and a content-addressed directory keyed on every input of the build cannot name a stale file the way a hand-written path can. Its tables are still read with the lindhard::electron::data loader and echoed under physics.materials with their path, SHA-256 and provenance.
  • A new tally becomes a key under [electron.tally], an object under results and, for profiles, a new CSV file listed under files.
  • Every table keeps deny_unknown_fields, and every default is echoed, so an input written today still means the same thing.