Source code for aim2dat.utils.chem_formula
"""Module to parse representations of chemical formulas into each other."""
# Standard library imports
import re
import math
[docs]
def transform_str_to_dict(formula_str):
"""
Create a dictionary from a formula string.
Parameters
----------
formula_str : str
Chemical formula as string, e.g. ``Fe2O3`, ``H2O``
Returns
-------
formula_dict : dict
Chemical formula as dictionary, e.g. ``{'Fe' : 2.0, 'O' : 3.0}`` or
``{'H' : 2.0, 'O' : 1.0}``
"""
formula_dict = {}
regex = r"(?P<element>[A-Z][a-z]?)(?P<quantity>\d+|-)?"
if isinstance(formula_str, str):
for match in re.finditer(regex, formula_str):
quantity = match["quantity"]
if not quantity:
quantity = 1.0
elif quantity.isdigit():
quantity = float(quantity)
formula_dict[match["element"]] = quantity
elif isinstance(formula_str, dict):
formula_dict = formula_str
else:
raise TypeError("Chemical formula has to be of type str or dict.")
return formula_dict
[docs]
def transform_dict_to_str(formula_dict, output_type=None):
"""
Create a string from a formula dictionary, fractional quantities are rounded.
Parameters
----------
formula_dict : dict
Chemical formula as dictionary, e.g. ``{'Fe' : 2.0, 'O' : 3.0}`` or
``{'H' : 2.0, 'O' : 1.0}``
output_type : None or str
If set to ``'alphabetic'`` the output formula will be alphabetically ordered.
Returns
-------
formula_str : str
Chemical formula as string, e.g. ``Fe2O3`, ``H2O``
"""
if isinstance(formula_dict, dict):
elements = list(formula_dict.keys())
if output_type is not None:
if output_type == "alphabetic":
elements.sort()
else:
raise ValueError(f"The output_type `{output_type}` is not supported.")
formula_l = []
for el in elements:
nr = formula_dict[el]
if nr == 1:
formula_l.append(el)
elif isinstance(nr, float) and nr.is_integer():
formula_l.append(el + str(int(round(nr, 0))))
else:
formula_l.append(el + str(nr))
formula_str = "".join(formula_l)
elif isinstance(formula_dict, str):
formula_str = formula_dict
else:
raise TypeError("Chemical formula has to be of type str or dict.")
return formula_str
[docs]
def transform_dict_to_latexstr(formula_dict):
r"""
Create a string from a formula dictionary, fractional quantities are rounded.
Parameters
----------
formula_dict : dict
Chemical formula as dictionary, e.g. ``{'Fe' : 2.0, 'O' : 3.0}`` or
``{'H' : 2.0, 'O' : 1.0}``.
Returns
-------
formula_str : str
Chemical formula as string with latex formating, e.g.
``r'$\mathrm{Fe}_{\mathrm{2}}\mathrm{O3}$'``, ``r'$\mathrm{H}_{\mathrm{2}}\mathrm{O}$'``.
"""
name = []
for el in [*formula_dict.items()]:
if str(int(el[1])) == "1":
name.append(r"\mathrm{" + str(el[0]) + r"}")
else:
name.append(r"\mathrm{" + str(el[0]) + r"}_\mathrm{" + str(int(round(el[1], 0))) + "}")
return r"$" + r"".join(name) + r"$"
[docs]
def transform_list_to_dict(formula_list):
"""
Convert a list of elements to a dictionary.
Parameters
----------
formula_list : list
Chemical formula as list, e.g. ``['Fe', 'Fe', 'O', 'O', 'O']`` or ``['H', 'O', 'H']``
Returns
-------
formula_dict : dict
Chemical formula as dictionary, e.g. ``{'Fe' : 2.0, 'O' : 3.0}`` or
``{'H' : 2.0, 'O' : 1.0}``
"""
formula_dict = {}
for el in formula_list:
if el in formula_dict.keys():
formula_dict[el] += 1
else:
formula_dict[el] = 1
return formula_dict
[docs]
def transform_list_to_str(formula_list):
"""
Convert a list of elements to a dictionary.
Parameters
----------
formula_list : list
Chemical formula as list, e.g. ``['Fe', 'Fe', 'O', 'O', 'O']`` or ``['H', 'O', 'H']``
Returns
-------
formula_str : str
Chemical formula as string, e.g. ``Fe2O3`, ``H2O``
"""
return transform_dict_to_str(transform_list_to_dict(formula_list))
[docs]
def reduce_formula(formula_dict):
"""
Try to find a reduced formula. Elements with fractional occupation numbers are neglected.
Parameters
----------
formula_dict : dict
Chemical formula as dictionary, e.g. ``{'Fe' : 4.0, 'O' : 6.0}`` or
``{'H' : 2.0, 'O' : 1.0}``
Returns
-------
formula_red : dict
Chemical formula as dictionary, e.g. ``{'Fe' : 2.0, 'O' : 3.0}`` or
``{'H' : 2.0, 'O' : 1.0}``
"""
formula_red = formula_dict.copy()
# Find elements with fractional occupation numbers:
formula_wo_fract = {}
elements_w_fract = []
for element, occ_number in formula_dict.items():
if float(occ_number).is_integer():
formula_wo_fract[element] = occ_number
else:
elements_w_fract.append(element)
# Find greatest common divisor:
is_reduced = True
occ_numbers = list(formula_wo_fract.values())
# In case only one element is available:
if len(occ_numbers) == 1:
gcd = int(occ_numbers[0])
else:
# Calculate the greatest common denominator
gcd = math.gcd(int(occ_numbers[0]), int(occ_numbers[1]))
for occ_number in occ_numbers[2:]:
gcd = math.gcd(gcd, int(occ_number))
# Try to reduce formula:
for element in formula_wo_fract.keys():
if (formula_red[element] / float(gcd)).is_integer:
formula_red[element] /= float(gcd)
else:
is_reduced = False
break
# Reduce fractional occupation numbers:
for element in elements_w_fract:
formula_red[element] /= float(gcd)
# Return reduced (if worked), otherwise original formula:
if is_reduced:
return formula_red
else:
return formula_dict
# compare_chem_formulas
[docs]
def compare_formulas(chem_formula1, chem_formula2, reduce_formulas=False):
"""
Check if two chemical formulas are identical.
Parameters
----------
chem_formula1 : dict
Chemical formula as dictionary, e.g. ``{'Fe' : 4.0, 'O' : 6.0}`` or
``{'H' : 2.0, 'O' : 1.0}``.
chem_formula2 : dict
Chemical formula as dictionary, e.g. ``{'Fe' : 4.0, 'O' : 6.0}`` or
``{'H' : 2.0, 'O' : 1.0}``.
reduce_formulas : bool
Whether to reduce the formulas before comparison.
Returns
-------
match : bool
``True`` if the two forumals are identical
"""
if reduce_formulas:
chem_formula1 = reduce_formula(chem_formula1)
chem_formula2 = reduce_formula(chem_formula2)
elements_cf2 = list(chem_formula2.keys())
match = True
for el1 in chem_formula1.keys():
if el1 in chem_formula2:
elements_cf2.remove(el1)
if chem_formula1[el1] != chem_formula2[el1]:
match = False
break
else:
match = False
break
# In case some elements were not in formula1 but in formula2:
if len(elements_cf2) > 0:
match = False
return match