lindhard/elements.rs
1//! Element table, Z = 1..=92.
2//!
3//! The table is `const` data with no I/O, so table builders and a WASM front
4//! end can use it directly. Provenance is also recorded in
5//! `docs/data-provenance.md`.
6//!
7//! # Sources
8//!
9//! * **Atomic weight** (`atomic_weight`, g/mol, i.e. relative atomic mass):
10//! the CIAAW / IUPAC standard atomic weights as tabulated at
11//! <https://www.ciaaw.org/atomic-weights.htm> ("Standard Atomic Weights
12//! 2024": the 2021 report, T. Prohaska et al., "Standard atomic weights of
13//! the elements 2021", Pure Appl. Chem. 94, 573 (2022),
14//! doi:10.1515/pac-2019-0603, plus the CIAAW 2024 revisions of Gd, Lu and
15//! Zr). For the 14 elements whose standard weight is an interval (H, Li, B,
16//! C, N, O, Mg, Si, S, Cl, Ar, Br, Tl, Pb) the single value is the
17//! abridged value of <https://www.ciaaw.org/abridged-atomic-weights.htm>,
18//! which the 2021 report (Table 1, column 7) says replaces, and equals, the
19//! former conventional atomic weights; for H that value is printed 1.0080,
20//! the former conventional 1.008. Elements with no standard atomic weight
21//! (Tc, Pm, Po, At, Rn, Fr, Ra, Ac) carry the mass number of their
22//! longest-lived isotope as listed by CIAAW at
23//! <https://www.ciaaw.org/radioactive-elements.htm> (from NUBASE2020,
24//! F. G. Kondev et al., Chin. Phys. C 45, 030001 (2021)) and have
25//! `weight_is_mass_number == true`; that is a convention, not a measurement
26//! of a natural mixture. For Tc that page lists 97 and 98 as equally
27//! long-lived; 98 is used. All 92 entries checked against these pages on
28//! 2026-10-06 (#15).
29//! * **Density** (`density_g_cm3`): density of the solid, g/cm³, as printed
30//! in A. Thompson et al., *X-Ray Data Booklet*, LBNL/PUB-490 Rev. 3
31//! (Lawrence Berkeley National Laboratory, October 2009), section 5.2,
32//! Table 5-2 "Properties of the elements", pp. 5-5 to 5-8
33//! (<https://xdb.lbl.gov/xdb-new.pdf>, PDF pages 153-156). Section 5.2 says
34//! the data were taken mostly from D. R. Lide (ed.), *CRC Handbook of
35//! Chemistry and Physics*, 80th ed. (CRC Press, 1999), and that densities
36//! are specific gravities at 20 °C unless a superscript gives another
37//! temperature (25 °C for Si, Sc, Ni, Zn, Ge, Y, La, Ce, Nd, Pm, Sm, Eu,
38//! Gd, Dy, Ho, Er, Tm, Lu; V 18.7 °C, Ga 29.6 °C, Ir 17 °C). Tc and Pa are
39//! marked "calculated" there, U "~18.95". The table names no allotrope
40//! except graphite for C. All populated entries checked on 2026-10-07
41//! (#15; the PDF's SHA-256 is recorded in `docs/data-provenance.md`), and
42//! 37 values set to the printed ones. Four entries the booklet does not pin
43//! were checked on 2026-10-07 (#15) against other open primary
44//! compilations, all U.S. government reports:
45//! - Cr 7.20 (was 7.15, which lay **outside** the booklet's printed range
46//! 7.18-7.20): the upper end of that range, and the density "calculated
47//! from the NBS lattice constant", 7.200 at 25 °C, in H. E. Swanson,
48//! R. K. Fuyat and G. M. Ugrinic, *Standard X-ray Diffraction Powder
49//! Patterns*, NBS Circular 539, Vol. V (1955), p. 20
50//! (<https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular539v5.pdf>).
51//! This is an X-ray (crystal) density.
52//! - Au 19.30: the booklet prints "~19.3"; NBS Circular 539, Vol. I
53//! (Swanson and Tatge, 1953), p. 33, prints 19.302 at 25 °C from its
54//! lattice constant
55//! (<https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular539v1.pdf>).
56//! - Ra 5.0: no density in the booklet; "a specific gravity of
57//! approximately 5.0" in H. W. Kirby and M. L. Salutsky, *The
58//! Radiochemistry of Radium*, NAS-NS 3057 (1964), p. 3, citing the
59//! International Critical Tables (<https://www.osti.gov/servlets/purl/4560824>).
60//! - Ac 10.07: no density in the booklet; the crystal density of fcc Ac
61//! metal (a = 5.311 Å), Table 4, p. 11, of J. D. Farr, A. L. Giorgi,
62//! M. G. Bowman and R. K. Money, "The crystal structure of actinium metal
63//! and actinium hydride", Los Alamos report LA-1545 (1953)
64//! (<https://www.osti.gov/servlets/purl/4397640>), published as
65//! J. Inorg. Nucl. Chem. 18, 42 (1961).
66//!
67//! Still **not verified** to their stored digits: C 2.267 (printed as the
68//! range 1.9-2.3) and Mn 7.44 (the upper end of the printed range
69//! 7.21-7.44). For Mn, the X-ray density of alpha-Mn, "(calculated) 7.475
70//! g/cm³" at 25 °C (a = 8.9121 Å, Z = 58), is printed in M. C. Morris
71//! et al., *Standard X-ray Diffraction Powder Patterns*, NBS Monograph 25,
72//! Section 17 (1980), p. 50
73//! (<https://nvlpubs.nist.gov/nistpubs/Legacy/MONO/nbsmonograph25-17.pdf>,
74//! checked 2026-10-07, #15). It lies above the booklet's range, so unlike
75//! Cr it does not pin a value inside that range; 7.44 is kept, unverified.
76//! Si keeps 2.329: the booklet prints 2.33
77//! at 25 °C, and 2.329 is the crystal density `M(Si) / V_m(Si)` from the
78//! CODATA 2022 "molar volume of silicon" (1.205 883 199e-5 m³/mol,
79//! <https://physics.nist.gov/cuu/Constants/Table/allascii.txt>) for any
80//! weight in the standard interval [28.084, 28.086]. The specific gravities
81//! are used as g/cm³; the booklet does not state the reference water
82//! state. Later CRC editions may differ; none was seen. It is `None` where
83//! no solid density at ambient conditions exists (gases: H, He, N, O, F,
84//! Ne, Cl, Ar, Kr, Xe, Rn; liquids: Br, Hg; no data: At, Fr). Densities are
85//! a default for a pure element only; compounds need an explicit density
86//! override.
87//! * **Sublimation (cohesive) enthalpy** (`default_surface_binding_ev`): where
88//! a measured value is tabulated, the cohesive energy per atom (eV) of
89//! C. Kittel, *Introduction to Solid State Physics*, 8th ed. (Wiley, 2005),
90//! chapter 3 (cohesive energies of the elements), which is the enthalpy of
91//! atomization at 0 K. Using it as the surface binding energy `E_s` is the
92//! usual binary-collision convention (Sigmund, Phys. Rev. 184, 383 (1969)),
93//! **not** a measured surface barrier. Elements left `None` must have
94//! `E_s` set explicitly by the user rather than have one invented.
95//! **Verified** on 2026-10-07 (#15) against Table 1 "Cohesive energies",
96//! chapter 3, p. 50 of the 8th edition (Wiley, 2005, ISBN 0-471-41526-X;
97//! edition, publisher and ISBN read from the title and copyright pages of
98//! the copy used, see `docs/data-provenance.md`). The table prints each
99//! element in kJ/mol, eV/atom and kcal/mol ("energy required to form
100//! separated neutral atoms in their ground electronic state from the solid
101//! at 0 K at 1 atm. The data were supplied by Prof. Leo Brewer."); the
102//! eV/atom value is used, to the digits printed. All 35 populated entries
103//! were compared: 33 equal the printed value, and Na (was 1.11) and K (was
104//! 0.93) were set to the printed 1.113 and 0.934. The table also prints
105//! values for elements left `None` here (among them N, O, P, S, Sc, Mn,
106//! Se, the rare gases and most lanthanides and actinides); those defaults
107//! stay unset, since #15 audits the populated entries only. Earlier
108//! cross-check, kept as a test: L. Brewer, "The cohesive energies of the
109//! elements", report LBL-3720 (1975), Table I, kcal/gram-atom
110//! (<https://www.osti.gov/servlets/purl/7187973>).
111//! * **Displacement energy** (`default_displacement_ev`): threshold
112//! displacement energies as recommended in ASTM E521 ("Standard Practice for
113//! Investigating the Effects of Neutron Radiation Damage Using Charged-Particle
114//! Irradiation"), the convention underlying the NRT damage model
115//! (Norgett, Robinson, Torrens, Nucl. Eng. Des. 33, 50 (1975)). These are
116//! conventions for damage accounting, not measured properties of a specific
117//! crystal direction. `None` where no convention value is recorded.
118//! **Not verified**: the standard could not be reached (see
119//! `docs/data-provenance.md`). The values are also not those of the
120//! Greenwood-Smither compilation used by SPECTER and NJOY (they differ for
121//! Al, Ti, Cu, Nb and Ag), which is not E521 either.
122
123/// One element of the periodic table with its default data.
124#[derive(Debug, Clone, Copy, PartialEq)]
125pub struct Element {
126 /// Atomic number.
127 pub z: u8,
128 /// Element symbol, e.g. `"Si"`.
129 pub symbol: &'static str,
130 /// Standard (conventional) atomic weight in g/mol; see the module docs.
131 pub atomic_weight: f64,
132 /// True if `atomic_weight` is the mass number of the longest-lived isotope
133 /// because IUPAC publishes no standard atomic weight for the element.
134 pub weight_is_mass_number: bool,
135 /// Default solid density in g/cm³, if one is defined; see the module docs.
136 pub density_g_cm3: Option<f64>,
137 /// Default surface binding energy `E_s` in eV (cohesive-energy convention).
138 pub default_surface_binding_ev: Option<f64>,
139 /// Default displacement energy `E_d` in eV (ASTM E521 convention).
140 pub default_displacement_ev: Option<f64>,
141}
142
143impl Element {
144 /// Atomic mass in kg: `atomic_weight` times the atomic mass constant.
145 ///
146 /// (The molar-mass constant differs from 1 g/mol by about 1e-9, CODATA
147 /// 2022 `M_u` = 1.000 000 001 05e-3 kg/mol; the standard-atomic-weight
148 /// uncertainty is far larger.)
149 pub fn mass_kg(&self) -> f64 {
150 self.atomic_weight * crate::constants::ATOMIC_MASS_UNIT
151 }
152
153 /// Default density in kg/m³, if defined.
154 pub fn density_kg_m3(&self) -> Option<f64> {
155 self.density_g_cm3.map(crate::units::g_cm3_to_kg_m3)
156 }
157}
158
159/// Number of elements in the table.
160pub const NUM_ELEMENTS: usize = 92;
161
162/// Look an element up by atomic number (1..=92).
163pub fn element(z: u8) -> Option<&'static Element> {
164 if z == 0 {
165 return None;
166 }
167 ELEMENTS.get(usize::from(z) - 1)
168}
169
170/// Look an element up by case-sensitive symbol (`"Si"`, not `"si"`).
171pub fn element_by_symbol(symbol: &str) -> Option<&'static Element> {
172 ELEMENTS.iter().find(|e| e.symbol == symbol)
173}
174
175const fn el(
176 z: u8,
177 symbol: &'static str,
178 atomic_weight: f64,
179 weight_is_mass_number: bool,
180 density_g_cm3: Option<f64>,
181 default_surface_binding_ev: Option<f64>,
182 default_displacement_ev: Option<f64>,
183) -> Element {
184 Element {
185 z,
186 symbol,
187 atomic_weight,
188 weight_is_mass_number,
189 density_g_cm3,
190 default_surface_binding_ev,
191 default_displacement_ev,
192 }
193}
194
195/// The table, indexed by `Z - 1`.
196// Sn's cohesive energy (3.14 eV) is data, not pi.
197#[allow(clippy::approx_constant)]
198pub static ELEMENTS: [Element; NUM_ELEMENTS] = [
199 el(1, "H", 1.008, false, None, None, None),
200 el(2, "He", 4.002602, false, None, None, None),
201 el(3, "Li", 6.94, false, Some(0.534), Some(1.63), None),
202 el(4, "Be", 9.0121831, false, Some(1.848), Some(3.32), None),
203 el(5, "B", 10.81, false, Some(2.34), Some(5.81), None),
204 el(6, "C", 12.011, false, Some(2.267), Some(7.37), None),
205 el(7, "N", 14.007, false, None, None, None),
206 el(8, "O", 15.999, false, None, None, None),
207 el(9, "F", 18.998403162, false, None, None, None),
208 el(10, "Ne", 20.1797, false, None, None, None),
209 el(11, "Na", 22.98976928, false, Some(0.971), Some(1.113), None),
210 el(12, "Mg", 24.305, false, Some(1.738), Some(1.51), None),
211 el(
212 13,
213 "Al",
214 26.9815384,
215 false,
216 Some(2.6989),
217 Some(3.39),
218 Some(25.0),
219 ),
220 el(14, "Si", 28.085, false, Some(2.329), Some(4.63), None),
221 el(15, "P", 30.973761998, false, Some(1.82), None, None),
222 el(16, "S", 32.06, false, Some(2.07), None, None),
223 el(17, "Cl", 35.45, false, None, None, None),
224 el(18, "Ar", 39.95, false, None, None, None),
225 el(19, "K", 39.0983, false, Some(0.862), Some(0.934), None),
226 el(20, "Ca", 40.078, false, Some(1.55), Some(1.84), None),
227 el(21, "Sc", 44.955907, false, Some(2.989), None, None),
228 el(22, "Ti", 47.867, false, Some(4.54), Some(4.85), Some(30.0)),
229 el(23, "V", 50.9415, false, Some(6.11), Some(5.31), Some(40.0)),
230 el(24, "Cr", 51.9961, false, Some(7.20), Some(4.1), Some(40.0)),
231 el(25, "Mn", 54.938043, false, Some(7.44), None, None),
232 el(26, "Fe", 55.845, false, Some(7.874), Some(4.28), Some(40.0)),
233 el(
234 27,
235 "Co",
236 58.933194,
237 false,
238 Some(8.90),
239 Some(4.39),
240 Some(40.0),
241 ),
242 el(
243 28,
244 "Ni",
245 58.6934,
246 false,
247 Some(8.902),
248 Some(4.44),
249 Some(40.0),
250 ),
251 el(29, "Cu", 63.546, false, Some(8.96), Some(3.49), Some(30.0)),
252 el(30, "Zn", 65.38, false, Some(7.133), Some(1.35), None),
253 el(31, "Ga", 69.723, false, Some(5.904), Some(2.81), None),
254 el(32, "Ge", 72.630, false, Some(5.323), Some(3.85), None),
255 el(33, "As", 74.921595, false, Some(5.73), Some(2.96), None),
256 el(34, "Se", 78.971, false, Some(4.79), None, None),
257 el(35, "Br", 79.904, false, None, None, None),
258 el(36, "Kr", 83.798, false, None, None, None),
259 el(37, "Rb", 85.4678, false, Some(1.532), None, None),
260 el(38, "Sr", 87.62, false, Some(2.54), None, None),
261 el(39, "Y", 88.905838, false, Some(4.469), None, None),
262 el(40, "Zr", 91.222, false, Some(6.506), Some(6.25), Some(40.0)),
263 el(
264 41,
265 "Nb",
266 92.90637,
267 false,
268 Some(8.57),
269 Some(7.57),
270 Some(60.0),
271 ),
272 el(42, "Mo", 95.95, false, Some(10.22), Some(6.82), Some(60.0)),
273 el(43, "Tc", 98.0, true, Some(11.50), None, None),
274 el(44, "Ru", 101.07, false, Some(12.41), None, None),
275 el(45, "Rh", 102.90549, false, Some(12.41), None, None),
276 el(46, "Pd", 106.42, false, Some(12.02), Some(3.89), None),
277 el(
278 47,
279 "Ag",
280 107.8682,
281 false,
282 Some(10.50),
283 Some(2.95),
284 Some(30.0),
285 ),
286 el(48, "Cd", 112.414, false, Some(8.65), Some(1.16), None),
287 el(49, "In", 114.818, false, Some(7.31), Some(2.52), None),
288 el(50, "Sn", 118.710, false, Some(7.31), Some(3.14), None),
289 el(51, "Sb", 121.760, false, Some(6.691), Some(2.75), None),
290 el(52, "Te", 127.60, false, Some(6.24), None, None),
291 el(53, "I", 126.90447, false, Some(4.93), None, None),
292 el(54, "Xe", 131.293, false, None, None, None),
293 el(55, "Cs", 132.90545196, false, Some(1.873), None, None),
294 el(56, "Ba", 137.327, false, Some(3.5), None, None),
295 el(57, "La", 138.90547, false, Some(6.145), None, None),
296 el(58, "Ce", 140.116, false, Some(6.770), None, None),
297 el(59, "Pr", 140.90766, false, Some(6.773), None, None),
298 el(60, "Nd", 144.242, false, Some(7.008), None, None),
299 el(61, "Pm", 145.0, true, Some(7.264), None, None),
300 el(62, "Sm", 150.36, false, Some(7.52), None, None),
301 el(63, "Eu", 151.964, false, Some(5.244), None, None),
302 el(64, "Gd", 157.249, false, Some(7.901), None, None),
303 el(65, "Tb", 158.925354, false, Some(8.23), None, None),
304 el(66, "Dy", 162.500, false, Some(8.551), None, None),
305 el(67, "Ho", 164.930329, false, Some(8.795), None, None),
306 el(68, "Er", 167.259, false, Some(9.066), None, None),
307 el(69, "Tm", 168.934219, false, Some(9.321), None, None),
308 el(70, "Yb", 173.045, false, Some(6.966), None, None),
309 el(71, "Lu", 174.96669, false, Some(9.841), None, None),
310 el(72, "Hf", 178.486, false, Some(13.31), None, None),
311 el(
312 73,
313 "Ta",
314 180.94788,
315 false,
316 Some(16.654),
317 Some(8.1),
318 Some(90.0),
319 ),
320 el(74, "W", 183.84, false, Some(19.3), Some(8.9), Some(90.0)),
321 el(75, "Re", 186.207, false, Some(21.02), None, None),
322 el(76, "Os", 190.23, false, Some(22.57), None, None),
323 el(77, "Ir", 192.217, false, Some(22.42), None, None),
324 el(78, "Pt", 195.084, false, Some(21.45), Some(5.84), None),
325 el(
326 79,
327 "Au",
328 196.966570,
329 false,
330 Some(19.30),
331 Some(3.81),
332 Some(30.0),
333 ),
334 el(80, "Hg", 200.592, false, None, None, None),
335 el(81, "Tl", 204.38, false, Some(11.85), None, None),
336 el(82, "Pb", 207.2, false, Some(11.35), Some(2.03), Some(25.0)),
337 el(83, "Bi", 208.98040, false, Some(9.747), None, None),
338 el(84, "Po", 209.0, true, Some(9.32), None, None),
339 el(85, "At", 210.0, true, None, None, None),
340 el(86, "Rn", 222.0, true, None, None, None),
341 el(87, "Fr", 223.0, true, None, None, None),
342 el(88, "Ra", 226.0, true, Some(5.0), None, None),
343 el(89, "Ac", 227.0, true, Some(10.07), None, None),
344 el(90, "Th", 232.0377, false, Some(11.72), None, None),
345 el(91, "Pa", 231.03588, false, Some(15.37), None, None),
346 el(92, "U", 238.02891, false, Some(18.95), None, None),
347];
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn table_is_ordered_and_complete() {
355 for (i, e) in ELEMENTS.iter().enumerate() {
356 assert_eq!(usize::from(e.z), i + 1);
357 assert!(e.atomic_weight > 0.9);
358 if let Some(d) = e.density_g_cm3 {
359 assert!(d > 0.0 && d < 30.0);
360 }
361 }
362 assert_eq!(element(14).unwrap().symbol, "Si");
363 assert_eq!(element(92).unwrap().symbol, "U");
364 assert!(element(0).is_none() && element(93).is_none());
365 assert_eq!(element_by_symbol("Ga").unwrap().z, 31);
366 assert!(element_by_symbol("Xx").is_none());
367 }
368
369 #[test]
370 fn mass_number_flag_is_exactly_the_elements_without_standard_weight() {
371 // Tc, Pm, Po, At, Rn, Fr, Ra, Ac have no CIAAW standard atomic weight.
372 let flagged: Vec<u8> = ELEMENTS
373 .iter()
374 .filter(|e| e.weight_is_mass_number)
375 .map(|e| e.z)
376 .collect();
377 assert_eq!(flagged, [43, 61, 84, 85, 86, 87, 88, 89]);
378 // A mass number is an integer.
379 for e in ELEMENTS.iter().filter(|e| e.weight_is_mass_number) {
380 assert_eq!(e.atomic_weight, e.atomic_weight.trunc());
381 }
382 }
383
384 #[test]
385 fn symbols_are_unique() {
386 for (i, a) in ELEMENTS.iter().enumerate() {
387 for b in &ELEMENTS[i + 1..] {
388 assert_ne!(a.symbol, b.symbol);
389 }
390 }
391 }
392
393 fn by(symbol: &str) -> &'static Element {
394 element_by_symbol(symbol).unwrap()
395 }
396
397 /// Standard atomic weights as printed by CIAAW, "Standard Atomic Weights
398 /// 2024", <https://www.ciaaw.org/atomic-weights.htm> (read 2026-10-06),
399 /// to the digits printed there. For interval elements the value is the
400 /// conventional (abridged) one of
401 /// <https://www.ciaaw.org/abridged-atomic-weights.htm>. That value need
402 /// not lie inside the interval (O: 15.999 against [15.999 03, 15.999 77]);
403 /// the 2021 report (Prohaska et al. 2022, text above Table 1) defines its
404 /// printed uncertainty as the smallest symmetric one covering the
405 /// interval, so the check is `value +- uncertainty` contains the interval.
406 #[test]
407 fn atomic_weights_match_ciaaw_spot_values() {
408 // Single-valued standard weights, value(uncertainty) as printed.
409 for (sym, w) in [
410 ("P", 30.973_761_998), // 30.973 761 998(5)
411 ("Ga", 69.723), // 69.723(1)
412 ("As", 74.921_595), // 74.921 595(6)
413 ("Au", 196.966_570), // 196.966 570(4)
414 // Corrected in #15 (were 44.955908, 88.90584, 91.224, 157.25, 174.9668).
415 ("Sc", 44.955_907), // 44.955 907(4)
416 ("Y", 88.905_838), // 88.905 838(2)
417 ("Zr", 91.222), // 91.222(3), CIAAW 2024 revision
418 ("Gd", 157.249), // 157.249(2), CIAAW 2024 revision
419 ("Lu", 174.966_69), // 174.966 69(5), CIAAW 2024 revision
420 ] {
421 assert_eq!(by(sym).atomic_weight, w, "{sym}");
422 assert!(!by(sym).weight_is_mass_number, "{sym}");
423 }
424 // Interval elements: (conventional value +- its abridged uncertainty,
425 // standard-weight interval).
426 for (sym, w, u, lo, hi) in [
427 ("Si", 28.085, 0.001, 28.084, 28.086),
428 ("O", 15.999, 0.001, 15.999_03, 15.999_77),
429 ("B", 10.81, 0.02, 10.806, 10.821),
430 ("Ar", 39.95, 0.16, 39.792, 39.963),
431 ("Pb", 207.2, 1.1, 206.14, 207.94),
432 ] {
433 let e = by(sym);
434 assert_eq!(e.atomic_weight, w, "{sym}");
435 assert!(w - u <= lo && hi <= w + u, "{sym}");
436 assert!(!e.weight_is_mass_number, "{sym}");
437 }
438 }
439
440 /// Mass numbers of the longest-lived isotopes, CIAAW "Radioactive
441 /// elements", <https://www.ciaaw.org/radioactive-elements.htm> (NUBASE2020;
442 /// read 2026-10-06). Tc is listed with 97 and 98 as equally long-lived.
443 #[test]
444 fn mass_number_fallbacks_match_ciaaw() {
445 for (sym, a) in [
446 ("Tc", 98.0),
447 ("Pm", 145.0),
448 ("Po", 209.0),
449 ("At", 210.0),
450 ("Rn", 222.0),
451 ("Fr", 223.0),
452 ("Ra", 226.0),
453 ("Ac", 227.0),
454 ] {
455 assert_eq!(by(sym).atomic_weight, a, "{sym}");
456 assert!(by(sym).weight_is_mass_number, "{sym}");
457 }
458 }
459
460 /// `E_s` against the cited source: C. Kittel, *Introduction to Solid State
461 /// Physics*, 8th ed. (Wiley, 2005), ch. 3, Table 1 "Cohesive energies",
462 /// p. 50, eV/atom row, to the digits printed (read 2026-10-07, #15).
463 /// Every populated entry is listed, so a new or changed default fails
464 /// here until it is checked against the table.
465 #[test]
466 // Sn's cohesive energy (3.14 eV) is data, not pi.
467 #[allow(clippy::approx_constant)]
468 fn surface_binding_matches_kittel_8th_ed_table_1() {
469 let kittel: [(&str, f64); 35] = [
470 ("Li", 1.63),
471 ("Be", 3.32),
472 ("B", 5.81),
473 ("C", 7.37),
474 ("Na", 1.113), // corrected in #15 (was 1.11)
475 ("Mg", 1.51),
476 ("Al", 3.39),
477 ("Si", 4.63),
478 ("K", 0.934), // corrected in #15 (was 0.93)
479 ("Ca", 1.84),
480 ("Ti", 4.85),
481 ("V", 5.31),
482 ("Cr", 4.10),
483 ("Fe", 4.28),
484 ("Co", 4.39),
485 ("Ni", 4.44),
486 ("Cu", 3.49),
487 ("Zn", 1.35),
488 ("Ga", 2.81),
489 ("Ge", 3.85),
490 ("As", 2.96),
491 ("Zr", 6.25),
492 ("Nb", 7.57),
493 ("Mo", 6.82),
494 ("Pd", 3.89),
495 ("Ag", 2.95),
496 ("Cd", 1.16),
497 ("In", 2.52),
498 ("Sn", 3.14),
499 ("Sb", 2.75),
500 ("Ta", 8.10),
501 ("W", 8.90),
502 ("Pt", 5.84),
503 ("Au", 3.81),
504 ("Pb", 2.03),
505 ];
506 for (sym, ev) in kittel {
507 assert_eq!(by(sym).default_surface_binding_ev, Some(ev), "{sym}");
508 }
509 let populated = ELEMENTS
510 .iter()
511 .filter(|e| e.default_surface_binding_ev.is_some())
512 .count();
513 assert_eq!(populated, kittel.len());
514 // Kittel prints O 2.60 and P 3.43 eV/atom, but these defaults are
515 // unset by design (#15 audits populated entries only); the user sets
516 // E_s for them.
517 assert_eq!(by("O").default_surface_binding_ev, None);
518 assert_eq!(by("P").default_surface_binding_ev, None);
519 }
520
521 /// Cross-check of `E_s` against L. Brewer, LBL-3720 (1975), Table I,
522 /// atomization energy to the gaseous ground state at 0 K, kcal/gram-atom
523 /// (<https://www.osti.gov/servlets/purl/7187973>), a second source
524 /// beside the cited Kittel table (whose data Brewer supplied).
525 /// 1 kcal/mol = 4184 J / (e N_A) eV per atom (thermochemical calorie).
526 #[test]
527 fn surface_binding_cross_checks_against_brewer_lbl_3720() {
528 use crate::constants::{AVOGADRO, ELEMENTARY_CHARGE};
529 let kcal_ev = 4184.0 / (ELEMENTARY_CHARGE * AVOGADRO);
530 // Agree to the printed digit (half a unit in the last place, 0.005 eV).
531 for (sym, kcal) in [("Si", 106.7), ("Ga", 64.8), ("As", 68.2), ("Au", 87.96)] {
532 let es = by(sym).default_surface_binding_ev.unwrap();
533 assert!(
534 (es - kcal * kcal_ev).abs() <= 0.005,
535 "{sym}: {es} vs {}",
536 kcal * kcal_ev
537 );
538 }
539 // B (beta): Brewer 133 +- 3 kcal = 5.77 +- 0.13 eV; the stored 5.81 is
540 // Kittel's printed value (Table 1 also prints 134 kcal/mol for B),
541 // inside Brewer's uncertainty.
542 let b = by("B").default_surface_binding_ev.unwrap();
543 assert!((b - 133.0 * kcal_ev).abs() <= 3.0 * kcal_ev, "{b}");
544 }
545
546 /// Densities as printed in the X-Ray Data Booklet, LBNL/PUB-490 Rev. 3
547 /// (2009), Table 5-2, pp. 5-5 to 5-8 (<https://xdb.lbl.gov/xdb-new.pdf>,
548 /// read 2026-10-07; from the CRC Handbook, 80th ed.), g/cm³, to the
549 /// digits printed there. Superscripts give the temperature where it is
550 /// not 20 °C.
551 #[test]
552 fn densities_match_x_ray_data_booklet_spot_values() {
553 for (sym, rho) in [
554 ("B", 2.34),
555 ("P", 1.82),
556 ("Ga", 5.904), // 5.904^29.6
557 ("Cu", 8.96),
558 ("Sc", 2.989), // 2.989^25; reviewer-flagged, unchanged
559 // Corrected in #15 (were 5.727, 10.49, 1.93, 22.59, 19.1).
560 ("As", 5.73),
561 ("Ag", 10.50),
562 ("Cs", 1.873),
563 ("Os", 22.57),
564 ("U", 18.95), // printed "~18.95"
565 ] {
566 assert_eq!(by(sym).density_g_cm3, Some(rho), "{sym}");
567 }
568 // Au is printed only as "~19.3"; the stored 19.30 agrees to that
569 // precision, its second decimal is not verified.
570 let au = by("Au").density_g_cm3.unwrap();
571 assert!((au - 19.3).abs() < 0.05, "{au}");
572 }
573
574 /// Densities the X-Ray Data Booklet does not pin, checked against other
575 /// open primary compilations (read 2026-10-07; see the module docs and
576 /// `docs/data-provenance.md`), g/cm³.
577 #[test]
578 fn densities_match_other_primary_compilations() {
579 // NBS Circular 539 Vol. I (1953), p. 33: Au 19.302 at 25 °C from the
580 // lattice constant; the booklet prints "~19.3". Agrees to the stored
581 // digits.
582 let au = by("Au").density_g_cm3.unwrap();
583 assert!((au - 19.302).abs() < 0.005, "{au}");
584 // NBS Circular 539 Vol. V (1955), p. 20: Cr 7.200 at 25 °C from the
585 // lattice constant, the upper end of the booklet's 7.18-7.20.
586 // Corrected in #15 (was 7.15, outside that range).
587 let cr = by("Cr").density_g_cm3.unwrap();
588 assert_eq!(cr, 7.20);
589 assert!((7.18..=7.20).contains(&cr));
590 // Kirby and Salutsky, NAS-NS 3057 (1964), p. 3: Ra "approximately
591 // 5.0". Reviewer-flagged; unchanged.
592 assert_eq!(by("Ra").density_g_cm3, Some(5.0));
593 // Farr et al., LA-1545 (1953), Table 4: Ac 10.07 (fcc, a = 5.311 Å).
594 assert_eq!(by("Ac").density_g_cm3, Some(10.07));
595 }
596
597 /// The Ac entry is the crystal density of its fcc cell as LA-1545 prints
598 /// it: 4 atoms of mass number 227 in a cube of edge 5.311 Å, rounded to
599 /// the printed 10.07 g/cm³ (the report's a carries +- 0.010 Å).
600 #[test]
601 fn actinium_density_matches_its_lattice_constant() {
602 use crate::constants::AVOGADRO;
603 let ac = by("Ac");
604 let a_cm = 5.311e-8;
605 let rho = 4.0 * ac.atomic_weight / (AVOGADRO * a_cm * a_cm * a_cm);
606 assert!((rho - ac.density_g_cm3.unwrap()).abs() < 0.01, "{rho}");
607 }
608
609 /// Mn is not pinned by any source read so far. The X-Ray Data Booklet
610 /// prints the range 7.21-7.44; NBS Monograph 25, Section 17 (1980), p. 50,
611 /// prints the alpha-Mn X-ray density "(calculated) 7.475 g/cm³" at 25 °C
612 /// from a = 8.9121 Å and Z = 58 atoms per cell (volume printed as
613 /// 707.85 ų). That page is reproduced here from its printed lattice
614 /// constant with CODATA 2022 N_A and the CIAAW weight; it lies above the
615 /// booklet's range, so the stored upper end, 7.44, is kept and stays
616 /// unverified.
617 #[test]
618 fn manganese_density_is_bounded_but_not_pinned() {
619 use crate::constants::AVOGADRO;
620 let mn = by("Mn");
621 let a_cm: f64 = 8.9121e-8;
622 assert!((a_cm * a_cm * a_cm * 1e24 - 707.85).abs() < 0.005);
623 let rho_x = 58.0 * mn.atomic_weight / (AVOGADRO * a_cm * a_cm * a_cm);
624 assert!((rho_x - 7.475).abs() < 0.0005, "{rho_x}");
625 let stored = mn.density_g_cm3.unwrap();
626 assert_eq!(stored, 7.44);
627 assert!((7.21..=7.44).contains(&stored));
628 assert!(rho_x > 7.44);
629 }
630
631 /// Si: the CODATA 2022 "molar volume of silicon", V_m(Si) =
632 /// 1.205 883 199e-5 m³/mol
633 /// (<https://physics.nist.gov/cuu/Constants/Table/allascii.txt>), which
634 /// equals N_A a³ / 8 for the listed lattice parameter a = 5.431 020 511e-10
635 /// m, gives the crystal density M(Si) / V_m(Si). For every weight in the
636 /// CIAAW standard interval [28.084, 28.086] it rounds to the stored 2.329
637 /// g/cm³; the X-Ray Data Booklet prints 2.33 at 25 °C.
638 #[test]
639 fn silicon_density_matches_codata_molar_volume() {
640 use crate::constants::AVOGADRO;
641 let a = 5.431_020_511e-10; // m
642 let v_m = 1.205_883_199e-5; // m^3/mol
643 assert!((AVOGADRO * a * a * a / 8.0 / v_m - 1.0).abs() < 1e-9);
644 let si = by("Si").density_g_cm3.unwrap();
645 assert_eq!(si, 2.329);
646 for m_g in [28.084, 28.085, 28.086] {
647 // g/mol / (m^3/mol) = g/m^3; 1e-6 converts to g/cm^3.
648 let rho = m_g / v_m * 1e-6;
649 assert!((rho - si).abs() < 0.0005, "{m_g}: {rho}");
650 }
651 assert!((si - 2.33).abs() < 0.005);
652 }
653
654 /// Defaults that are unset by design stay unset. `E_d` values are not
655 /// pinned: ASTM E521 could not be reached, so no populated value is
656 /// source-verified.
657 #[test]
658 fn unset_defaults_are_preserved() {
659 // Gases have no solid density.
660 for sym in ["H", "N", "O", "F", "Cl", "Ar", "Rn"] {
661 assert_eq!(by(sym).density_g_cm3, None, "{sym}");
662 }
663 // No E_d convention value is recorded for the semiconductor and dopant
664 // elements; the user must set one.
665 for sym in ["Si", "O", "Ga", "As", "B", "P"] {
666 assert_eq!(by(sym).default_displacement_ev, None, "{sym}");
667 }
668 }
669}