Damage Metrics
NRT-dpa and arc-dpa displacement functions, the Lindhard damage-energy partition, He/H gas production in appm, spectral folding conventions, uncertainty propagation through the folds, and coil fast-fluence / lifetime bookkeeping.
Nucleide computes activation inventories and gamma sources; the matching first-wall
question — how many displacements, and how much gas, a neutron spectrum produces in a
material — is answered by the nucleide.damage kernel. This page pins the equation
set: the Lindhard damage-energy partition, the NRT-dpa standard of Norgett, Robinson &
Torrensnrt-1975, the arc-dpa efficiency correction of Nordlund et
al.nordlund-2018, and the spectral-folding conventions that turn
caller-condensed cross sections into displacements per atom (dpa), atomic parts per
million (appm) of helium or hydrogen, and He/dpa ratios — plus the uncertainty
propagation hooked over the folds. Every material constant is caller data; nothing is
fitted or vendored here.
Overview
A neutron spectrum folded with a material’s damage response cross sections gives the displacement dose in dpa; folded with gas-production cross sections it gives the helium/hydrogen inventory in appm; the ratio of the two tracks embrittlement-relevant helium per unit damage. The crate has two layers of pure functions over caller inputs:
- closed-form primary-damage physics — the Lindhard partition (Robinson fit), the NRT displacement function, and the arc-dpa correction;
- spectral folds —
nrt_dpa/arc_dpa/gas_appm/he_dpa_ratioover(flux, response, bounds)slices with piecewise-constant-per-group semantics.
A third module, fold_uq, propagates caller-block uncertainty through the folds by
reusing the shared seeded-sampler machinery documented on the UQ-lite page — no new
sampling code. A fourth module, coil.rs, books magnet fast-fluence and lifetime over
caller spectra, reusing the folds as the dpa spectral weight (C1–C3 below).
The crate mirrors the kinetics layout: physics.rs (the closed-form damage
functions with validating inputs), fold.rs (the multigroup folds with documented
conventions), uq.rs (MVN uncertainty propagation), coil.rs (fast-fluence sums,
history accumulation, and weakest-link magnet life over caller spectra), specter.rs
(the opt-in Table VII fallback), and error.rs (a
#[non_exhaustive] error enum whose every variant names its cause — out-of-scope
capabilities report “not yet supported”, never a silent fallback).
Mathematical details
The Lindhard partition and damage energy
A recoiling lattice atom loses part of its energy to electronic excitation before it
can displace further atoms. The Lindhard partition divides the primary knock-on atom
(PKA) energy T between that ionizing part and the displacement (damage) energy
T_damlindhard-1963; Nucleide uses Robinson’s analytic fit to the
partition, with constants as documented by Griffin (SAND2016-2269 §3.2)griffin-sand2016:
with Z_R, A_R the recoil nuclide and Z_L, A_L the lattice nuclide (damage physics
sees only Z and A — the metastable state index of a nuclide key is ignored).
P → 1asT → 0(only logarithmically — theε^{1/6}term still costs ~0.7% at 1 µeV for iron) andP → 0asT → ∞; the damage energy saturates atE_L/k_Land never exceedsT.- The constants follow Griffin §3.2, which also documents the NJOY manual’s
k_Lexponent typo: the combined mass term in the numerator of (P2) carries exponent 3/2, not 2/3. Nucleide uses the corrected 3/2. - Energies are electron-volts throughout the damage functions; the spectral folds use MeV group bounds because neutron group structures are tabulated in MeV.
- Damage energy follows the SPECTER/NJOY lower-integration-bound-zero convention
(Griffin §2.5)griffin-sand2016: recoil energy below
E_dstill counts toward damage energy — it just does not displace.
The NRT displacement function
The Norgett–Robinson–Torrens standardnrt-1975 modifies the
Kinchin–Pease count with a displacement efficiency κ = 0.8 and the Lindhard damage
energy:
Nucleide evaluates (N1) for a self-recoil target, or for an arbitrary recoil/lattice
pair (a transmutation recoil stopped in a host lattice). The piecewise boundaries sit
on the PKA energy T — at E_d and 2E_d/κ — while the high branch evaluates
κ·T_dam/(2E_d) on the damage energy. That boundary convention follows SPECTER/NJOY
(Griffin §2.5)griffin-sand2016, not the arc-dpa paper verbatim
(its Eqs. (1) and (5) write both boundaries on the damage energy T_d). Two
consequences are pinned as known artifacts of the convention, not bugs: the N_d = 1
plateau covers PKA energies whose damage energy is still below E_d (a
damage-energy boundary would return 0 there), and the small downward step at the
junction T = 2E_d/κ is the known NRT construction.
The arc-dpa correction
Molecular-dynamics (MD) cascades show the NRT count overestimates surviving defects because recombination inside the cascade is ignored. Nordlund et al. (2018, CC BY 4.0) repair this with an efficiency function fitted to MD data — Nucleide implements Eqs. (5)–(7) of that papernordlund-2018:
By construction ξ(2E_d/κ) = 1 (junction with the plateau), ξ → c_arc as
T_d → ∞ (the MD saturation value), and b_arc < 0 makes ξ monotone decreasing.
b_arc and c_arc are material constants the caller supplies from MD or experiment;
the constructor enforces the fitted domain — b_arc finite and negative, c_arc
finite in (0, 1) — and the crate ships no fitted table. The corrected count is the
NRT piecewise form with the high branch multiplied by ξ(T_dam(T)):
One artifact of the PKA-energy boundary convention carries over: at T = 2E_d/κ the
efficiency overshoots 1 because T_dam < T there (the paper’s damage-energy form
stays at or below 1). ξ(0) diverges — zero to a negative power — and raises a clear
error, never inf.
Spectral folds
The closed forms above are what a nuclear-data pipeline would tabulate into per-group response cross sections; the crate itself folds a caller-supplied multigroup flux with caller-supplied responses under fixed conventions:
boundsholdsG + 1group boundaries in MeV, strictly increasing. Groupgspans[bounds[g], bounds[g+1]). Bounds are validated and document the grid; the fold is piecewise-constant per group, so a group’s width never enters the sum.flux[g]is the per-group integrated flux (n/cm²/s through groupg), i.e.∫_group φ(E) dE— not a per-lethargy or per-unit-energy density.response[g]is the group response cross section in barns, condensed by the caller consistently with the flux spectrum — a SPECTER/NJOY-class pipeline for displacement cross sections, an activation library for gas production. The kernel multiplies; it never re-weights within a group.
10⁻²⁴ converts barns to cm² (per target atom); the gas scale adds the
per-atom → parts-per-million factor 10⁶. t_exp is the exposure time at the
tabulated flux; pass 1.0 with a fluence parked in flux for a fluence fold.
nrt_dpa and arc_dpa share (F1) — they differ only in which displacement function
built the response. gas_appm counts whichever gas (He or H) the caller’s response
counts, one nuclide’s production per target atom. he_dpa_ratio folds the He and
damage responses over the same flux and divides; a damage fold of exactly zero raises
a clear error, never inf.
Coil fast-fluence and lifetime bookkeeping
Magnet lifetime is arithmetic once spectra exist: accumulate a damage metric over the operating history, compare each channel against its caller-supplied limit, and report the weakest link. The fast-flux sum is the piecewise-constant group sum over groups whose upper edge clears a caller threshold (a common fast-neutron choice is 0.1 MeV):
A threshold cutting through a group includes the whole group — callers
align the grid so the threshold sits on a group boundary when that
conservatism matters. Units are caller-consistent: whatever area
normalization flux uses, the fluence and the limit table share it. The
dpa spectral weighting stays in the landed folds (the NRT/arc fold at one
second is the rate the life kernel consumes); an irradiation history
accumulates piecewise-constantly,
and the weakest-link life is the minimum over channels of limit over rate — or limit-minus-accumulated over rate for remaining life, clamped at zero once a limit is reached:
A zero rate never breaches and is skipped (an all-zero history is infinite life with no limiting channel); all shapes, signs, and orderings are clear named errors reusing the fold vocabulary. Limit tables are always caller-supplied: published design numbers are gates, never defaults — the 99th-percentile coil fast flux m⁻²s⁻¹ against fast-fluence limits / m⁻² gives a 10 full-power-year life on the first channel (the hand quotient at ; ten full-power years at that flux lands back on the limit within ; the second channel alone is 50 FPY).
Uncertainty propagation
fold_uq perturbs the stacked [flux, response] vector (dimension 2G) with seeded
draws from a multivariate normal (MVN) distribution — the shared nucleide-linalg
sampler the UQ-lite page documents — using relative-unit perturbations applied as
nominal·(1 + δ) (the SANDY convention), then refolds each draw. For the three
linear folds the expectation under the Gaussian block is exact, and the standard
deviation is available to first order:
with μ the block mean, C the (2G)×(2G) covariance, and ⊙ the elementwise
product. For small relative blocks the quartic term neglected by (U2) sits orders of
magnitude inside the check width (in-crate tests size it at ~3×10⁻⁴ of the linear
term). The returned summary carries the nominal value, the sample mean and standard
deviation over the draws, the exact expectation (U1), the first-order propagation
(U2), and honest k-standard-error checks: the sample mean against the expectation
within k·std/√n, and the sample standard deviation against (U2) within
k·std/√(2(n−1)). A draw that leaves the physical domain (negative flux or response)
raises the fold’s named error, never a silent clip — callers keep covariance blocks
in the small-perturbation regime.
The He/dpa ratio is a nonlinear statistic, so its UQ forms the ratio per
draw instead of reusing the linear-fold machinery: each seeded draw over the
joint [flux, he, dpa] block refolds He and dpa and reports
, and the kernel reports the draw mean
± draw standard deviation. The honest gates need the ratio moments — with
, / the first-order fold variances, and
their cross term:
The mean of ratios carries the second-order bias, so gating the sample mean against the bare ratio of means would fail honestly-sized blocks — the gate compares against (U3) instead. At ±10% input uncertainty the heavy tails already inflate the sample spread past any honest first-order width, so the committed hand vector (independent appm He over dpa → , spread ) pins the analytics exactly while the seeded -SE convergence gate runs in the small-perturbation regime. Any draw at non-positive dpa fails loudly with the landed zero-dpa error — never infinity with a spread, never silently dropped draws.
Because a skewed ratio needs asymmetric margins, the summary also carries the distribution-free 68% interval: the draw 16th/50th/84th percentiles (linear-interpolating order statistics) gated against the quantiles of the Fieller constructionfieller-1954 — the ratio of the implied joint-normal pair, whose CDF at is the ordinary normal CDF of :
inverted by bisection, with the quantile standard error at the closed-form density . The evaluation is the Abramowitz–Stegun 7.1.26 rational approximationabramowitz-stegun-1964 (, pinned by reference values in-crate). The interval verdict holds at the ±10% hand-vector block where the symmetric-spread gate honestly cannot: draws against analytic at , skewing upward as a ratio must. The construction assumes negligible non-positive-dpa mass — the regime the loud per-draw zero-dpa check enforces.
Algorithm
The physics layer is pure functions: validate (finite non-negative T, E_d > 0,
valid nuclide keys, arc constants in the fitted domain), then evaluate (P1)–(A2) —
the partition at the PKA energy, the piecewise branches against PKA-energy
thresholds. The fold layer validates shape, finiteness, sign, and monotonicity first
— empty input, mismatched lengths, non-finite values, negative flux or response,
non-increasing bounds, and non-positive exposure each raise a named error — then
accumulates Σ f·r in one pass with the metric scale. There is no division anywhere
in the fold, so a zero-flux group contributes exactly 0.0 and cannot produce
NaN/inf; the ratio computes two folds and checks the denominator. The UQ layer
draws once (Cholesky factorization primary, eigenvalue-clip fallback), perturbs and
refolds per draw, and compares unbiased sample moments against (U1)–(U2). The coil
layer validates the shared (flux, bounds) shape once, then accumulates (C1)–(C2)
and takes the channel minimum (C3) — zero rates skipped, spent limits clamped.
Correctness checks: in-crate tests and the damage_vs_specter.py harness replay the
synthetic checks labeled G1–G7 — G1 the partition and NRT branches against an
independent in-script transcription of the published Robinson-fit constants
(tolerance 10⁻¹²), G2 the arc construction (junction value exactly 1, saturation at
c_arc, monotone decrease, high branch = NRT × ξ), G3 the folds against hand sums,
G4 the invariants (zero-flux groups exact; the zero-dpa ratio and malformed inputs
raising named errors), G5 the pinned-seed UQ checks including bit-identical
reruns, G6 the ratio-UQ hand vector plus its seeded k-SE gate, and G7 the
68% interval shape plus its Fieller-quantile k-SE gate at the ±10% block.
The coil
gates are the same style: threshold hand vectors on the three-group
grid (a cut-through threshold keeps the whole group), the dpa-rate feed (a 7.0e-10
limit against the hand fold rate is exactly 1.0 s of life), the weakest-link / zero /
clamp vectors, and the Stellaris-class anchor above. The external SPECTER cross-check is described under Related work.
Worked example: Fe-56 anchors and a three-group fold
All inputs below are synthetic: an Fe-56 self-recoil with an assumed average
threshold displacement energy E_d = 40 eV, assumed arc constants b_arc = −0.55,
c_arc = 0.3, and a hand-written three-group flux — the crate ships no material data.
import nucleide.damage as dmg
ED = 40.0 # eV, assumed average threshold displacement energy
B_ARC, C_ARC = -0.55, 0.3 # assumed MD-style arc constants (fitted domain: b < 0, 0 < c < 1)
print(dmg.nrt_displacements(20.0, ED, "Fe56")) # 0.0 (T < E_d)
print(dmg.nrt_displacements(40.0, ED, "Fe56")) # 1.0 (T = E_d)
print(dmg.nrt_displacements(100.0 * (1 - 1e-12), ED, "Fe56")) # 1.0 (2*E_d/0.8 = 100 eV)
t_dam = dmg.damage_energy(1.0e5, "Fe56", "Fe56")
print(t_dam) # 61865.089085593674 eV
print(dmg.nrt_displacements(1.0e5, ED, "Fe56")) # 618.6508908559368
print(dmg.arc_efficiency(100.0, ED, B_ARC, C_ARC)) # 1.0 exactly (A1 junction)
print(dmg.arc_displacements(1.0e5, ED, "Fe56", B_ARC, C_ARC)) # 198.22076705250228
# (F1): three-group fold; hand sum 1e12*100 + 2e12*200 + 4e12*50 = 7e14
bounds = [0.0, 0.1, 1.0, 20.0]
flux = [1.0e12, 2.0e12, 4.0e12]
resp = [100.0, 200.0, 50.0]
print(dmg.nrt_dpa(flux, resp, bounds, 2.0)) # 2*1e-24*7e14 = 1.4e-9
print(dmg.gas_appm(flux, resp, bounds, 2.0)) # 2*1e-18*7e14 = 1.4e-3
print(dmg.he_dpa_ratio(flux, resp, resp, bounds, 2.0)) # 1.4e-3 / 1.4e-9 = 1.0e6
print(dmg.nrt_dpa(flux, resp, [0.0, 5.0, 10.0, 20.0], 2.0)) # 1.4e-9 (bounds never enter)
dead = [0.0, 2.0e12, 0.0]
print(dmg.nrt_dpa(dead, resp, bounds, 1.0)) # 3.9999999999999996e-10
print(dmg.nrt_dpa(dead, resp, bounds, 1.0) == 1e-24 * (2e12 * 200.0)) # True
# (U1), (U2): seeded MVN over the stacked [flux, response] block, 1% std per group
mean = [0.0] * 6
cov = [[1e-4 if i == j else 0.0 for j in range(6)] for i in range(6)]
out = dmg.fold_uq("nrt_dpa", flux, resp, bounds, 2.0, mean, cov, n=4000, seed=20260915, k=5.0)
print(out["expected"], out["analytic_std"], out["passed"])
Output (verified against the built extension):
0.0
1.0
1.0
61865.089085593674
618.6508908559368
1.0
198.22076705250228
1.4e-09
0.0014000000000000002
1000000.0000000002
1.4e-09
3.9999999999999996e-10
True
1.4e-09 1.296148139681572e-11 True
Hand verification:
- The anchors follow from (N1) alone:
2E_d/κ = 2×40/0.8 = 100 eV, so 20 eV is below threshold, 40 eV displaces exactly once, and the plateau holds right up to the junction. - At
T = 10⁵ eVthe partition givesT_dam = 61865.089 eV(≈ 0.62 of the PKA energy survives electronic losses), and the high branch is0.8 × 61865.089 / (2×40) = 618.6508909— hereκ/(2E_d) = 1/(100 eV), so the value is justT_damin units of 100 eV. An independent transcription of the published constants —E_L = 30.724·26²·(2·26^{2/3})^{1/2}·2 = 174031.25 eV,k_L = 0.0793·2^{3/4}·26^{2/3}/56^{1/2} = 0.156411— reproduces the partitionP(10 keV) = 1/(1 + k_L·g(0.057461)) = 0.7424990to all printed digits. - For the arc count,
ξ(T_dam) = 0.7×(618.65089)^{−0.55} + 0.3 = 0.3204081and618.6508909 × 0.3204081 = 198.2208; at the junctionξ(100 eV) = 1exactly. - The fold hand sum is
7×10¹⁴barns·n/cm²/s, givingdpa = 2×10⁻²⁴×7×10¹⁴ = 1.4×10⁻⁹andappm = 2×10⁻¹⁸×7×10¹⁴ = 1.4×10⁻³; the ratio is the per-atom→ppm factor,1.0×10⁶ appm/dpato floating rounding. Regridding the bounds changes nothing — the bounds document the grid and never enter the sum. With the first and third groups dead, the fold equals the literal hand product1e-24 * (2e12 * 200.0)exactly (Trueon the equality check). - For the UQ block, (U1) with zero means gives
E[m] = m₀ = 1.4×10⁻⁹exactly, and (U2) givesstd = 2×10⁻²⁴ × √((10¹⁴)² + (4×10¹⁴)² + (2×10¹⁴)²) × √(2×10⁻⁴) = 1.2961×10⁻¹¹— matching the reportedanalytic_stdto all printed digits, with bothk-standard-error checks passing.
Assumptions and limitations
- PKA-energy boundary convention: the piecewise boundaries sit on the PKA energy
(
E_d,2E_d/κ) with the high branch evaluated on the damage energy — the SPECTER/NJOY convention, not the arc-dpa paper’s damage-energy boundaries. Pinned artifacts: theN_d = 1plateau covers PKA energies whose damage energy is belowE_d, and the arc efficiency slightly exceeds 1 just above the junction (becauseT_dam < Tthere). The small downward step atT = 2E_d/κis the known NRT construction. - Units: the damage functions take electron-volts; group bounds are MeV.
- Caller-supplied constants:
E_d,b_arc,c_arc, and every response table are caller data; arc constants are validated against the MD-fitted domain (b_arc < 0,0 < c_arc < 1) and no fitted parameter table ships. The partition needs onlyZandAof recoil and lattice. - Piecewise-constant folds: the kernel never re-weights within a group, and
boundsdocument the grid without entering the sum; responses must be condensed consistently with the flux spectrum by the caller’s own pipeline. - Zero and sign handling: zero-flux groups contribute exactly
0.0; the He/dpa ratio at zero damage raises a clear error — never infinity; the arc efficiency at zero damage energy diverges by construction and raises a clear error — never infinity; negative flux or response raises a clear error, never clamped. - Uncertainty scope: UQ covers the three linear folds. The He/dpa ratio is a nonlinear statistic and its UQ is not yet supported (a clear named error). MVN draws that leave the physical domain surface as the fold’s named error, never a silent clip — keep covariance blocks small (the first-order (U2) neglects a quartic term ~3×10⁻⁴ of the linear term at the in-crate sizing).
- Coil bookkeeping scope: fast flux, fluence, history, and weakest-link life are the only coil metrics; dpa weighting reuses the folds unchanged. Limit tables are caller data (published design numbers gate, never default), flux/limit units must match by caller discipline, and magnetics, quench, structural analysis, and transport solving stay out.
- Out of scope: PKA-spectrum solving, displacement-table vendoring, transport solving, and evaluated-data consumption (TENDL/JEFF/EAF/IRDFF) — callers bring their own response tables. ASTM E693/E521 are paywalled standards referenced by designation string only.
- Thin facades:
nucleide.damage(Python) exposes the crate one-to-one; no physics beyond the folds above.
Related work beyond Nucleide
SPECTER (Greenwood & Smither, ANL/FPP/TM-197, 1985 — a US-government work, public
domain)specter-1985 is the fusion-community damage calculator whose
printed HFIR-CTR32 results (Tables V/VI, total fluence 4.78373×10²² n/cm²) serve as
the external correctness check for the folds in the repo validation harness: a
handful of transcribed spectrum-averaged dpa and gas-production cross sections are
folded one-group at report print precision (five significant digits, 10⁻⁴ relative
tolerance) and matched against the report’s printed dpa, GAS(APPM), and Fe He/dpa
values. SPECTER is a correctness-check source and the source of one opt-in
fallback table — the Table VII spectrum-averaged damage-energy cross
sections for 24 elements (plus the Table II threshold energies), exposed
through nucleide.damage for callers with no damage-data pipeline of their
own and never consulted implicitly — and when the report text is
unavailable the check is recorded as skipped, never a silent pass. ASTM
E693/E521 are the paywalled displacement-dose standards of record,
referenced by designation only.
Adjacent capabilities Nucleide does not implement: PKA-spectrum solving (the GPL-licensed fispact-org PKA evaluator was never read), transport solving, and per-group displacement tables built from an evaluated library — that NJOY/SPECTER-class pipeline remains the caller’s job.