Skip to main content

endf_mat/
abundances.rs

1//! Natural isotopic abundances.
2//!
3//! IUPAC-recommended isotopic compositions of the elements (2016 values,
4//! from NIST Atomic Weights and Isotopic Compositions database). Covers
5//! 289 naturally occurring isotopes across 85 elements.
6//!
7//! Data source: <https://physics.nist.gov/cgi-bin/Compositions/stand_alone.pl>
8
9/// Natural isotopic abundance (mole fraction) for a given isotope.
10///
11/// Returns `None` for isotopes with no natural occurrence (synthetic elements
12/// such as Tc, Pm, or transuranic elements like Pu) or isotopes not in the
13/// IUPAC 2016 database. Callers requiring reactor/engineered material compositions
14/// (e.g. Pu-239 in fuel) must supply the abundance explicitly.
15///
16/// # Examples
17/// ```
18/// assert!((endf_mat::natural_abundance(92, 238).unwrap() - 0.992742).abs() < 1e-6);
19/// assert_eq!(endf_mat::natural_abundance(43, 99), None); // Tc is synthetic
20/// assert_eq!(endf_mat::natural_abundance(94, 239), None); // Pu has no natural abundance
21/// ```
22pub fn natural_abundance(z: u32, a: u32) -> Option<f64> {
23    NATURAL_ABUNDANCES
24        .binary_search_by(|&(tz, ta, _)| (tz, ta).cmp(&(z, a)))
25        .ok()
26        .map(|idx| NATURAL_ABUNDANCES[idx].2)
27}
28
29/// All naturally occurring isotopes for element Z, as `(A, fraction)` pairs.
30///
31/// Returns an empty `Vec` for synthetic elements (e.g., Tc, Pm).
32///
33/// # Examples
34/// ```
35/// let fe = endf_mat::natural_isotopes(26);
36/// assert_eq!(fe.len(), 4); // Fe-54, Fe-56, Fe-57, Fe-58
37/// let total: f64 = fe.iter().map(|(_, f)| f).sum();
38/// assert!((total - 1.0).abs() < 0.01);
39/// ```
40pub fn natural_isotopes(z: u32) -> Vec<(u32, f64)> {
41    NATURAL_ABUNDANCES
42        .iter()
43        .filter(|&&(tz, _, _)| tz == z)
44        .map(|&(_, a, frac)| (a, frac))
45        .collect()
46}
47
48// Natural isotopic abundances: (Z, A, fraction)
49// Source: NIST Atomic Weights and Isotopic Compositions (IUPAC 2016)
50// 289 entries covering 85 elements, sorted by (Z, A)
51#[rustfmt::skip]
52static NATURAL_ABUNDANCES: &[(u32, u32, f64)] = &[
53    // Hydrogen (H, Z=1)
54    (1, 1, 0.999885),
55    (1, 2, 0.000115),
56
57    // Helium (He, Z=2)
58    (2, 3, 0.00000134),
59    (2, 4, 0.999999),
60
61    // Lithium (Li, Z=3)
62    (3, 6, 0.0759),
63    (3, 7, 0.9241),
64
65    // Beryllium (Be, Z=4)
66    (4, 9, 1.0),
67
68    // Boron (B, Z=5)
69    (5, 10, 0.199),
70    (5, 11, 0.801),
71
72    // Carbon (C, Z=6)
73    (6, 12, 0.9893),
74    (6, 13, 0.0107),
75
76    // Nitrogen (N, Z=7)
77    (7, 14, 0.99636),
78    (7, 15, 0.00364),
79
80    // Oxygen (O, Z=8)
81    (8, 16, 0.99757),
82    (8, 17, 0.00038),
83    (8, 18, 0.00205),
84
85    // Fluorine (F, Z=9)
86    (9, 19, 1.0),
87
88    // Neon (Ne, Z=10)
89    (10, 20, 0.9048),
90    (10, 21, 0.0027),
91    (10, 22, 0.0925),
92
93    // Sodium (Na, Z=11)
94    (11, 23, 1.0),
95
96    // Magnesium (Mg, Z=12)
97    (12, 24, 0.7899),
98    (12, 25, 0.1),
99    (12, 26, 0.1101),
100
101    // Aluminum (Al, Z=13)
102    (13, 27, 1.0),
103
104    // Silicon (Si, Z=14)
105    (14, 28, 0.92223),
106    (14, 29, 0.04685),
107    (14, 30, 0.03092),
108
109    // Phosphorus (P, Z=15)
110    (15, 31, 1.0),
111
112    // Sulfur (S, Z=16)
113    (16, 32, 0.9499),
114    (16, 33, 0.0075),
115    (16, 34, 0.0425),
116    (16, 36, 0.0001),
117
118    // Chlorine (Cl, Z=17)
119    (17, 35, 0.7576),
120    (17, 37, 0.2424),
121
122    // Argon (Ar, Z=18)
123    (18, 36, 0.003336),
124    (18, 38, 0.000629),
125    (18, 40, 0.996035),
126
127    // Potassium (K, Z=19)
128    (19, 39, 0.932581),
129    (19, 40, 0.000117),
130    (19, 41, 0.067302),
131
132    // Calcium (Ca, Z=20)
133    (20, 40, 0.96941),
134    (20, 42, 0.00647),
135    (20, 43, 0.00135),
136    (20, 44, 0.02086),
137    (20, 46, 0.00004),
138    (20, 48, 0.00187),
139
140    // Scandium (Sc, Z=21)
141    (21, 45, 1.0),
142
143    // Titanium (Ti, Z=22)
144    (22, 46, 0.0825),
145    (22, 47, 0.0744),
146    (22, 48, 0.7372),
147    (22, 49, 0.0541),
148    (22, 50, 0.0518),
149
150    // Vanadium (V, Z=23)
151    (23, 50, 0.0025),
152    (23, 51, 0.9975),
153
154    // Chromium (Cr, Z=24)
155    (24, 50, 0.04345),
156    (24, 52, 0.83789),
157    (24, 53, 0.09501),
158    (24, 54, 0.02365),
159
160    // Manganese (Mn, Z=25)
161    (25, 55, 1.0),
162
163    // Iron (Fe, Z=26)
164    (26, 54, 0.05845),
165    (26, 56, 0.91754),
166    (26, 57, 0.02119),
167    (26, 58, 0.00282),
168
169    // Cobalt (Co, Z=27)
170    (27, 59, 1.0),
171
172    // Nickel (Ni, Z=28)
173    (28, 58, 0.68077),
174    (28, 60, 0.26223),
175    (28, 61, 0.011399),
176    (28, 62, 0.036346),
177    (28, 64, 0.009255),
178
179    // Copper (Cu, Z=29)
180    (29, 63, 0.6915),
181    (29, 65, 0.3085),
182
183    // Zinc (Zn, Z=30)
184    (30, 64, 0.4917),
185    (30, 66, 0.2773),
186    (30, 67, 0.0404),
187    (30, 68, 0.1845),
188    (30, 70, 0.0061),
189
190    // Gallium (Ga, Z=31)
191    (31, 69, 0.60108),
192    (31, 71, 0.39892),
193
194    // Germanium (Ge, Z=32)
195    (32, 70, 0.2057),
196    (32, 72, 0.2745),
197    (32, 73, 0.0775),
198    (32, 74, 0.365),
199    (32, 76, 0.0773),
200
201    // Arsenic (As, Z=33)
202    (33, 75, 1.0),
203
204    // Selenium (Se, Z=34)
205    (34, 74, 0.0089),
206    (34, 76, 0.0937),
207    (34, 77, 0.0763),
208    (34, 78, 0.2377),
209    (34, 80, 0.4961),
210    (34, 82, 0.0873),
211
212    // Bromine (Br, Z=35)
213    (35, 79, 0.5069),
214    (35, 81, 0.4931),
215
216    // Krypton (Kr, Z=36)
217    (36, 78, 0.00355),
218    (36, 80, 0.02286),
219    (36, 82, 0.11593),
220    (36, 83, 0.115),
221    (36, 84, 0.56987),
222    (36, 86, 0.17279),
223
224    // Rubidium (Rb, Z=37)
225    (37, 85, 0.7217),
226    (37, 87, 0.2783),
227
228    // Strontium (Sr, Z=38)
229    (38, 84, 0.0056),
230    (38, 86, 0.0986),
231    (38, 87, 0.07),
232    (38, 88, 0.8258),
233
234    // Yttrium (Y, Z=39)
235    (39, 89, 1.0),
236
237    // Zirconium (Zr, Z=40)
238    (40, 90, 0.5145),
239    (40, 91, 0.1122),
240    (40, 92, 0.1715),
241    (40, 94, 0.1738),
242    (40, 96, 0.028),
243
244    // Niobium (Nb, Z=41)
245    (41, 93, 1.0),
246
247    // Molybdenum (Mo, Z=42)
248    (42, 92, 0.1453),
249    (42, 94, 0.0915),
250    (42, 95, 0.1584),
251    (42, 96, 0.1667),
252    (42, 97, 0.096),
253    (42, 98, 0.2439),
254    (42, 100, 0.0982),
255
256    // Ruthenium (Ru, Z=44)
257    (44, 96, 0.0554),
258    (44, 98, 0.0187),
259    (44, 99, 0.1276),
260    (44, 100, 0.126),
261    (44, 101, 0.1706),
262    (44, 102, 0.3155),
263    (44, 104, 0.1862),
264
265    // Rhodium (Rh, Z=45)
266    (45, 103, 1.0),
267
268    // Palladium (Pd, Z=46)
269    (46, 102, 0.0102),
270    (46, 104, 0.1114),
271    (46, 105, 0.2233),
272    (46, 106, 0.2733),
273    (46, 108, 0.2646),
274    (46, 110, 0.1172),
275
276    // Silver (Ag, Z=47)
277    (47, 107, 0.51839),
278    (47, 109, 0.48161),
279
280    // Cadmium (Cd, Z=48)
281    (48, 106, 0.0125),
282    (48, 108, 0.0089),
283    (48, 110, 0.1249),
284    (48, 111, 0.128),
285    (48, 112, 0.2413),
286    (48, 113, 0.1222),
287    (48, 114, 0.2873),
288    (48, 116, 0.0749),
289
290    // Indium (In, Z=49)
291    (49, 113, 0.0429),
292    (49, 115, 0.9571),
293
294    // Tin (Sn, Z=50)
295    (50, 112, 0.0097),
296    (50, 114, 0.0066),
297    (50, 115, 0.0034),
298    (50, 116, 0.1454),
299    (50, 117, 0.0768),
300    (50, 118, 0.2422),
301    (50, 119, 0.0859),
302    (50, 120, 0.3258),
303    (50, 122, 0.0463),
304    (50, 124, 0.0579),
305
306    // Antimony (Sb, Z=51)
307    (51, 121, 0.5721),
308    (51, 123, 0.4279),
309
310    // Tellurium (Te, Z=52)
311    (52, 120, 0.0009),
312    (52, 122, 0.0255),
313    (52, 123, 0.0089),
314    (52, 124, 0.0474),
315    (52, 125, 0.0707),
316    (52, 126, 0.1884),
317    (52, 128, 0.3174),
318    (52, 130, 0.3408),
319
320    // Iodine (I, Z=53)
321    (53, 127, 1.0),
322
323    // Xenon (Xe, Z=54)
324    (54, 124, 0.000952),
325    (54, 126, 0.00089),
326    (54, 128, 0.019102),
327    (54, 129, 0.264006),
328    (54, 130, 0.04071),
329    (54, 131, 0.212324),
330    (54, 132, 0.269086),
331    (54, 134, 0.104357),
332    (54, 136, 0.088573),
333
334    // Cesium (Cs, Z=55)
335    (55, 133, 1.0),
336
337    // Barium (Ba, Z=56)
338    (56, 130, 0.00106),
339    (56, 132, 0.00101),
340    (56, 134, 0.02417),
341    (56, 135, 0.06592),
342    (56, 136, 0.07854),
343    (56, 137, 0.11232),
344    (56, 138, 0.71698),
345
346    // Lanthanum (La, Z=57)
347    (57, 138, 0.0008881),
348    (57, 139, 0.999112),
349
350    // Cerium (Ce, Z=58)
351    (58, 136, 0.00185),
352    (58, 138, 0.00251),
353    (58, 140, 0.8845),
354    (58, 142, 0.11114),
355
356    // Praseodymium (Pr, Z=59)
357    (59, 141, 1.0),
358
359    // Neodymium (Nd, Z=60)
360    (60, 142, 0.27152),
361    (60, 143, 0.12174),
362    (60, 144, 0.23798),
363    (60, 145, 0.08293),
364    (60, 146, 0.17189),
365    (60, 148, 0.05756),
366    (60, 150, 0.05638),
367
368    // Samarium (Sm, Z=62)
369    (62, 144, 0.0307),
370    (62, 147, 0.1499),
371    (62, 148, 0.1124),
372    (62, 149, 0.1382),
373    (62, 150, 0.0738),
374    (62, 152, 0.2675),
375    (62, 154, 0.2275),
376
377    // Europium (Eu, Z=63)
378    (63, 151, 0.4781),
379    (63, 153, 0.5219),
380
381    // Gadolinium (Gd, Z=64)
382    (64, 152, 0.002),
383    (64, 154, 0.0218),
384    (64, 155, 0.148),
385    (64, 156, 0.2047),
386    (64, 157, 0.1565),
387    (64, 158, 0.2484),
388    (64, 160, 0.2186),
389
390    // Terbium (Tb, Z=65)
391    (65, 159, 1.0),
392
393    // Dysprosium (Dy, Z=66)
394    (66, 156, 0.00056),
395    (66, 158, 0.00095),
396    (66, 160, 0.02329),
397    (66, 161, 0.18889),
398    (66, 162, 0.25475),
399    (66, 163, 0.24896),
400    (66, 164, 0.2826),
401
402    // Holmium (Ho, Z=67)
403    (67, 165, 1.0),
404
405    // Erbium (Er, Z=68)
406    (68, 162, 0.00139),
407    (68, 164, 0.01601),
408    (68, 166, 0.33503),
409    (68, 167, 0.22869),
410    (68, 168, 0.26978),
411    (68, 170, 0.1491),
412
413    // Thulium (Tm, Z=69)
414    (69, 169, 1.0),
415
416    // Ytterbium (Yb, Z=70)
417    (70, 168, 0.00123),
418    (70, 170, 0.02982),
419    (70, 171, 0.1409),
420    (70, 172, 0.2168),
421    (70, 173, 0.16103),
422    (70, 174, 0.32026),
423    (70, 176, 0.12996),
424
425    // Lutetium (Lu, Z=71)
426    (71, 175, 0.97401),
427    (71, 176, 0.02599),
428
429    // Hafnium (Hf, Z=72)
430    (72, 174, 0.0016),
431    (72, 176, 0.0526),
432    (72, 177, 0.186),
433    (72, 178, 0.2728),
434    (72, 179, 0.1362),
435    (72, 180, 0.3508),
436
437    // Tantalum (Ta, Z=73)
438    (73, 180, 0.0001201),
439    (73, 181, 0.99988),
440
441    // Tungsten (W, Z=74)
442    (74, 180, 0.0012),
443    (74, 182, 0.265),
444    (74, 183, 0.1431),
445    (74, 184, 0.3064),
446    (74, 186, 0.2843),
447
448    // Rhenium (Re, Z=75)
449    (75, 185, 0.374),
450    (75, 187, 0.626),
451
452    // Osmium (Os, Z=76)
453    (76, 184, 0.0002),
454    (76, 186, 0.0159),
455    (76, 187, 0.0196),
456    (76, 188, 0.1324),
457    (76, 189, 0.1615),
458    (76, 190, 0.2626),
459    (76, 192, 0.4078),
460
461    // Iridium (Ir, Z=77)
462    (77, 191, 0.373),
463    (77, 193, 0.627),
464
465    // Platinum (Pt, Z=78)
466    (78, 190, 0.00012),
467    (78, 192, 0.00782),
468    (78, 194, 0.3286),
469    (78, 195, 0.3378),
470    (78, 196, 0.2521),
471    (78, 198, 0.07356),
472
473    // Gold (Au, Z=79)
474    (79, 197, 1.0),
475
476    // Mercury (Hg, Z=80)
477    (80, 196, 0.0015),
478    (80, 198, 0.0997),
479    (80, 199, 0.1687),
480    (80, 200, 0.231),
481    (80, 201, 0.1318),
482    (80, 202, 0.2986),
483    (80, 204, 0.0687),
484
485    // Thallium (Tl, Z=81)
486    (81, 203, 0.2952),
487    (81, 205, 0.7048),
488
489    // Lead (Pb, Z=82)
490    (82, 204, 0.014),
491    (82, 206, 0.241),
492    (82, 207, 0.221),
493    (82, 208, 0.524),
494
495    // Bismuth (Bi, Z=83)
496    (83, 209, 1.0),
497
498    // Thorium (Th, Z=90)
499    (90, 232, 1.0),
500
501    // Protactinium (Pa, Z=91)
502    (91, 231, 1.0),
503
504    // Uranium (U, Z=92)
505    (92, 234, 0.000054),
506    (92, 235, 0.007204),
507    (92, 238, 0.992742),
508
509];
510
511#[cfg(test)]
512mod tests {
513    use super::*;
514
515    #[test]
516    fn test_natural_abundance_uranium() {
517        assert!((natural_abundance(92, 238).unwrap() - 0.992742).abs() < 1e-6);
518        assert!((natural_abundance(92, 235).unwrap() - 0.007204).abs() < 1e-6);
519        assert!((natural_abundance(92, 234).unwrap() - 0.000054).abs() < 1e-6);
520    }
521
522    #[test]
523    fn test_natural_abundance_iron() {
524        assert!((natural_abundance(26, 56).unwrap() - 0.91754).abs() < 1e-5);
525        assert!((natural_abundance(26, 54).unwrap() - 0.05845).abs() < 1e-5);
526    }
527
528    #[test]
529    fn test_natural_abundance_synthetic() {
530        // Technetium (Z=43) has no stable isotopes
531        assert_eq!(natural_abundance(43, 99), None);
532        // Promethium (Z=61) has no stable isotopes
533        assert_eq!(natural_abundance(61, 147), None);
534        // Plutonium: synthetic, no natural abundance (reactor compositions must be supplied explicitly)
535        assert_eq!(natural_abundance(94, 239), None);
536    }
537
538    #[test]
539    fn test_natural_abundance_unknown() {
540        assert_eq!(natural_abundance(200, 400), None);
541    }
542
543    #[test]
544    fn test_natural_isotopes_iron() {
545        let fe = natural_isotopes(26);
546        assert_eq!(fe.len(), 4);
547        let total: f64 = fe.iter().map(|(_, f)| f).sum();
548        assert!((total - 1.0).abs() < 0.001);
549    }
550
551    #[test]
552    fn test_natural_isotopes_tin() {
553        let sn = natural_isotopes(50);
554        assert_eq!(sn.len(), 10); // 10 stable tin isotopes
555        let total: f64 = sn.iter().map(|(_, f)| f).sum();
556        assert!((total - 1.0).abs() < 0.001);
557    }
558
559    #[test]
560    fn test_natural_isotopes_tungsten() {
561        let w = natural_isotopes(74);
562        assert_eq!(w.len(), 5); // W-180, W-182, W-183, W-184, W-186
563    }
564
565    #[test]
566    fn test_natural_isotopes_synthetic() {
567        let tc = natural_isotopes(43);
568        assert!(tc.is_empty());
569    }
570
571    #[test]
572    fn test_natural_isotopes_mono_isotopic() {
573        // Gold: only Au-197
574        let au = natural_isotopes(79);
575        assert_eq!(au.len(), 1);
576        assert_eq!(au[0].0, 197);
577        assert!((au[0].1 - 1.0).abs() < 1e-10);
578    }
579
580    #[test]
581    fn test_table_size() {
582        assert_eq!(NATURAL_ABUNDANCES.len(), 288);
583    }
584
585    #[test]
586    fn test_table_sorted() {
587        for i in 1..NATURAL_ABUNDANCES.len() {
588            let (z1, a1, _) = NATURAL_ABUNDANCES[i - 1];
589            let (z2, a2, _) = NATURAL_ABUNDANCES[i];
590            assert!(
591                (z1, a1) < (z2, a2),
592                "Table not sorted at index {}: ({}, {}) >= ({}, {})",
593                i,
594                z1,
595                a1,
596                z2,
597                a2
598            );
599        }
600    }
601}