use std::collections::BTreeMap;
use std::sync::OnceLock;
use crate::NuclideId;
const AME2020_TSV: &str = include_str!("data/ame2020.tsv");
const NATURAL_ABUNDANCE_TSV: &str = include_str!("data/natural_abundance.tsv");
const HALF_LIFE_TSV: &str = include_str!("data/half_life.tsv");
const SIMPLE_XS_TSV: &str = include_str!("data/simple_xs.tsv");
const SCATTERING_LENGTHS_TSV: &str = include_str!("data/scattering_lengths.tsv");
const DECAY_ENERGY_TSV: &str = include_str!("data/decay_energy.tsv");
static MASSES: OnceLock<BTreeMap<u32, f64>> = OnceLock::new();
static ABUNDANCES: OnceLock<BTreeMap<u32, f64>> = OnceLock::new();
static HALF_LIVES: OnceLock<BTreeMap<u32, f64>> = OnceLock::new();
static SIMPLE_XS: OnceLock<BTreeMap<u32, (f64, f64)>> = OnceLock::new();
static SCATTERING_LENGTHS: OnceLock<BTreeMap<u32, (f64, f64)>> = OnceLock::new();
static DECAY_ENERGIES: OnceLock<BTreeMap<u32, f64>> = OnceLock::new();
pub const MEV_PER_U: f64 = 931.494_102_42;
pub const NEUTRON_MASS_U: f64 = 1.008_664_915_95;
pub const HELIUM4_MASS_U: f64 = 4.002_603_254_13;
pub const fn neutron_mass_u() -> f64 {
NEUTRON_MASS_U
}
fn parse_masses(tsv: &str) -> BTreeMap<u32, f64> {
tsv.lines()
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.filter_map(|line| {
let mut cols = line.split('\t');
let nucid = cols.next()?.parse().ok()?;
let mass = cols.next()?.parse().ok()?;
Some((nucid, mass))
})
.collect()
}
fn parse_abundances(tsv: &str) -> BTreeMap<u32, f64> {
tsv.lines()
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.filter_map(|line| {
let mut cols = line.split('\t');
let name = cols.next()?;
let fraction: f64 = cols.next()?.parse().ok()?;
let nucid = NuclideId::from_name(name).ok()?;
Some((nucid.nucid(), fraction))
})
.collect()
}
fn parse_half_lives(tsv: &str) -> BTreeMap<u32, f64> {
tsv.lines()
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.filter_map(|line| {
let mut cols = line.split('\t');
let name = cols.next()?;
let seconds: f64 = cols.next()?.parse().ok()?;
let nucid = NuclideId::from_name(name).ok()?;
Some((nucid.nucid(), seconds))
})
.collect()
}
fn masses() -> &'static BTreeMap<u32, f64> {
MASSES.get_or_init(|| parse_masses(AME2020_TSV))
}
fn abundances() -> &'static BTreeMap<u32, f64> {
ABUNDANCES.get_or_init(|| parse_abundances(NATURAL_ABUNDANCE_TSV))
}
fn half_lives() -> &'static BTreeMap<u32, f64> {
HALF_LIVES.get_or_init(|| parse_half_lives(HALF_LIFE_TSV))
}
fn parse_simple_xs(tsv: &str) -> BTreeMap<u32, (f64, f64)> {
tsv.lines()
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.filter_map(|line| {
let mut cols = line.split('\t');
let name = cols.next()?;
let thermal: f64 = cols.next()?.parse().ok()?;
let fast: f64 = cols.next()?.parse().ok()?;
let nucid = NuclideId::from_name(name).ok()?;
Some((nucid.nucid(), (thermal, fast)))
})
.collect()
}
fn parse_scattering_lengths(tsv: &str) -> BTreeMap<u32, (f64, f64)> {
tsv.lines()
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.filter_map(|line| {
let mut cols = line.split('\t');
let name = cols.next()?;
let coherent: f64 = cols.next()?.parse().ok()?;
let incoherent: f64 = cols.next()?.parse().ok()?;
let nucid = NuclideId::from_name(name).ok()?;
Some((nucid.nucid(), (coherent, incoherent)))
})
.collect()
}
fn parse_decay_energies(tsv: &str) -> BTreeMap<u32, f64> {
tsv.lines()
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.filter_map(|line| {
let mut cols = line.split('\t');
let name = cols.next()?;
let mev: f64 = cols.next()?.parse().ok()?;
let nucid = NuclideId::from_name(name).ok()?;
Some((nucid.nucid(), mev))
})
.collect()
}
fn simple_xs_map() -> &'static BTreeMap<u32, (f64, f64)> {
SIMPLE_XS.get_or_init(|| parse_simple_xs(SIMPLE_XS_TSV))
}
fn scattering_length_map() -> &'static BTreeMap<u32, (f64, f64)> {
SCATTERING_LENGTHS.get_or_init(|| parse_scattering_lengths(SCATTERING_LENGTHS_TSV))
}
fn decay_energy_map() -> &'static BTreeMap<u32, f64> {
DECAY_ENERGIES.get_or_init(|| parse_decay_energies(DECAY_ENERGY_TSV))
}
pub fn mass_table() -> &'static BTreeMap<u32, f64> {
masses()
}
pub fn abundance_table() -> &'static BTreeMap<u32, f64> {
abundances()
}
pub fn half_life_table() -> &'static BTreeMap<u32, f64> {
half_lives()
}
pub fn simple_xs_table() -> &'static BTreeMap<u32, (f64, f64)> {
simple_xs_map()
}
pub fn scattering_length_table() -> &'static BTreeMap<u32, (f64, f64)> {
scattering_length_map()
}
pub fn decay_energy_table() -> &'static BTreeMap<u32, f64> {
decay_energy_map()
}
pub fn simple_xs(nucid: u32) -> Option<(f64, f64)> {
simple_xs_map().get(&nucid).copied()
}
pub fn simple_xs_by_name(name: &str) -> Option<(f64, f64)> {
simple_xs(NuclideId::from_name(name).ok()?.nucid())
}
pub fn scattering_length(nucid: u32) -> Option<(f64, f64)> {
scattering_length_map().get(&nucid).copied()
}
pub fn scattering_length_by_name(name: &str) -> Option<(f64, f64)> {
scattering_length(NuclideId::from_name(name).ok()?.nucid())
}
pub fn decay_energy_mev(nucid: u32) -> Option<f64> {
decay_energy_map().get(&nucid).copied()
}
pub fn decay_energy_mev_by_name(name: &str) -> Option<f64> {
decay_energy_mev(NuclideId::from_name(name).ok()?.nucid())
}
pub fn atomic_mass(nucid: u32) -> Option<f64> {
masses().get(&nucid).copied()
}
pub fn atomic_mass_by_name(name: &str) -> Option<f64> {
atomic_mass(NuclideId::from_name(name).ok()?.nucid())
}
pub fn natural_abundance(nucid: u32) -> Option<f64> {
abundances().get(&nucid).copied()
}
pub fn natural_abundance_by_name(name: &str) -> Option<f64> {
natural_abundance(NuclideId::from_name(name).ok()?.nucid())
}
pub fn half_life(nucid: u32) -> Option<f64> {
half_lives().get(&nucid).copied()
}
pub fn half_life_by_name(name: &str) -> Option<f64> {
half_life(NuclideId::from_name(name).ok()?.nucid())
}
pub fn decay_constant(nucid: u32) -> Option<f64> {
half_life(nucid).map(|t_half| std::f64::consts::LN_2 / t_half)
}
pub fn decay_constant_by_name(name: &str) -> Option<f64> {
decay_constant(NuclideId::from_name(name).ok()?.nucid())
}
pub fn q_value_neutron_capture(nucid: u32) -> Option<f64> {
let id = NuclideId::from_nucid(nucid);
if id.state() != 0 || id.z() == 0 {
return None;
}
let product = (id.z() * 1000 + id.a() + 1) * 10_000;
let q_u = atomic_mass(nucid)? + NEUTRON_MASS_U - atomic_mass(product)?;
Some(q_u * MEV_PER_U)
}
pub fn q_value_neutron_capture_by_name(name: &str) -> Option<f64> {
q_value_neutron_capture(NuclideId::from_name(name).ok()?.nucid())
}
pub fn q_value_alpha(nucid: u32) -> Option<f64> {
let id = NuclideId::from_nucid(nucid);
if id.state() != 0 || id.z() <= 2 || id.a() <= 4 {
return None;
}
let daughter = ((id.z() - 2) * 1000 + (id.a() - 4)) * 10_000;
let q_u = atomic_mass(nucid)? - atomic_mass(daughter)? - HELIUM4_MASS_U;
Some(q_u * MEV_PER_U)
}
pub fn q_value_alpha_by_name(name: &str) -> Option<f64> {
q_value_alpha(NuclideId::from_name(name).ok()?.nucid())
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct AmeMasses;
impl AmeMasses {
pub fn atomic_mass(&self, nucid: u32) -> Option<f64> {
atomic_mass(nucid)
}
pub fn atomic_mass_by_name(&self, name: &str) -> Option<f64> {
atomic_mass_by_name(name)
}
pub fn natural_abundance(&self, nucid: u32) -> Option<f64> {
natural_abundance(nucid)
}
pub fn natural_abundance_by_name(&self, name: &str) -> Option<f64> {
natural_abundance_by_name(name)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct DecayData;
impl DecayData {
pub fn half_life(&self, nucid: u32) -> Option<f64> {
half_life(nucid)
}
pub fn half_life_by_name(&self, name: &str) -> Option<f64> {
half_life_by_name(name)
}
pub fn decay_constant(&self, nucid: u32) -> Option<f64> {
decay_constant(nucid)
}
pub fn decay_constant_by_name(&self, name: &str) -> Option<f64> {
decay_constant_by_name(name)
}
pub fn decay_energy_mev(&self, nucid: u32) -> Option<f64> {
decay_energy_mev(nucid)
}
pub fn decay_energy_mev_by_name(&self, name: &str) -> Option<f64> {
decay_energy_mev_by_name(name)
}
}
#[cfg(test)]
mod tests {
use super::*;
const H1: u32 = 10_010_000;
const O16: u32 = 80_160_000;
const FE56: u32 = 260_560_000;
const U235: u32 = 922_350_000;
#[test]
fn h1_exact_ame2020_value() {
assert_eq!(atomic_mass(H1), Some(1.007825031_898));
assert_eq!(atomic_mass_by_name("H1"), Some(1.007825031_898));
}
#[test]
fn heavy_nuclide_spot_values() {
assert_eq!(atomic_mass(U235), Some(235.043_928_117));
assert_eq!(atomic_mass(FE56), Some(55.934_935_537));
assert_eq!(atomic_mass(O16), Some(15.994_914_619_26));
}
#[test]
fn non_nuclides_and_unknown_ids_return_none() {
assert_eq!(atomic_mass(10_000), None);
assert_eq!(atomic_mass(999_999_999), None);
assert_eq!(atomic_mass(922_350_001), None);
assert_eq!(atomic_mass_by_name("U235_m1"), None);
}
#[test]
fn by_name_agrees_with_nucid_lookup() {
for name in ["H1", "O16", "Fe56", "U235", "Og294"] {
let nucid = NuclideId::from_name(name).unwrap().nucid();
assert_eq!(atomic_mass_by_name(name), atomic_mass(nucid), "{name}");
}
}
#[test]
fn natural_abundance_spot_values() {
assert_eq!(natural_abundance(U235), Some(0.007_204));
assert_eq!(natural_abundance_by_name("U235"), Some(0.007_204));
assert_eq!(natural_abundance(O16), Some(0.997_620_6));
assert_eq!(natural_abundance_by_name("H1"), Some(0.999_844_26));
assert_eq!(natural_abundance_by_name("Ta180_m1"), Some(0.000_120_1));
}
#[test]
fn natural_abundance_unknown_returns_none() {
assert_eq!(natural_abundance(999_999_999), None);
assert_eq!(natural_abundance_by_name("C14"), None);
assert_eq!(natural_abundance_by_name("Xx999"), None);
}
#[test]
fn abundances_sum_to_one_per_element() {
let mut totals = [0.0_f64; 119];
for (nucid, frac) in abundance_table() {
totals[NuclideId::from_nucid(*nucid).z() as usize] += frac;
}
for (z, total) in totals.iter().enumerate() {
if *total > 0.0 {
assert!(
(total - 1.0).abs() < 1e-6,
"Z={z} abundances sum to {total}"
);
}
}
}
#[test]
fn mass_sanity_sweep() {
let table = mass_table();
for (nucid, mass) in table {
let id = NuclideId::from_nucid(*nucid);
assert_eq!(id.state(), 0, "only ground states expected");
let (lo, hi) = (0.9 * f64::from(id.a()), 1.2 * f64::from(id.a()));
assert!(*mass > lo && *mass < hi, "{} mass {mass}", id.to_name());
assert!(*mass > 0.0);
}
}
#[test]
fn vendored_row_counts_match_tables() {
let mass_rows = AME2020_TSV
.lines()
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.count();
let abundance_rows = NATURAL_ABUNDANCE_TSV
.lines()
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.count();
assert_eq!(mass_table().len(), mass_rows);
assert_eq!(mass_rows, 3557);
assert_eq!(abundance_table().len(), abundance_rows);
assert_eq!(abundance_rows, 289);
}
#[test]
fn ame_masses_facade_delegates() {
let provider = AmeMasses;
assert_eq!(provider.atomic_mass(U235), atomic_mass(U235));
assert_eq!(provider.atomic_mass_by_name("Fe56"), Some(55.934_935_537));
assert_eq!(provider.natural_abundance(O16), Some(0.997_620_6));
assert_eq!(provider.natural_abundance_by_name("Nope1"), None);
}
const U238: u32 = 922_380_000;
const I135: u32 = 531_350_000;
const CS137: u32 = 551_370_000;
#[test]
fn half_life_spot_values() {
let t_u238 = half_life(U238).unwrap();
assert!((t_u238 - 1.409_99e17).abs() / t_u238 < 1e-9);
assert_eq!(half_life(I135), Some(23_652.0));
let t_cs135 = half_life_by_name("Cs135").unwrap();
assert!((t_cs135 - 7.258_25e13).abs() / t_cs135 < 1e-12);
let t_cs137 = half_life(CS137).unwrap();
assert!((t_cs137 / 3.155_695_2e7 - 30.08).abs() < 0.01, "{t_cs137}");
assert_eq!(half_life_by_name("Am242_m1"), Some(4_449_622_000.0));
}
#[test]
fn stable_and_unknown_nuclides_have_no_half_life() {
assert_eq!(half_life(O16), None);
assert_eq!(half_life_by_name("Fe56"), None);
assert_eq!(decay_constant(H1), None);
assert_eq!(half_life(999_999_999), None);
assert_eq!(half_life_by_name("Xx999"), None);
}
#[test]
fn decay_constant_is_ln2_over_half_life() {
for nucid in [
U238,
I135,
CS137,
NuclideId::from_name("Te132").unwrap().nucid(),
] {
let t_half = half_life(nucid).unwrap();
let lambda = decay_constant(nucid).unwrap();
let rel = (lambda * t_half - std::f64::consts::LN_2).abs() / std::f64::consts::LN_2;
assert!(rel < 1e-12, "nucid {nucid}: rel err {rel}");
}
let lam = decay_constant_by_name("I135").unwrap();
assert!((lam - std::f64::consts::LN_2 / 23_652.0).abs() < 1e-18);
}
#[test]
fn shorter_half_life_gives_larger_decay_constant() {
let te = NuclideId::from_name("Te132").unwrap().nucid();
let xe = NuclideId::from_name("Xe135").unwrap().nucid();
let (t_te, t_i, t_xe) = (
half_life(te).unwrap(),
half_life(I135).unwrap(),
half_life(xe).unwrap(),
);
assert!(t_te > t_xe && t_i < t_xe);
assert!(decay_constant(te).unwrap() < decay_constant(xe).unwrap());
assert!(decay_constant(xe).unwrap() < decay_constant(I135).unwrap());
}
#[test]
fn half_lives_are_positive_and_finite() {
for (nucid, t_half) in half_life_table() {
assert!(*t_half > 0.0 && t_half.is_finite(), "{nucid}: {t_half}");
let id = NuclideId::from_nucid(*nucid);
assert!(id.a() >= id.z(), "{}", id.to_name());
}
}
#[test]
fn vendored_half_life_row_count_matches_table() {
let rows = HALF_LIFE_TSV
.lines()
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.count();
assert_eq!(half_life_table().len(), rows);
assert_eq!(rows, 3561);
}
#[test]
fn neutron_capture_q_value_anchors() {
let q_h = q_value_neutron_capture(H1).unwrap();
assert!((q_h - 2.224_566).abs() < 1e-3, "{q_h}");
let q_u238 = q_value_neutron_capture(U238).unwrap();
assert!((q_u238 - 4.806_382).abs() < 1e-3, "{q_u238}");
assert!(q_u238 > 4.79 && q_u238 < 4.81);
let q_o16 = q_value_neutron_capture(O16).unwrap();
assert!((q_o16 - 4.143_080).abs() < 1e-3, "{q_o16}");
}
#[test]
fn capture_q_value_matches_manual_formula() {
let expected = (atomic_mass(U238).unwrap() + NEUTRON_MASS_U
- atomic_mass(922_390_000).unwrap())
* MEV_PER_U;
let q = q_value_neutron_capture(U238).unwrap();
assert!((q - expected).abs() < 1e-9);
assert_eq!(q_value_neutron_capture_by_name("U238"), Some(q));
assert_eq!(neutron_mass_u(), NEUTRON_MASS_U);
assert_eq!(neutron_mass_u(), 1.008_664_915_95);
}
#[test]
fn capture_q_value_missing_or_bad_targets_return_none() {
let he10 = NuclideId::from_name("He10").unwrap().nucid();
assert_eq!(atomic_mass(he10 + 10_000), None);
assert_eq!(q_value_neutron_capture(he10), None);
assert_eq!(q_value_neutron_capture(922_350_001), None);
assert_eq!(q_value_neutron_capture(10_000), None);
assert_eq!(q_value_neutron_capture_by_name("U235_m1"), None);
assert_eq!(q_value_neutron_capture_by_name("Nope1"), None);
}
#[test]
fn alpha_q_value_anchors() {
for (name, lit) in [
("U238", 4.269_858),
("Po210", 5.407_530),
("Ra226", 4.870_703),
] {
let q = q_value_alpha_by_name(name).unwrap();
assert!((q - lit).abs() < 1e-3, "{name}: {q} vs {lit}");
}
assert_eq!(q_value_alpha(U238), q_value_alpha_by_name("U238"));
}
#[test]
fn alpha_q_value_rejects_light_and_metastable() {
assert_eq!(q_value_alpha(H1), None);
assert_eq!(q_value_alpha_by_name("He4"), None);
assert_eq!(q_value_alpha(922_350_001), None);
assert_eq!(q_value_alpha_by_name("Am242_m1"), None);
let q_o16 = q_value_alpha_by_name("O16").unwrap();
assert!((q_o16 + 7.162).abs() < 1e-3, "{q_o16}");
}
#[test]
fn decay_data_facade_delegates() {
let provider = DecayData;
assert_eq!(provider.half_life(I135), Some(23_652.0));
assert_eq!(provider.half_life_by_name("I135"), Some(23_652.0));
assert_eq!(provider.decay_constant(U238), decay_constant(U238));
assert_eq!(provider.decay_constant_by_name("Fe56"), None);
}
#[test]
fn simple_xs_h1_anchors_within_ten_percent() {
let (thermal, fast) = simple_xs_by_name("H1").unwrap();
assert!((thermal - 20.84).abs() / 20.84 < 0.05, "{thermal}");
assert!((fast - 0.687).abs() / 0.687 < 0.05, "{fast}");
assert_eq!(simple_xs(H1), Some((thermal, fast)));
}
#[test]
fn simple_xs_absorber_and_actinide_bands() {
let (b10_th, _) = simple_xs_by_name("B10").unwrap();
assert!(b10_th > 3700.0 && b10_th < 3950.0, "{b10_th}");
let (u235_th, u235_fast) = simple_xs_by_name("U235").unwrap();
assert!(u235_th > 680.0 && u235_th < 710.0, "{u235_th}");
assert!(u235_fast > 5.0 && u235_fast < 7.0, "{u235_fast}");
let (pu239_th, _) = simple_xs_by_name("Pu239").unwrap();
assert!(pu239_th > 1000.0 && pu239_th < 1050.0, "{pu239_th}");
let (o16_th, _) = simple_xs_by_name("O16").unwrap();
assert!(o16_th > 3.5 && o16_th < 4.0, "{o16_th}");
}
#[test]
fn simple_xs_coverage_gaps_are_documented() {
for name in ["Cs137", "Co60", "I135", "Xe135", "Am242_m1"] {
assert_eq!(simple_xs_by_name(name), None, "{name}");
}
}
#[test]
fn simple_xs_unknown_returns_none() {
assert_eq!(simple_xs(999_999_999), None);
assert_eq!(simple_xs_by_name("Og294"), None);
assert_eq!(simple_xs_by_name("Xx999"), None);
}
#[test]
fn scattering_length_nist_anchors() {
let (h_coh, h_inc) = scattering_length_by_name("H1").unwrap();
assert!((h_coh - -3.7406).abs() < 1e-3, "{h_coh}");
assert!((h_inc - 25.274).abs() < 1e-3, "{h_inc}");
let (d_coh, d_inc) = scattering_length_by_name("H2").unwrap();
assert!((d_coh - 6.671).abs() < 1e-2, "{d_coh}");
assert!((d_inc - 4.04).abs() < 1e-2, "{d_inc}");
let (o_coh, o_inc) = scattering_length_by_name("O16").unwrap();
assert!((o_coh - 5.803).abs() < 1e-2, "{o_coh}");
assert_eq!(o_inc, 0.0);
}
#[test]
fn scattering_length_unknown_returns_none() {
assert_eq!(scattering_length(999_999_999), None);
assert_eq!(scattering_length_by_name("Og294"), None);
assert_eq!(scattering_length_by_name("Xx999"), None);
}
#[test]
fn decay_energy_anchors_within_tolerance() {
let cs = decay_energy_mev_by_name("Cs137").unwrap();
assert!((cs - 0.1794).abs() / 0.1794 < 0.05, "{cs}");
let co = decay_energy_mev_by_name("Co60").unwrap();
assert!((co - 2.6006).abs() / 2.6006 < 0.05, "{co}");
let h3 = decay_energy_mev_by_name("H3").unwrap();
assert!((h3 - 0.00569).abs() / 0.00569 < 0.05, "{h3}");
}
#[test]
fn decay_energy_stable_and_unknown_return_none() {
assert_eq!(decay_energy_mev(O16), None);
assert_eq!(decay_energy_mev(FE56), None);
assert_eq!(decay_energy_mev(999_999_999), None);
assert_eq!(decay_energy_mev_by_name("Fe56"), None);
assert_eq!(decay_energy_mev_by_name("Xx999"), None);
}
#[test]
fn decay_energy_isomers_carry_own_rows() {
let ba_m1 = decay_energy_mev_by_name("Ba137_m1").unwrap();
assert!((ba_m1 - 0.6614).abs() / 0.6614 < 0.05, "{ba_m1}");
assert!(decay_energy_mev_by_name("Ba137").is_none());
let am_m2 = NuclideId::from_name("Am242_m2").unwrap().nucid();
assert_eq!(
decay_energy_mev_by_name("Am242_m2"),
decay_energy_mev(am_m2)
);
assert!(decay_energy_mev(am_m2).is_some());
}
#[test]
fn vendored_generated_row_counts_match_tables() {
for (tsv, table_len, expected) in [
(SIMPLE_XS_TSV, simple_xs_table().len(), 241),
(SCATTERING_LENGTHS_TSV, scattering_length_table().len(), 267),
(DECAY_ENERGY_TSV, decay_energy_table().len(), 3557),
] {
let rows = tsv
.lines()
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.count();
assert_eq!(table_len, rows);
assert_eq!(rows, expected);
}
}
#[test]
fn generated_by_name_agrees_with_nucid_lookup() {
for name in ["H1", "B10", "O16", "Fe56", "U235", "Pu239"] {
let nucid = NuclideId::from_name(name).unwrap().nucid();
assert_eq!(simple_xs_by_name(name), simple_xs(nucid), "{name}");
assert_eq!(
scattering_length_by_name(name),
scattering_length(nucid),
"{name}"
);
}
for name in ["H3", "Co60", "Cs137"] {
let nucid = NuclideId::from_name(name).unwrap().nucid();
assert_eq!(
decay_energy_mev_by_name(name),
decay_energy_mev(nucid),
"{name}"
);
}
}
#[test]
fn decay_data_facade_delegates_decay_energy() {
let provider = DecayData;
assert_eq!(provider.decay_energy_mev(CS137), decay_energy_mev(CS137));
let co60 = provider.decay_energy_mev_by_name("Co60").unwrap();
assert!((co60 - 2.6006).abs() / 2.6006 < 0.05, "{co60}");
assert_eq!(provider.decay_energy_mev_by_name("Fe56"), None);
}
}