The lindhard Python package (crate lindhard-py, built with
maturin and pyo3) runs the same
engine as the command line and returns the tallies as NumPy arrays. Usage and
build steps: ../lindhard-py/README.md; type stubs
are in lindhard-py/python/lindhard/.
| Class | What |
|---|---|
Element, Material |
Inline material: elements with fractions and optional e_d_ev / e_b_ev / e_s_ev; density_g_cm3 (required for compounds) |
Layer, Target |
Finite layers front to back (a material name, element symbol or Material, and thickness_nm) and an optional semi-infinite substrate |
Beam |
ion, energy_ev, mass_amu, tilt_deg, azimuth_deg, and optionally divergence_model ("gaussian" or "uniform-cone") with divergence_deg (the [beam.divergence] table) |
Physics |
The [physics] table: models (as the TOML names), cutoffs, energy overrides |
Tally |
The [tally] table (CLI defaults) |
Run |
Beam, target, physics, tally, ions, seed, threads, named materials and stopping_tables; from_toml(), from_toml_file(), to_toml(), validate(), run(), run_dynamic() |
RunResult |
depth, lateral_y, lateral_z, radial, vacancies, interstitials, replacements (each a Histogram with edges, counts, density, underflow, overflow), energy_profile ({"electronic", "lattice", "recoil"}, each an EnergyHistogram with edges, energy, density, underflow, overflow: energy by depth as in energy_profile.csv), escapes, ions (final state of each primary), summary() (dict), write(dir) |
ElectronRun |
An electron input (an [electron] table): from_toml(), from_toml_file(), to_toml(), validate(), run(); settable histories, seed, threads, base_dir |
ElectronRunResult |
Result of ElectronRun.run(): escape_spectra, cartesian, cylindrical, damage_export(), summary() (dict), summary_json() |
DynamicRunResult |
Result of Run.run_dynamic(): species, steps (per-step arrays), composition (per-step slab profiles), summary() (dict), write(dir) |
The classes are value wrappers over lindhard::input's structs, so the TOML
schema (cli.md) has a single definition: Run.to_toml() output is
valid CLI input and Run.from_toml() reads CLI input. Run.run() calls the
same driver as lindhard run (lindhard_cli::sim::simulate) and the arrays
and summary() are computed with the same expressions as the CSV and
summary.json writers. For the same input and seed the result equals the
command's output bit for bit, at any thread count; tests/test_cli_parity.py
checks this against the built binary. Run.run() releases the GIL while it
transports.
A [[crystal]] section (cli.md) is part of that schema: Run.from_toml() keeps it,
Run.to_toml() writes it back, and Run.run() attaches the crystal in the
same driver, so summary() carries physics.crystal as the command's
summary.json does. The Python classes have no crystal constructor of their
own yet; a crystal run starts from TOML.
An input with a [dynamic] section (cli.md) is a fluence-stepped run. Read
it with Run.from_toml() and call Run.run_dynamic(ions=, seed=, threads=),
which applies the same per-call overrides and warnings as run(), calls the
CLI's driver (lindhard_cli::dynamic::simulate_dynamic) with the GIL
released and returns an immutable DynamicRunResult. Run.run() is
unchanged and still rejects a [dynamic] input. run_dynamic() raises
InputError for an input without [dynamic].
res.species/res.species_z: the elements the run can contain (beam species and target elements, sorted by atomic number).res.steps: a dict of arrays, one entry per step with index 0 the initial target:step,first_index,ions,ions_done,fluence_cm2(ions/cm^2),attempts,max_change,clamped,removed_slabs,n_slabs,surface_nm(cumulative recession),thickness_nm, the cumulativecum_*event counts,sputter_yieldandcum_sputtered_by_species((n_steps, len(species))). These are the columns ofdynamic_steps.csv.res.composition: a list parallel tosteps; each entry hasfront_nm,back_nm,thickness_nm((n_slabs,)) andatoms_per_cm2,fraction((n_slabs, len(species))), the columns ofdynamic_composition.csv. Erosion can change the slab count between steps, so the snapshots are separate arrays rather than one rectangular tensor.res.summary()isdynamic_summary.jsonas a dict;res.write(dir)writes it and the two CSVs with the CLI's policy (the previous summary is removed before any CSV changes, the new one is published last).
Arrays equal the CSV values bit for bit for the same input and seed, at any
thread count (tests/test_dynamic.py). A walk-through is in
../examples/python/dynamic_composition.py.
An input with an [electron] table (cli.md, "Electron runs") is read with
ElectronRun.from_toml() / ElectronRun.from_toml_file(). This first
increment is TOML-first: there are no electron configuration classes, because
the TOML already defines the choices. ElectronRun.run(histories=, seed=, threads=) applies call-local overrides, resolves the input with the same
validation as lindhard check, and calls the CLI's driver
(lindhard_cli::electron::simulate_electron, no table cache) with the GIL
released during both table construction and transport. Resolution warnings are
issued as UserWarning (validate() returns them); a bad configuration raises
InputError, a failed build or transport RunError. Relative paths to optical
and other data files resolve against the file's directory
(from_toml_file) or the base_dir argument/attribute, the working directory
otherwise. Disk-cache controls, result-file writers and PSF array accessors are
not exposed yet; use the CLI for those.
ElectronRunResult:
res.summary():electron_summary.jsonas a dict, including input echo, models, table diagnostics, transport metadata, yields, energy budget, per-layer deposition and the PSF totals and fits. Itsrunblock (threads, timings) and each table'ssource/cacheare the only parts that vary between otherwise identical runs.res.escape_spectra:{"front": ..., "back": ...}, each a dict ofHistograms:energy(eV, all escaping electrons),polar_slowandpolar_fast(degrees from the outward normal).densityis per primary per eV or degree, as inelectron_escape_spectra.csv; the back face of a substrate has zero counts.res.cartesian:Noneunless[electron.tally.cartesian]is configured; elsex_edges_nm(depth),y_edges_nm,z_edges_nm,energy_evof shape(nx, ny, nz)(energy deposited per voxel summed over all histories, eV, index order(ix, iy, iz)),ev_per_primary_per_nm3(the same divided by histories and voxel volume) andoutside_ev.res.cylindrical:Noneunless[electron.tally.cylindrical]is configured; elser_edges_nm,depth_edges_nm,energy_evandev_per_primary_per_nm3of shape(nr, ndepth)(cell volume is the annulus times the depth bin) andoutside_ev.
As for ions, the arrays equal the CSV values bit for bit for the same input and
seed at any thread count (tests/test_electron.py compares against the
built binary). A walk-through is in
../examples/python/electron_run.py.
The dose of a shot list from a fitted Gaussian-sum point-spread function is
available without a run; the functions are the ones behind lindhard exposure (cli.md, "Exposure"), so the arrays equal its CSV values bit for
bit at any thread count (tests/test_exposure.py).
exposure_dose(shots, x_m, y_m, *, total_ev, alpha_m, beta_m, eta, gamma_m=None, nu=None, blur_m=0.0, subsamples=16, threads=None): dose in eV/m^2 at the points.shotsis the CSV text ofcli.mdor an(n, 3)array ofx_m, y_m, weight.exposure_dose_grid(shots, x0_m, y0_m, dx_m, dy_m, nx, ny, *, ...): the dose on a grid, shape(ny, nx), element[j, i]at(x0_m + i dx_m, y0_m + j dy_m).area_dose(*, total_ev, alpha_m, beta_m, eta, gamma_m=None, nu=None, weight_per_m2=1.0): the large-area dose with its forward-only and long-range parts.exposure_direct(shots, run, x0_m, y0_m, dx_m, dy_m, nx, ny, *, total_ev, alpha_m, beta_m, eta, slab_hi_m, histories_per_shot, gamma_m=None, nu=None, blur_m=0.0, subsamples=16, replicas=8, seed=1, slab_lo_m=0.0, tolerance_sigmas=5.0, conv_rel_err=0.0, floor_fraction=0.01, threads=None): the same shot list by electron transport (direct Monte Carlo) on the layered target of anElectronRun(its layers, tables and beam energy; the shots set where the primaries land), compared with the superposition. Returns a dict of(ny, nx)arrays:dose_ev_per_m2andstd_err_ev_per_m2(transport, mean and standard error overreplicas),convolution_ev_per_m2(averaged over each grid cell),deviation_sigmasand the booleanflagged(points where the two differ by more thantolerance_sigmascombined standard errors). Details and the meaning of the options are incli.md, "Exposure", section "Direct mode"; it runs the same code aslindhard exposure --direct, andthreadsnever changes the results.iso_contours(dose, x0_m, y0_m, dx_m, dy_m, threshold): a list of dicts withpoints((n, 2), m),closed, signedarea_m2andcentroid_m(Nonefor a contour that leaves the grid).
Invalid values raise InputError.
An input with a [tally.voxel] table gives res.damage_export(): a dict of
NumPy arrays in the damage export format, also
written by res.write(dir) as damage_export.csv. lindhard.read_damage_export(path)
reads such a file back into the same dict; the arrays are equal to the run's
bit for bit. res.damage_export() is None without the table. An electron
run (ElectronRun) gives the same from an [electron.tally.voxel] table
(engine "electron" in provenance).
Keys: origin_m, spacing_m (3-vectors, m), dims, histories,
species_z (list), the (nx, ny, nz) arrays implanted, displacements
(integer counts) and electronic_ev, lattice_ev, rest_ev, deposited_ev
(eV), the (nspecies, nx, ny, nz) integer arrays vacancies, interstitials,
replacements, outside (the same fields summed outside the grid, plus
defects of shape (nspecies, 3)) and provenance (engine, seed,
lindhard_version, input_sha256, format_version). Unoccupied voxels are 0.
Electron voxel maps use the same file format (species_z empty); the electron
Python run does not yet configure a voxel grid, so only the reader applies to
them there.
| Exception | Base classes | Raised for |
|---|---|---|
InputError |
LindhardError, ValueError |
malformed TOML, unknown key, invalid value (message names the field), unknown choice name |
RunError |
LindhardError, RuntimeError |
a run that was accepted but failed in transport (for example a stopping table queried outside its range) |
Resolution warnings (the CLI prints them on stderr) are issued as
UserWarning; Run.validate() returns them as a list.