from pymeasurement.util.parser import Parser
from pymeasurement.util.chem.element import Element
from pymeasurement.measurement import Measurement
import re
[docs]class Compound(Parser):
"""A class to represent a chemical compound.
:param string: The string to parse.
:type string: str
"""
latexPrint = False # Whether to print the compound in latex format.
[docs] def setLatexPrint(value):
"""Set whether to print the compound in latex format.
:param value: Whether to print the compound in latex format.
:type value: bool
"""
Element.setLatexPrint(value)
Compound.latexPrint = value
[docs] def __init__(self, string):
"""Compound Constructor
"""
super().__init__(string)
self.composition = {}
self.element = ""
self.number = ""
self.parenthesesOn = False
self.parentheses = ')'
self.subString = ""
self.compoundString = self.string
self.stateString = ""
self.splitString(self.string, checks = self.split)
for i in range(len(self.compoundString)):
if not self.compoundString[i].isdigit():
self.compoundString = self.compoundString[i:].strip()
break
self.readByCharacter(self.compoundString, checks = self.checks, endSetup = self.save)
self.mass = Measurement.sum([e.mass * Measurement.fromFloat(self.composition[e]) for e in self.composition])
[docs] def split(self, string):
"""Split the string into a compound string and a state string.
:param string: The string to split.
:type string: str
"""
if '(s)' in string or '(l)' in string or '(g)' in string or '(aq)' in string:
if '(s)' in string:
self.stateString = '(s)'
elif '(l)' in string:
self.stateString = '(l)'
elif '(g)' in string:
self.stateString = '(g)'
else:
self.stateString = '(aq)'
self.compoundString = self.string.replace(self.stateString, '').strip()
[docs] def checks(self, i, char):
"""Check the character.
:param i: The index of the character.
:type i: int
:param char: The character to check.
:type char: str
"""
if char.isalpha():
if self.parenthesesOn:
self.subString += char
else:
if char.isupper():
self.save()
self.element += char
else:
self.element += char
elif char.isdigit():
if self.parenthesesOn:
self.subString += char
else:
self.number += char
else:
if char == '(' or char == '[':
if not self.parenthesesOn:
self.save()
self.parenthesesOn = True
self.parentheses = ')' if char == '(' else ']'
else:
self.subString += char
elif char == ')' or char == ']':
if char == self.parentheses:
self.parenthesesOn = False
else:
self.subString += char
else:
raise Exception(f"Compound Parser Exception: Unknown character '{char}'")
[docs] def saveElement(self, element, number, multiple = 1):
"""Save the element to the composition.
:param element: The element to save.
:type element: str
:param number: The number of the element.
:type number: str
:param multiple: The multiple of the element.
:type multiple: int
"""
if Element(element) not in self.composition:
self.composition[Element(element)] = (int(number) if number else 1) * multiple
else:
self.composition[Element(element)] += (int(number) if number else 1) * multiple
[docs] def saveSubcompound(self):
"""Save subcompound to the composition.
"""
subCompound = Compound(self.subString)
for element in subCompound.composition:
self.saveElement(element.string, self.number, multiple = subCompound.composition[element])
[docs] def save(self):
"""Save the element or subcompound to the composition.
"""
if self.element or self.subString:
if self.element:
self.saveElement(self.element, self.number)
if self.subString:
self.saveSubcompound()
self.element = ""
self.number = ""
self.subString = ""
[docs] def __str__(self, textOverride = False):
"""Get the string representation of the compound.
:param textOverride: Whether to override the latex print setting.
:type textOverride: bool
:return: The string representation of the compound.
:rtype: str
"""
if Compound.latexPrint and not textOverride:
allCoefficients = re.findall("\d+", self.compoundString)
finalString = ''
currentIndex = 0
for i in allCoefficients:
ind = self.compoundString[currentIndex:].index(i)
finalString += self.compoundString[currentIndex:currentIndex + ind]+f'_{{{i}}}'
currentIndex = currentIndex + ind + len(i)
if currentIndex < len(self.compoundString):
finalString += self.compoundString[currentIndex:]
return finalString + (self.stateString if self.stateString else '')
return self.compoundString + (self.stateString if self.stateString else '')
[docs] def __eq__(self, other):
"""Check if the compound is equal to another compound.
:param other: The other compound to check.
:type other: Compound
:return: Whether the compounds are equal.
:rtype: bool
"""
return self.composition == other.composition
[docs] def __ne__(self, other):
"""Check if the compound is not equal to another compound.
:param other: The other compound to check.
:type other: Compound
:return: Whether the compounds are not equal.
:rtype: bool
"""
return self.composition != other.composition
[docs] def massPercentComposition(self):
"""Get the mass percent composition of the compound.
:return: The mass percent composition of the compound.
:rtype: list
"""
return [['Element', 'Mass']]+[[str(i), self.composition[i]*i.mass] for i in self.composition]
[docs] def molePercentComposition(self):
"""Get the mole percent composition of the compound.
:return: The mole percent composition of the compound.
:rtype: list
"""
return [['Element', 'Moles']]+[[str(i), self.composition[i]] for i in self.composition]