Skip to content

Commit

Permalink
Merge pull request #1 from glaciapag/feat/stoich-calculations
Browse files Browse the repository at this point in the history
Separated stoichiometric functions from Compound class and placed in …
  • Loading branch information
glaciapag committed Jan 8, 2023
2 parents 62f9069 + 9add0c6 commit 89e3e9e
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 22 deletions.
51 changes: 51 additions & 0 deletions src/pychemkit/exceptions/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from typing import List, Union

from pychemkit.foundations.element import Element
from pychemkit.foundations.compound import Compound
from pychemkit.exceptions.invalids import NotAValidCompoundException, NotAValidElementException
from pychemkit.utils.utils import is_number


def entity_handler(entity: Union[str, Element, Compound]) -> Union[str, Element, Compound]:
"""
Handler for string inputs (not an instance of element, or a compound)
:param entity: string input of an element or a compound
:return: if valid, an instance of element of a compound, raises an exception if not
"""
if isinstance(entity, str):
try:
entity = Element(entity)
except NotAValidElementException:
try:
entity = Compound(entity)
except NotAValidCompoundException:
raise ValueError(
f'{entity} is not a valid Element of Compound'
)

return (entity, type(entity))


def amount_handler(amount: float) -> float:
"""
Handler for amount inputs
:param amount: float or int input representing valid amounts in grams, moles, etc
:return: raises a TypeError if not a valid amount
"""
if is_number(amount):
return amount
else:
raise TypeError('Enter a valid amount')


def selection_handler(input: str, selection: List[str]):
"""
Handler for selections
:param input: input string
:param selection: list of valid selections
:return: raises a ValueError if the selection is not in the list
"""
if input not in selection:
raise ValueError(
f'Invalid input: {input}. Valud options are {selection}'
)
14 changes: 0 additions & 14 deletions src/pychemkit/foundations/compound.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,6 @@ def composition(self):
def molecular_mass(self):
return self._get_molecular_mass()

def mass_to_moles(self, mass):
if is_number(mass):
return mass / self._get_molecular_mass()
else:
return 'Enter a valid mass in grams'

def mass_to_atoms(self, mass):
if is_number(mass):
moles = self._get_moles(mass)
atoms = self.moles_to_atoms(moles)
return atoms
else:
return 'Enter a valid mass in grams'

def moles_to_mass(self, moles):
if is_number(moles):
return moles * self._get_molecular_mass()
Expand Down
36 changes: 36 additions & 0 deletions src/pychemkit/stoich/converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from typing import Union, List

import numpy as np
from pychemkit.exceptions.invalids import NotAValidCompoundException, NotAValidElementException
from pychemkit.foundations.element import Element
from pychemkit.foundations.compound import Compound
from pychemkit.foundations.constants import AVOGADRO_NUM
from pychemkit.utils.utils import is_number
from pychemkit.exceptions.handlers import entity_handler, amount_handler, selection_handler


class StoichConverter:

def convert_mass(self, entity: Union[str, Element, Compound], mass: float, to: str) -> float:
"""
Converts mass in grams to mole, given a compound
:param Compound: An instance of a compound
:param mass: mass in grams
"""

selection_handler(to, ['moles', 'atoms'])
entity, ent_type = entity_handler(entity)

mass = amount_handler(mass)

if ent_type == Element:
ent_mass = entity.atomic_mass
elif ent_type == Compound:
ent_mass = entity.molecular_mass

moles = mass / ent_mass

if to == 'moles':
return moles
elif to == 'atoms':
return np.round(moles * AVOGADRO_NUM, 1)
8 changes: 0 additions & 8 deletions test/test_compound.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,6 @@ def test_invalid_compounds(self):
self.assertRaises(NotAValidCompoundException, Compound, 'HGA')
self.assertRaises(NotAValidCompoundException, Compound, 'AL')

def test_mass_to_moles(self):
mass_test_grams = [128, 150, 400, 0.4, 10.9]
expected_moles = [x / z for x, z in zip(mass_test_grams, self.mmass)]

for index, tc in enumerate(self.test_compounds):
moles = tc.mass_to_moles(mass_test_grams[index])
self.assertAlmostEqual(moles, expected_moles[index], 3)

def test_get_element_percentage(self):

mass_c = [24.02, 36.03, 72.06, 144.12, 12.01]
Expand Down
58 changes: 58 additions & 0 deletions test/test_stoich.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import unittest

from pychemkit.foundations.element import Element
from pychemkit.foundations.compound import Compound
from pychemkit.foundations.constants import AVOGADRO_NUM
from pychemkit.stoich.converter import StoichConverter


class TestStoich(unittest.TestCase):

def setUp(self) -> None:

el1 = Element('C')
el2 = Element('Hg')
el3 = 'Po'

cpd1 = Compound('CH3COOH')
cpd2 = Compound('NaCl')
cpd3 = 'MgSO4'

self.test_elements = [el1, el2, el3]
self.test_compounds = [cpd1, cpd2, cpd3]

self.test_elem_masses = [12.01, 200.59, 208.98243]
self.test_cpd_masses = [60.025, 58.44, 120.366]

self.converter = StoichConverter()

def test_mass_to_moles(self):

convert_to_list = ['moles']

expected_moles = {
'moles': [1, 1, 1],
'atoms': [AVOGADRO_NUM] * 3
}

for to in convert_to_list:
for i, (el, cpd) in enumerate(zip(self.test_elements, self.test_compounds)):
expected = expected_moles.get(to)[i]
actual_el = self.converter.convert_mass(
entity=el,
mass=self.test_elem_masses[i],
to=to
)

actual_cpd = self.converter.convert_mass(
entity=cpd,
mass=self.test_cpd_masses[i],
to=to
)

self.assertAlmostEqual(actual_el, expected, 1)
self.assertAlmostEqual(actual_cpd, expected, 1)


if __name__ == '__main__':
unittest.main()

0 comments on commit 89e3e9e

Please sign in to comment.