Skip to content

Latest commit

 

History

History
201 lines (161 loc) · 10.1 KB

File metadata and controls

201 lines (161 loc) · 10.1 KB

Contributing to Temari

Thank you for looking. This project has one unusual rule that dominates everything else, so it comes first.

The one rule: reproducibility outranks speed

The tables generated by this code are a scientific product. The same input must produce the same bits. A change that makes the code 30 % faster but perturbs the last few bits of the output is not an improvement — it invalidates every dataset generated before it, because the dataset can no longer be reproduced from the source.

Concretely, in any code that participates in a computation:

  • Do not change summation order. No @simd on a reduction, no reassociation, no fast-math.
  • Do not introduce muladd or fma. They fuse a rounding step away and change results.
  • Do not swap Base.sum() for a hand-written loop, or vice versa. Base.sum() uses pairwise summation internally; the two are not interchangeable.
  • Cache blocking is fine as long as tiles accumulate into the same scalar in increasing index order. That is how the existing 4× speedups were obtained.
  • Watch out for Core.Box: do not let a closure passed to ntuple capture a variable that is reassigned inside the loop.

Two kinds of change are allowed to alter output values:

  1. A correctness fix. If the current code is wrong, the fix wins — even over bit compatibility. Non-determinism is itself a correctness bug, so output that depends on load or thread partitioning is never protected as a reference.
  2. A deliberate new dataset generation. Optimizations that must break bit identity (for example changing the quadrature grid) are held until the next dataset generation and declared in that dataset's manifest.

In both cases the change must be declared in the pull request, and the reference bits are redefined afterwards.

No dependencies

Julia standard library only. No SIMD.jl, no LoopVectorization.jl, no BLAS beyond what the standard library already provides. Before reaching for a package, try reorganizing arrays so that @simd / @inbounds lets LLVM vectorize on its own — that is how the current 8-lane spherical Bessel kernel was written.

The one bundled data file

src/bote_salvat.json holds the Bote–Salvat analytic ionization cross-section coefficients and shell binding energies for Z = 1–99. Its provenance, in full, because "no third-party number tables in the repository" makes it the one exception that has to be justified:

  • What it is. The analytic parameterization of Bote, Salvat, Jablonski & Powell, At. Data Nucl. Data Tables 95, 871 (2009) (and its Erratum, 97, 186), fitted to the relativistic distorted-wave calculations of Bote & Salvat, Phys. Rev. A 77, 042701 (2008).
  • Where these bits came from. Machine-extracted from NIST's BoteSalvatICX.jl (github.com/usnistgov/BoteSalvatICX.jl), which is released under The Unlicense — a public-domain dedication. That package in turn transcribes xion.f by the same authors. So the numbers reach us through a US Government public-domain redistribution, not from the publisher's PDF.
  • The citable NIST source of record for the underlying cross sections is NIST SRD 164 / NSRDS 164, Llovet, Salvat, Bote, Salvat-Pujol, Jablonski & Powell, NIST Database of Cross Sections for Inner-Shell Ionization by Electron or Positron Impact, doi:10.6028/NIST.NSRDS.164 (US Government work, published free of charge). ⚠ Note what it is and is not: NSRDS 164 is a user's guide plus 198 ASCII tables of computed cross sections. It does not print the analytic coefficients — it points at the ADNDT papers for those (its §3). Swapping our analytic evaluation for its tables would be a different prescription, would move every shipped σ, and would therefore need a full table regeneration; it is not a drop-in citation change.

Nothing else is bundled.

The engine is src/ionization.jl — a thin loader and command line — plus the l0l5 layer files it includes. There is no Julia module: the namespace stays flat, so including src/ionization.jl exposes every name, and concatenating the files in include order yields a single runnable file. Both properties matter. Dropping src/ somewhere on its own and running it must keep working, and nothing may come to depend on a package-manager environment.

Do not add reference data from restricted sources

Comparison against published tables (Oxley & Allen 2000) and against GPL-licensed codes (µSTEM) is part of development, but their values may not enter this repository -- not in code, test fixtures, documentation or issues. That means no transcription of their tables: no columns of their numbers, no lists of their raw values, and nothing that is one arithmetic step from either. Cite where the comparison data lives instead.

What you should write is the outcome of the comparison: a deviation quantified over a stated channel and a stated s range, or a table of our own values, or the ratio between ours and theirs. Stating how far apart two calculations sit is ordinary scientific reporting and is what makes a validation claim checkable -- replacing it with "agrees well" would be less honest, not more careful. The line is transcription, not arithmetic: a deviation figure characterises the comparison, a copied column republishes their work.

Verification before you open a pull request

All of these run from the repository root and need nothing but Julia.

# 1. Analytic ladder T0-T24 and T26-T27 (~1 min; up to ~3 min cold). Failures are assertions.
julia -t auto src/ionization.jl selftest

# 2. Against the independent Python implementation (~1 min).
#    Expected: WORST ~9e-8, which is the residual of independently converged SCF.
julia -t auto src/ionization.jl refcheck

# 3. Bit-identity of the optimized kernels against their scalar references.
julia -t 1 tools/verify_simd_bessel.jl      # 288 cases
julia -t 1 tools/verify_e5_qlane.jl         #  75 cases
julia -t 1 tools/verify_e5_qlane_dirac.jl   #  75 cases (the v4 shipping path)
julia -t 1 tools/verify_angular_pack.jl     # 61440 elements

# 4. A generated dataset (only if you changed the generator).
julia -t auto tools/check_tables.jl <prod_dir> [--eb]

For anything that touches a computation, add the end-to-end bit-identity check. Take the "before" snapshot first — you cannot reconstruct it afterwards:

julia -t 4 tools/bitident_snapshot.jl before.txt          # BEFORE your change (v2/v3 prescriptions, 5 channels)
julia -t 4 tools/bitident_snapshot.jl --v4 before4.txt    # BEFORE your change (v4 shipping prescription, 7 channels)
# ... make the change ...
julia -t 4 tools/bitident_snapshot.jl after.txt
julia -t 4 tools/bitident_snapshot.jl --v4 after4.txt
diff before.txt after.txt                                 # empty = bit-identical
diff before4.txt after4.txt

Run both. The plain snapshot covers five channels chosen to span light/heavy elements, K and L3, and both continuum models of the v2/v3 prescriptions — it guards the reproduction path. The --v4 snapshot covers seven channels of the v4 shipping prescription (κ-resolved Dirac continuum, Dirac SCF, including M1 = 3s and M5 = 3d) — it guards the shipping path, which the five-channel set does not exercise. Both print every value with a round-trippable representation, so a text diff is equivalent to a === comparison on Float64.

If your change is supposed to alter values, also build a version with the change neutralised (the way J0_MIN = 0.0 disables the spherical-Bessel guard) and confirm that version is bit-identical to the old code. That is the only reliable way to separate an intended physical change from a refactoring accident.

Which Julia

The gate is Julia 1.11.9, the interpreter pinned by the current F(s, E₀) dataset generation (dataset-factors v1.0.0 is pinned to 1.12.6 in its own manifest). Newer versions are tested in CI, but a result that only passes on a newer version is not accepted. If you use juliaup, julia +1.11 selects it.

Style

  • Comments are the authoritative statement of the prescription. Explain the physics and the choice, not the syntax. Existing comments are in Japanese; English is equally welcome for new ones.
  • Commit messages in English, imperative mood, explaining why.
  • The L0–L5 split has happened; docs/architecture.md maps every file to its layer. Put new code in the layer it belongs to and keep the dependencies pointing downwards — nothing below L5 may know which exit is being computed.

Performance work

Measure, do not assume. Several plausible optimizations measured at 1.0× on this workload: hoisting a reciprocal out of a recurrence (latency-bound dependency chain), --heap-size-hint (the live set is small, GC is allocation-rate driven), and replacing BigInt factorials with a lookup table.

Use BenchmarkTools.jl from a separate environment (julia --project=/some/scratch/benchenv) — the repository itself must stay dependency-free. Report the min statistic; treat anything under 2–3 % as noise. Benchmarks that saturate all cores should say so in the pull request.

Reporting a bug

Use the issue template. The three things that make a report actionable are the exact command line including the thread count, the Julia version, and the selftest output. If the process stopped making progress on Windows under a long multithreaded batch, read Troubleshooting first — that is a known Julia runtime problem, not a Temari one.

AI assistance

Most of the implementation code was produced with assistance from Anthropic Claude and was reviewed and integrated by the author. The author is responsible for the physical prescription, for the tests and for the released data. AI assistance is not treated as independent validation — the reproducible checks, the external comparisons and the known unresolved discrepancies are what carry the claim, and they are documented on the Verification page.

Contributions written the same way are welcome, under the same condition: you must have verified the result yourself, with the commands above, and you are answerable for it.