use std::collections::hash_map::{HashMap, Iter, IterMut};
use std::fmt::Display;
use std::iter::FromIterator;
use std::ops::{Index, IndexMut};
use std::str::FromStr;
use fnv::FnvBuildHasher;
#[cfg(feature = "serde")]
use serde_with::{DeserializeFromStr, SerializeDisplay};
use crate::element_specification::{ElementSpecification, ElementSpecificationLike};
use crate::formula::FormulaParserError;
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))]
pub struct ChemicalCompositionMap<'a> {
pub composition: HashMap<ElementSpecification<'a>, i32, FnvBuildHasher>,
mass_cache: Option<f64>,
}
impl<'lifespan> ChemicalCompositionMap<'lifespan> {
pub fn new() -> ChemicalCompositionMap<'lifespan> {
ChemicalCompositionMap {
..Default::default()
}
}
#[inline]
pub fn get(&self, elt_spec: &ElementSpecification<'lifespan>) -> i32 {
match self.composition.get(elt_spec) {
Some(i) => *i,
None => 0,
}
}
#[inline]
pub fn set(&mut self, elt_spec: ElementSpecification<'lifespan>, count: i32) {
self.composition.insert(elt_spec, count);
self.mass_cache = None;
}
#[inline]
pub fn inc(&mut self, elt_spec: ElementSpecification<'lifespan>, count: i32) {
let i = self.get(&elt_spec);
self.set(elt_spec, i + count);
}
#[inline]
pub fn iter(&self) -> Iter<ElementSpecification<'lifespan>, i32> {
(self.composition).iter()
}
#[inline]
pub fn iter_mut(&mut self) -> IterMut<ElementSpecification<'lifespan>, i32> {
(self.composition).iter_mut()
}
pub fn into_inner(self) -> HashMap<ElementSpecification<'lifespan>, i32, FnvBuildHasher> {
self.composition
}
#[inline]
pub fn calc_mass(&self) -> f64 {
let mut total = 0.0;
for (elt_spec, count) in &self.composition {
let element = elt_spec.element;
total = if elt_spec.isotope == 0 {
element.most_abundant_mass
} else {
element.isotopes[&elt_spec.isotope].mass
}
.mul_add(*count as f64, total);
}
total
}
#[inline]
pub fn mass(&self) -> f64 {
match self.mass_cache {
None => self.calc_mass(),
Some(val) => val,
}
}
#[inline]
pub fn fmass(&mut self) -> f64 {
match self.mass_cache {
None => {
let total = self.mass();
self.mass_cache = Some(total);
total
}
Some(val) => val,
}
}
#[inline]
pub fn has_mass_cached(&self) -> bool {
self.mass_cache.is_some()
}
}
impl<'lifespan, 'transient, 'outer: 'transient> ChemicalCompositionMap<'lifespan> {
#[inline]
pub(crate) fn _add_from(
&'outer mut self,
other: &'transient ChemicalCompositionMap<'lifespan>,
) {
for (key, val) in other.composition.iter() {
self.inc(*key, *val);
}
}
#[inline]
pub(crate) fn _sub_from(
&'outer mut self,
other: &'transient ChemicalCompositionMap<'lifespan>,
) {
for (key, val) in other.composition.iter() {
self.inc(*key, -(*val));
}
}
#[inline]
pub(crate) fn _mul_by(&mut self, scaler: i32) {
self.iter_mut().for_each(|(_, v)| *v *= scaler);
}
#[inline]
pub fn len(&self) -> usize {
self.composition.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.composition.is_empty()
}
}
impl<'lifespan> Index<&ElementSpecification<'lifespan>> for ChemicalCompositionMap<'lifespan> {
type Output = i32;
#[inline]
fn index(&self, key: &ElementSpecification<'lifespan>) -> &Self::Output {
let ent = self.composition.get(key);
ent.unwrap()
}
}
impl<'lifespan> IndexMut<&ElementSpecification<'lifespan>> for ChemicalCompositionMap<'lifespan> {
#[inline]
fn index_mut(&mut self, key: &ElementSpecification<'lifespan>) -> &mut Self::Output {
self.mass_cache = None;
let entry = self.composition.entry(*key);
entry.or_insert(0)
}
}
impl ChemicalCompositionMap<'_> {
pub fn get_str(&self, elt: &str) -> i32 {
match self.composition.get(elt) {
Some(c) => *c,
None => 0,
}
}
pub fn get_str_mut(&mut self, elt: &str) -> Option<&mut i32> {
self.mass_cache = None;
self.composition.get_mut(elt)
}
pub fn inc_str(&mut self, elt: &str, count: i32) {
self.mass_cache = None;
if let Some(val) = self.get_str_mut(elt) {
*val += count;
} else {
match ElementSpecification::parse(elt) {
Ok(spec) => self.inc(spec, count),
Err(err) => {
panic!("Failed to parse element specification {} while incrementing composition: {:?}", elt, err)
}
}
}
}
}
const ZERO: i32 = 0;
impl Index<&str> for ChemicalCompositionMap<'_> {
type Output = i32;
#[inline]
fn index(&self, key: &str) -> &Self::Output {
match ElementSpecification::quick_check_str(key) {
ElementSpecificationLike::Yes => self.composition.get(key).unwrap_or(&ZERO),
ElementSpecificationLike::No => &ZERO,
ElementSpecificationLike::Maybe => {
let spec = key.parse::<ElementSpecification>();
match spec {
Ok(spec) => self.composition.get(&spec).unwrap_or(&ZERO),
Err(_err) => &ZERO,
}
}
}
}
}
impl IndexMut<&str> for ChemicalCompositionMap<'_> {
#[inline]
fn index_mut(&mut self, key: &str) -> &mut Self::Output {
self.mass_cache = None;
let key = key.parse::<ElementSpecification>().unwrap();
let entry = self.composition.entry(key);
entry.or_insert(0)
}
}
impl<'lifespan> PartialEq<ChemicalCompositionMap<'lifespan>> for ChemicalCompositionMap<'lifespan> {
#[inline]
fn eq(&self, other: &ChemicalCompositionMap<'lifespan>) -> bool {
self.composition == other.composition
}
}
impl<'lifespan> FromIterator<(ElementSpecification<'lifespan>, i32)>
for ChemicalCompositionMap<'lifespan>
{
#[inline]
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = (ElementSpecification<'lifespan>, i32)>,
{
let mut composition = ChemicalCompositionMap::new();
for (k, v) in iter {
composition.inc(k, v);
}
composition
}
}
impl<'lifespan> FromIterator<(&'lifespan str, i32)> for ChemicalCompositionMap<'lifespan> {
#[inline]
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = (&'lifespan str, i32)>,
{
let mut composition = ChemicalCompositionMap::new();
for (k, v) in iter {
let elt_spec = ElementSpecification::parse(k).unwrap();
composition.inc(elt_spec, v);
}
composition
}
}
impl<'lifespan> From<Vec<(&'lifespan str, i32)>> for ChemicalCompositionMap<'lifespan> {
#[inline]
fn from(elements: Vec<(&'lifespan str, i32)>) -> Self {
elements.iter().cloned().collect()
}
}
impl<'lifespan> From<Vec<(ElementSpecification<'lifespan>, i32)>>
for ChemicalCompositionMap<'lifespan>
{
fn from(elements: Vec<(ElementSpecification<'lifespan>, i32)>) -> Self {
elements.iter().cloned().collect()
}
}
impl FromStr for ChemicalCompositionMap<'_> {
type Err = FormulaParserError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse()
}
}
impl Display for ChemicalCompositionMap<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&crate::formula::to_formula(self))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_from_vec_str() {
let case = ChemicalCompositionMap::from(vec![("O", 1), ("H", 2)]);
let mut ctrl = ChemicalCompositionMap::new();
ctrl.set(("O").parse::<ElementSpecification>().unwrap(), 1);
ctrl.set(("H").parse::<ElementSpecification>().unwrap(), 2);
assert_eq!(case, ctrl);
}
#[test]
fn test_from_vec_elt_spec() {
let hydrogen = ("H").parse::<ElementSpecification>().unwrap();
let oxygen = ("O").parse::<ElementSpecification>().unwrap();
let case = ChemicalCompositionMap::from(vec![(oxygen, 1), (hydrogen, 2)]);
let mut ctrl = ChemicalCompositionMap::new();
let hydrogen = ("H").parse::<ElementSpecification>().unwrap();
let oxygen = ("O").parse::<ElementSpecification>().unwrap();
ctrl.set(oxygen, 1);
ctrl.set(hydrogen, 2);
assert_eq!(case, ctrl);
}
#[test]
fn test_mass() {
let case = ChemicalCompositionMap::from(vec![("O", 1), ("H", 2)]);
let mass = 18.0105646837;
let calc = case.mass();
assert!((mass - calc).abs() < 1e-6);
}
#[test]
fn test_fmass() {
let mut case = ChemicalCompositionMap::from(vec![("O", 1), ("H", 2)]);
let mass = 18.0105646837;
let calc = case.fmass();
assert!((mass - calc).abs() < 1e-6);
}
#[test]
fn test_add() {
let case = ChemicalCompositionMap::from(vec![("O", 1), ("H", 2)]);
let ctrl = ChemicalCompositionMap::from(vec![("O", 2), ("H", 4)]);
let combo = &case + &case;
assert_eq!(ctrl, combo);
}
#[test]
fn test_sub() {
let case = ChemicalCompositionMap::from(vec![("O", 2), ("H", 4)]);
let ctrl = ChemicalCompositionMap::from(vec![("O", 1), ("H", 2)]);
let combo = &case - &ctrl;
assert_eq!(ctrl, combo);
}
#[test]
fn test_mul() {
let case = ChemicalCompositionMap::from(vec![("O", 1), ("H", 2)]);
let ctrl = ChemicalCompositionMap::from(vec![("O", 2), ("H", 4)]);
let combo = &case * 2;
assert_eq!(ctrl, combo);
}
}