lindhard/ion/damage.rs
1//! Displacement-damage models: the Lindhard partition, NRT and Kinchin-Pease.
2//!
3//! These are *model estimates* of the number of displacements made by a
4//! primary knock-on atom (PKA) of a given energy. They are kept apart from the
5//! *event counts* of a full-cascade simulation (vacancies, interstitials,
6//! replacements, [`crate::tally::CascadeDefects`]), which the BCA engine
7//! produces by following every recoil. The two answer different questions and
8//! are reported side by side, never added or substituted for each other.
9//!
10//! # Damage energy (Lindhard partition)
11//!
12//! Of a PKA's energy `T`, the part eventually given to atomic motion (the
13//! damage energy) is `T_dam = T / (1 + k g(ε))`, J. Lindhard, V. Nielsen,
14//! M. Scharff and P. V. Thomsen, "Integral equations governing radiation
15//! effects", Mat. Fys. Medd. Dan. Vid. Selsk. 33 (10) (1963), with the
16//! analytic fit `g(ε) = 3.4008 ε^(1/6) + 0.40244 ε^(3/4) + ε` of M. T.
17//! Robinson (in *Nuclear Fusion Reactors*, British Nuclear Energy Society,
18//! London, 1970, p. 364), as adopted by the NRT standard below (coefficients
19//! checked against J. F. Ziegler, J. P. Biersack, M. D. Ziegler, *SRIM: The
20//! Stopping and Range of Ions in Matter* (SRIM Co., 2015), Eq. 7-52,
21//! p. 7-28, a secondary source). Here `ε` is
22//! the Lindhard reduced energy and `k` the Lindhard-Scharff electronic
23//! stopping coefficient of the PKA (atomic number `Z1`, mass `A1`) in the
24//! target (`Z2`, `A2`), as in [`crate::ion::stopping::lindhard_scharff`]:
25//!
26//! ```text
27//! ε = T a A2 / (Z1 Z2 e^2 (A1 + A2)), a = 0.8853 a0 (Z1^(2/3) + Z2^(2/3))^(-1/2),
28//! k = 0.0793 Z1^(2/3) Z2^(1/2) (A1 + A2)^(3/2) / ((Z1^(2/3) + Z2^(2/3))^(3/4) A1^(3/2) A2^(1/2)).
29//! ```
30//!
31//! For a self-ion (`Z1 = Z2 = Z`, `A1 = A2 = A`) these reduce to the forms
32//! written in the NRT standard, `ε = T / (86.931 Z^(7/3))` (T in eV) and
33//! `k = 0.1337 Z^(1/6) (Z/A)^(1/2)` (checked in a test).
34//!
35//! # NRT and Kinchin-Pease
36//!
37//! M. J. Norgett, M. T. Robinson and I. M. Torrens, "A proposed method of
38//! calculating displacement dose rates", Nucl. Eng. Des. 33 (1975) 50:
39//!
40//! ```text
41//! N_NRT = 0 for T_dam < E_d,
42//! = 1 for E_d <= T_dam < 2 E_d / 0.8,
43//! = 0.8 T_dam / (2 E_d) for T_dam >= 2 E_d / 0.8,
44//! ```
45//!
46//! with `E_d` the displacement threshold and 0.8 the displacement efficiency
47//! the authors took from BCA simulations. The older G. H. Kinchin and R. S.
48//! Pease, "The displacement of atoms in solids by radiation", Rep. Prog.
49//! Phys. 18 (1955) 1 count is `0`, `1`, or `T / (2 E_d)` with thresholds `E_d`
50//! and `2 E_d`; [`kinchin_pease`] is evaluated on whatever energy it is given
51//! (the tallies pass the damage energy, the usual "modified" form).
52//!
53//! **NRT is not a cascade count.** R. E. Stoller, M. B. Toloczko, G. S. Was,
54//! A. G. Certain, S. Dwaraknath and F. A. Garner, "On the use of SRIM for
55//! computing radiation damage exposure", Nucl. Instrum. Methods B 310 (2013)
56//! 75, discuss how BCA full-cascade vacancy counts and NRT displacement
57//! estimates differ and recommend that, when a displacement dose comparable
58//! with the NRT standard is wanted, it be computed from the damage energy with
59//! the NRT formula rather than taken from the full-cascade vacancy count. NRT
60//! is also known to overestimate the number of defects that survive
61//! in-cascade recombination (as seen in molecular dynamics); it is a standard
62//! exposure unit, not a prediction of surviving defects. This crate therefore
63//! reports both and lets the user choose.
64//!
65//! # Compound and multi-element targets (an approximation)
66//!
67//! NRT (Norgett, Robinson and Torrens 1975) and the Lindhard partition it
68//! uses are defined for a monatomic target. For a layer with more than one
69//! element, the tally ([`crate::tally::IonTally`]) evaluates the partition
70//! with:
71//!
72//! * `Z1`, `A1`: the PKA's own atomic number and mass;
73//! * `Z2`, `A2`: the atom-fraction-weighted mean atomic number and mean
74//! mass of the layer the PKA starts in (non-integer in general);
75//! * `E_d`: the PKA element's own displacement threshold in that layer.
76//!
77//! This is an approximation outside NRT's monatomic definition. It is
78//! **this crate's own convention**; no published source is claimed for this
79//! particular averaging. For a monatomic layer it reduces exactly to the
80//! standard NRT evaluation. Treat NRT numbers for compounds as an exposure
81//! index that is comparable within this crate, not as a value that follows
82//! any compound-target standard.
83
84use crate::constants::{BOHR_RADIUS, COULOMB_E2};
85use crate::units::J_PER_EV;
86
87/// NRT displacement efficiency (Norgett, Robinson and Torrens 1975).
88pub const NRT_EFFICIENCY: f64 = 0.8;
89
90/// Lindhard reduced energy `ε` of a PKA of energy `t_ev` (`Z1`, mass `a1`
91/// u) in a target (`Z2`, `a2` u). `Z` and masses may be non-integer
92/// (averaged) values.
93pub fn lindhard_reduced_energy(t_ev: f64, z1: f64, a1: f64, z2: f64, a2: f64) -> f64 {
94 let a = 0.8853 * BOHR_RADIUS / (z1.powf(2.0 / 3.0) + z2.powf(2.0 / 3.0)).sqrt();
95 t_ev * J_PER_EV * a * a2 / (z1 * z2 * COULOMB_E2 * (a1 + a2))
96}
97
98/// Lindhard-Scharff electronic stopping coefficient `k` of the PKA in the
99/// target (see the module docs).
100pub fn lindhard_k(z1: f64, a1: f64, z2: f64, a2: f64) -> f64 {
101 0.0793 * z1.powf(2.0 / 3.0) * z2.sqrt() * (a1 + a2).powf(1.5)
102 / ((z1.powf(2.0 / 3.0) + z2.powf(2.0 / 3.0)).powf(0.75) * a1.powf(1.5) * a2.sqrt())
103}
104
105/// Robinson's fit `g(ε) = 3.4008 ε^(1/6) + 0.40244 ε^(3/4) + ε`.
106pub fn robinson_g(eps: f64) -> f64 {
107 3.4008 * eps.powf(1.0 / 6.0) + 0.40244 * eps.powf(0.75) + eps
108}
109
110/// Damage energy `T / (1 + k g(ε))`, eV, of a PKA of energy `t_ev`.
111/// Returns 0 for non-positive `t_ev`.
112pub fn damage_energy_ev(t_ev: f64, z1: f64, a1: f64, z2: f64, a2: f64) -> f64 {
113 if t_ev.is_nan() || t_ev <= 0.0 {
114 return 0.0;
115 }
116 let eps = lindhard_reduced_energy(t_ev, z1, a1, z2, a2);
117 t_ev / (1.0 + lindhard_k(z1, a1, z2, a2) * robinson_g(eps))
118}
119
120/// NRT displacements for damage energy `t_dam_ev` and threshold `e_d_ev`.
121pub fn nrt_displacements(t_dam_ev: f64, e_d_ev: f64) -> f64 {
122 if t_dam_ev < e_d_ev {
123 0.0
124 } else if t_dam_ev < 2.0 * e_d_ev / NRT_EFFICIENCY {
125 1.0
126 } else {
127 NRT_EFFICIENCY * t_dam_ev / (2.0 * e_d_ev)
128 }
129}
130
131/// Kinchin-Pease displacements for energy `energy_ev` and threshold
132/// `e_d_ev`.
133pub fn kinchin_pease(energy_ev: f64, e_d_ev: f64) -> f64 {
134 if energy_ev < e_d_ev {
135 0.0
136 } else if energy_ev < 2.0 * e_d_ev {
137 1.0
138 } else {
139 energy_ev / (2.0 * e_d_ev)
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn self_ion_forms_match_the_nrt_constants() {
149 for (z, a) in [(14.0, 28.0855), (26.0, 55.845), (74.0, 183.84)] {
150 let k = lindhard_k(z, a, z, a);
151 let k_nrt = 0.1337 * f64::powf(z, 1.0 / 6.0) * (z / a).sqrt();
152 assert!((k / k_nrt - 1.0).abs() < 3e-3, "k: {k} vs {k_nrt}");
153 let eps = lindhard_reduced_energy(1e4, z, a, z, a);
154 let eps_nrt = 1e4 / (86.931 * f64::powf(z, 7.0 / 3.0));
155 assert!(
156 (eps / eps_nrt - 1.0).abs() < 2e-4,
157 "eps: {eps} vs {eps_nrt}"
158 );
159 }
160 }
161
162 #[test]
163 fn damage_energy_is_below_t_and_tends_to_t_at_low_energy() {
164 let (z, a) = (26.0, 55.845);
165 let t = 1e5;
166 let td = damage_energy_ev(t, z, a, z, a);
167 assert!(td > 0.3 * t && td < t);
168 let low = damage_energy_ev(10.0, z, a, z, a);
169 assert!(low < 10.0 && low > 9.0);
170 assert_eq!(damage_energy_ev(0.0, z, a, z, a), 0.0);
171 }
172
173 #[test]
174 fn displacement_count_steps() {
175 let ed = 40.0;
176 assert_eq!(nrt_displacements(39.9, ed), 0.0);
177 assert_eq!(nrt_displacements(40.0, ed), 1.0);
178 assert_eq!(nrt_displacements(99.9, ed), 1.0);
179 assert_eq!(nrt_displacements(100.0, ed), 1.0);
180 assert!((nrt_displacements(1000.0, ed) - 10.0).abs() < 1e-12);
181 assert_eq!(kinchin_pease(79.9, ed), 1.0);
182 assert!((kinchin_pease(1000.0, ed) - 12.5).abs() < 1e-12);
183 }
184}