Skip to content

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 05 Nov 04:32
· 230 commits to main since this release

All SRIM outputs as pandas DataFrames (incl. per-ion positions, trajectories, backscattered/transmitted/sputtered atoms and full cascades), editable run.yaml run specs with pysrim2 run, and export to Parquet/HDF5/CSV.

Added

  • CI reports, informationally, the RANGE_3D digest of the same spec and seed on Linux (wine), Windows and in the container (the repro-report job compares Linux and Windows; the container digest is listed separately); same-platform reruns are asserted identical (tests/integration/test_reproducibility.py). Observed: Linux (wine) and the container are bit-identical, native Windows gives the same summary but different per-ion detail (see docs/run-spec.md).

  • .df (a pandas DataFrame, first column depth_A, columns named with SRIM units) and .metadata on Ioniz, Vacancy, NoVacancy, EnergyToRecoils, Phonons and Range; Results.to_dataframes(include=None) returns them as a dict.

  • srim.output.Range3D reads RANGE_3D.txt (per-ion final positions) into a DataFrame with columns ion, depth_A, y_A, z_A; Results.range3d holds it (None if the file is absent) and Results.to_dataframes() includes it.

  • Range.summary returns the range and straggling values SRIM prints in the RANGE.txt header (depth, lateral, radial, skewness, kurtosis). srim.analysis.implant_summary(df) computes the same quantities from the per-ion positions of Range3D (standard deviations with ddof=1).

  • srim.output.EXYZ, Backscat, Transmit and Sputter read EXYZ.txt, BACKSCAT.txt, TRANSMIT.txt and SPUTTER.txt into DataFrames (ion, atomic_number, energy_eV, depth_A, y_A, z_A, cos_x, cos_y, cos_z; EXYZ: ion, energy_keV, depth_A, y_A, z_A, electronic_stopping_eV_per_A, last_recoil_energy_eV); Results.exyz, .backscat, .transmit, .sputter hold them (None if absent; parsed on first access, streaming, so large files are cheap to open) and to_dataframes() returns them. A zero-byte event file gives an empty frame and a warning. They replace the stubs. EXYZ parser based on work by philis-johan. Fixtures generated with real SRIM in test_files/outputs-0.8/.

  • Collision.iter_events() streams COLLISON.txt line by line as flat event dicts (ion, kind = ion for a collision of the primary ion or recoil for a cascade recoil, energy_keV, depth_A, y_A, z_A, atom, recoil_energy_eV, target_displacements, target_vacancies, target_replacements, target_interstitials, plus atomic_number, electronic_stopping_eV_per_A, vacancy, replacement); Collision.df collects them into a DataFrame chunk-wise. Results.collision (lazy, None if absent) and Results.to_dataframes(include=["collision"]) expose them; to_dataframes() leaves collision out by default (opt-in like the exports, since COLLISON.txt can be several GB).

  • Results.to_parquet(directory), .to_hdf5(path) and .to_csv(directory) export the tables (optionally selected with include=; collision only on request) with metadata: the table's .metadata, pysrim2 version, export time and an optional run spec (source is the file name only; Parquet: schema key pysrim2; HDF5: node attribute pysrim2; CSV: metadata.yaml). Collision.to_parquet(path, chunk_rows=100_000) (also a chunk_rows argument of the Results.to_* methods) and Collision.to_csv stream the cascade events in chunks, so memory stays small for huge COLLISON.txt files. New optional extras pysrim2[parquet] (pyarrow) and pysrim2[hdf5] (PyTables); Collision.metadata. The spec argument of the exports accepts a RunSpec (stored as its mapping) or a dict; anything else raises TypeError.

  • run.yaml: srim.spec.RunSpec (and IonSpec, LayerSpec, ElementSpec) describes a run with units in the key names (energy_keV, thickness_nm, density_g_cm3, angle_deg, ...), validates it with errors naming the dotted key (SpecError), and round-trips to a byte-identical TRIM.IN. TRIM.to_spec(), TRIM.from_spec(), TRIM.to_yaml(), TRIM.from_yaml(); the seed is always recorded. TRIM.run() writes run.yaml with provenance (pysrim2 version, SRIM SHA-256, platform, backend, start/end time, host) next to the outputs (write_spec=False to skip), TRIM.copy_output_files copies it, and Results.spec is the default spec of the to_parquet/to_hdf5/to_csv exports (no host name). See docs/run-spec.md.

  • Documentation: new pages docs/dataframes.md (every column, Range.summary, implant_summary, iter_events), docs/export.md, and a full schema reference in docs/run-spec.md; the README shows .df and implant_summary.

  • pysrim2 run run.yaml [--set dotted.key=value ...] [--output DIR] runs TRIM from a run spec (values converted by the schema; outputs and run.yaml copied to DIR, default ./srim-output-<UTC timestamp>), and pysrim2 import TRIM.IN [-o run.yaml] turns an existing TRIM.IN into a run spec. Exit codes in docs/cli.md.

  • Example notebooks rewritten on the 0.8.0 API (default SRIM location, .df tables, implant_summary, Collision.iter_events() / .df, Parquet export, run.yaml with TRIM.from_spec and pysrim2 run), with small ion counts and no committed outputs; Regular SRIM Calculation.ipynb is now Regular_SRIM_Calculation.ipynb, and KTaO3.ipynb is merged into SiC.ipynb. A srim-marked test (tests/integration/test_run_notebooks.py, nbclient) executes them; a static test checks that they are valid and import existing names.

  • examples/scripts/run_yaml_example/ (a run.yaml and README for pysrim2 run run.yaml --set ion.energy_keV=30); the agent skill and examples/agents/README.md teach .df, implant_summary, event tables, cascades with iter_events() and chunked export, and run.yaml.

Changed

  • A Layer without a name is now named after its elements (Si+C, or the formula for Layer.from_formula("SiC", ...); the same default for a layer without name: in run.yaml) instead of the text None. This changes the layer name written to TRIM.IN (and run.yaml) for unnamed layers compared with 0.7.0.

Fixed

  • The .df element columns of Kinchin-Pease tables (vacancies_<element>_..., absorbed_<element>_...) are named after the element for single-element targets instead of 1; the header of those tables lists no symbols, so they are read from the target section. Multi-element targets keep the numbers.

  • A zero-byte RANGE_3D.txt gives an empty Range3D frame (same columns) and a warning, like the event files, instead of raising. Range3D also warns when a line in the data section cannot be parsed (a truncated final line is still ignored silently).

  • The reference example's "lateral straggle" (341.6 A for the boron case) was the root mean square of the radial distance, not what SRIM calls lateral straggling; the example and skill now report SRIM's definitions (lateral straggle 241.8 A, radial straggle 154.9 A).

  • Results.range3d also finds RANGE_3D.txt in the SRIM Outputs subfolder.

  • A table header whose element names cannot be read now warns before the element columns fall back to 1, 2, ...; the Vacancy.knock_ons docs give the correct unit (knock-ons per Angstrom per ion).

  • Collision (COLLISON.txt) now parses every ion: the file is decoded as cp437 and split on SRIM's box-drawing column separators, so numbers no longer fail with glued separator characters (508 of 1000 ions of the SiC-C fixture raised ValueError/IndexError). Cascades without a summary line and stray recoil lines are handled; the per-ion dict API is unchanged.

  • pysrim2 install exits 130 with a [skip] ... cancelled line on Ctrl-C during a step instead of printing a traceback.