sdecay 0.1.0

Bindings for SandiaDecay C++ library, used to compute nuclide mixtures
Documentation
//! Defines ways to identify [`Nuclide`] in [`crate::nuclide_mixture::Mixture`] or [`SandiaDecayDataBase`]
//!
//! Unsafe: no

use core::ffi::CStr;

use crate::wrapper::{Nuclide, NuclideMixture, SandiaDecayDataBase, StdString};

/// Marks type as identifier for [`Nuclide`] in the [`crate::Mixture`] or [`SandiaDecayDataBase`]
pub trait NuclideSpec {
    /// Retrieves described [`Nuclide`] from the database
    fn get_nuclide<'l>(&self, database: &'l SandiaDecayDataBase) -> Option<&'l Nuclide<'l>>;

    /// Retrieves activity of the described [`Nuclide`] in the mixture at the provided time
    fn mixture_activity(&self, time: f64, mixture: &NuclideMixture<'_>) -> Option<f64>;

    /// Retrieves number of atoms of the described [`Nuclide`] in the mixture at the provided time
    fn mixture_num_atoms(&self, time: f64, mixture: &NuclideMixture<'_>) -> Option<f64>;
}

macro_rules! impl_as_cpp_string {
    (<$l:ident> $t:ty) => {
        impl<const $l: usize> NuclideSpec for $t {
            #[inline]
            fn get_nuclide<'l>(
                &self,
                database: &'l SandiaDecayDataBase,
            ) -> Option<&'l Nuclide<'l>> {
                database.nuclide_by_name(*self)
            }

            #[inline]
            fn mixture_activity(
                &self,
                time: f64,
                mixture: &NuclideMixture<'_>,
            ) -> Option<f64> {
                mixture.activity_by_symbol(time, *self)
            }

            #[inline]
            fn mixture_num_atoms(
                &self,
                time: f64,
                mixture: &NuclideMixture<'_>,
            ) -> Option<f64> {
                mixture.atoms_by_symbol(time, *self)
            }
        }
    };
    ($t:ty) => {
        impl_as_cpp_string!(@$t);
        #[cfg(feature = "alloc")]
        impl_as_cpp_string!(@alloc::boxed::Box<$t>);
        #[cfg(feature = "alloc")]
        impl_as_cpp_string!(@alloc::borrow::Cow<'_, $t>);
    };
    (@$t:ty) => {
        impl NuclideSpec for $t {
            #[inline]
            fn get_nuclide<'l>(
                &self,
                database: &'l SandiaDecayDataBase,
            ) -> Option<&'l Nuclide<'l>> {
                database.nuclide_by_name(self)
            }

            #[inline]
            fn mixture_activity(
                &self,
                time: f64,
                mixture: &NuclideMixture<'_>,
            ) -> Option<f64> {
                mixture.activity_by_symbol(time, self)
            }

            #[inline]
            fn mixture_num_atoms(
                &self,
                time: f64,
                mixture: &NuclideMixture<'_>,
            ) -> Option<f64> {
                mixture.atoms_by_symbol(time, self)
            }
        }
        impl_as_cpp_string!(@@&$t);
        impl_as_cpp_string!(@@&&$t);
    };
    (@@$t:ty) => {
        impl NuclideSpec for $t {
            #[inline]
            fn get_nuclide<'l>(
                &self,
                database: &'l SandiaDecayDataBase,
            ) -> Option<&'l Nuclide<'l>> {
                database.nuclide_by_name(&**self)
            }

            #[inline]
            fn mixture_activity(
                &self,
                time: f64,
                mixture: &NuclideMixture<'_>,
            ) -> Option<f64> {
                mixture.activity_by_symbol(time, &**self)
            }

            #[inline]
            fn mixture_num_atoms(
                &self,
                time: f64,
                mixture: &NuclideMixture<'_>,
            ) -> Option<f64> {
                mixture.atoms_by_symbol(time, &**self)
            }
        }
    };
}

impl_as_cpp_string!(@StdString);

impl_as_cpp_string!(CStr);
#[cfg(feature = "alloc")]
impl_as_cpp_string!(@alloc::ffi::CString);

impl_as_cpp_string!(str);
#[cfg(feature = "alloc")]
impl_as_cpp_string!(@alloc::string::String);

impl_as_cpp_string!([u8]);
#[cfg(feature = "alloc")]
impl_as_cpp_string!(@alloc::vec::Vec<u8>);

#[cfg(feature = "std")]
impl_as_cpp_string!(std::ffi::OsStr);
#[cfg(feature = "std")]
impl_as_cpp_string!(@std::ffi::OsString);

#[cfg(feature = "std")]
impl_as_cpp_string!(std::path::Path);
#[cfg(feature = "std")]
impl_as_cpp_string!(@std::path::PathBuf);

impl_as_cpp_string!(<N> [u8; N]);
impl_as_cpp_string!(<N> &[u8; N]);
impl_as_cpp_string!(<N> &&[u8; N]);

impl NuclideSpec for Nuclide<'_> {
    #[inline]
    fn get_nuclide<'l>(&self, database: &'l SandiaDecayDataBase) -> Option<&'l Nuclide<'l>> {
        database.nuclide_by_name(&self.symbol)
    }

    #[inline]
    fn mixture_activity(&self, time: f64, mixture: &NuclideMixture<'_>) -> Option<f64> {
        mixture.activity_by_nuclide(time, self)
    }

    #[inline]
    fn mixture_num_atoms(&self, time: f64, mixture: &NuclideMixture<'_>) -> Option<f64> {
        mixture.atoms_by_nuclide(time, self)
    }
}

impl NuclideSpec for &Nuclide<'_> {
    #[inline]
    fn get_nuclide<'l>(&self, database: &'l SandiaDecayDataBase) -> Option<&'l Nuclide<'l>> {
        database.nuclide_by_name(&self.symbol)
    }

    #[inline]
    fn mixture_activity(&self, time: f64, mixture: &NuclideMixture<'_>) -> Option<f64> {
        mixture.activity_by_nuclide(time, self)
    }

    #[inline]
    fn mixture_num_atoms(&self, time: f64, mixture: &NuclideMixture<'_>) -> Option<f64> {
        mixture.atoms_by_nuclide(time, self)
    }
}

/// Numeric description of the [`Nuclide`]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NumSpec {
    /// Nuclei charge i.e. proton count
    pub z: i32,
    /// Nuclei mass number i.e. nucleon count
    pub mass_number: i32,
    /// Isotope index (seems to be SandiaDecay-specific)
    pub iso: Option<i32>,
}

impl NuclideSpec for NumSpec {
    #[inline]
    fn get_nuclide<'l>(&self, database: &'l SandiaDecayDataBase) -> Option<&'l Nuclide<'l>> {
        database.nuclide_by_num(self.z, self.mass_number, self.iso.unwrap_or(0))
    }

    #[inline]
    fn mixture_activity(&self, time: f64, mixture: &NuclideMixture<'_>) -> Option<f64> {
        mixture.activity_by_num(time, self)
    }

    #[inline]
    fn mixture_num_atoms(&self, time: f64, mixture: &NuclideMixture<'_>) -> Option<f64> {
        mixture.atoms_by_num(time, self)
    }
}

/// Simplified constructor for [`NumSpec`], allowing construction via statically checked element symbol
///
/// ### Example
/// ```rust
/// # use sdecay::nuclide;
/// let protium = nuclide!(H-1);
/// let deuterium = nuclide!(H-2);
/// let tritium = nuclide!(H-3);
///
/// let k40 = nuclide!(k-40);
/// let a = 238;
/// let u238 = nuclide!(u-a);
/// let pu239 = nuclide!(pu-239);
/// ```
#[macro_export]
macro_rules! nuclide {
    ($symbol:ident-$a:literal) => {
        $crate::nuclide_spec::NumSpec {
            z: $crate::element_inner!($symbol),
            mass_number: $a,
            iso: None,
        }
    };
    ($symbol:ident-$a:ident) => {
        $crate::nuclide_spec::NumSpec {
            z: $crate::element_inner!($symbol),
            mass_number: $a,
            iso: None,
        }
    };
    ($symbol:ident-$a:literal m) => {
        compile_error!("nspec! macro does not support isomers, sorry");
    };
    ($symbol:ident-$a:ident m) => {
        compile_error!("nspec! macro does not support isomers, sorry");
    };
}