# -*- coding: utf-8 -*-
"""
Module containing functions useful for manipulating chemical formulas
"""
import re
from math import gcd
from functools import reduce
from collections import defaultdict
[docs]def count_elements(compound, total=False):
""" Returns the number of atoms in a chemical system.
Parameters
----------
compound : str
the compound of interest
total : bool
If True, the total number of atoms in the system is also returned
Returns
-------
el_count : dict/tuple
Dictionary: ``atom:number of atoms`` for each atom in :data:`compound`.
If total, returns also the number of elements in :data:`compound`
"""
re_spec = re.compile(r'([A-Z][a-z]?)([0-9]*)')
groups_comp = re.findall(re_spec, compound)
el_count = defaultdict(float)
for g in groups_comp:
if g[1]:
el_count[g[0]] += float(g[1])
else:
el_count[g[0]] += 1
if total:
return el_count, sum(el_count.values())
return el_count
[docs]def get_stoichiometry(compound, fractional=True):
""" Given :data:`compound`, it returns its stoichiometry.
Parameters
----------
compound : string
the compound's formula
fractional : bool
if True, for each element is returned its molar fraction;
otherwise, it is returned the number of atoms per formula
Returns
-------
elements_count : dict
A dictionary element:coefficient for each element in :data:`compound`
"""
elements_count = count_elements(compound)
if fractional:
total_atoms = sum(elements_count.values())
for key in elements_count.keys():
elements_count[key] /= total_atoms
return elements_count
[docs]def get_number_fu(compound, fu=None):
""" Returns how many formula units :data:`fu` are present in
:data:`compound`
Parameters
----------
compound : string
the formula of the compound
fu : string
the formula unit
If None, the actual formula unit is used
Returns
-------
no_units : float
The number of formula units in :data:`compound`
"""
if fu is None:
fu = get_formula_unit(compound)
el_count = count_elements(compound)
el_count_fu = count_elements(fu)
multiples = []
for key, value in el_count.items():
multi = value/el_count_fu[key]
try:
assert multi%1 < 1e-6
except AssertionError:
raise AssertionError('The compound {:s} has not an integer number '
'of formula units {:s}'.format(compound, fu))
multiples.append(int(multi))
no_units = multiples[0]
try:
assert all(x == no_units for x in multiples)
except AssertionError:
raise AssertionError('The compound {:s} has not an integer number '
'of formula units {:s}'.format(compound, fu))
return no_units