Skip to content

Latest commit

 

History

History
184 lines (159 loc) · 11.3 KB

python.md

File metadata and controls

184 lines (159 loc) · 11.3 KB

Python bindings

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/.

Classes

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)

One schema, one engine

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.

Fluence-dependent runs

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 cumulative cum_* event counts, sputter_yield and cum_sputtered_by_species ((n_steps, len(species))). These are the columns of dynamic_steps.csv.
  • res.composition: a list parallel to steps; each entry has front_nm, back_nm, thickness_nm ((n_slabs,)) and atoms_per_cm2, fraction ((n_slabs, len(species))), the columns of dynamic_composition.csv. Erosion can change the slab count between steps, so the snapshots are separate arrays rather than one rectangular tensor.
  • res.summary() is dynamic_summary.json as 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.

Electron runs

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.json as a dict, including input echo, models, table diagnostics, transport metadata, yields, energy budget, per-layer deposition and the PSF totals and fits. Its run block (threads, timings) and each table's source/cache are the only parts that vary between otherwise identical runs.
  • res.escape_spectra: {"front": ..., "back": ...}, each a dict of Histograms: energy (eV, all escaping electrons), polar_slow and polar_fast (degrees from the outward normal). density is per primary per eV or degree, as in electron_escape_spectra.csv; the back face of a substrate has zero counts.
  • res.cartesian: None unless [electron.tally.cartesian] is configured; else x_edges_nm (depth), y_edges_nm, z_edges_nm, energy_ev of 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) and outside_ev.
  • res.cylindrical: None unless [electron.tally.cylindrical] is configured; else r_edges_nm, depth_edges_nm, energy_ev and ev_per_primary_per_nm3 of shape (nr, ndepth) (cell volume is the annulus times the depth bin) and outside_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.

Exposure dose and contours

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. shots is the CSV text of cli.md or an (n, 3) array of x_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 an ElectronRun (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_m2 and std_err_ev_per_m2 (transport, mean and standard error over replicas), convolution_ev_per_m2 (averaged over each grid cell), deviation_sigmas and the boolean flagged (points where the two differ by more than tolerance_sigmas combined standard errors). Details and the meaning of the options are in cli.md, "Exposure", section "Direct mode"; it runs the same code as lindhard exposure --direct, and threads never changes the results.
  • iso_contours(dose, x0_m, y0_m, dx_m, dy_m, threshold): a list of dicts with points ((n, 2), m), closed, signed area_m2 and centroid_m (None for a contour that leaves the grid).

Invalid values raise InputError.

Damage export

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.

Errors

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.