Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FINITO #33

Merged
merged 1 commit into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 39 additions & 14 deletions src/chembalancer/chembalancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from collections import defaultdict
from rdkit import Chem
import requests

import base64


def get_smiles_from_name(name):
Expand Down Expand Up @@ -45,11 +45,11 @@ def count_atoms(smiles):
mol = Chem.MolFromSmiles(smiles)
atom_counts = defaultdict(int)
if mol:
mol = Chem.AddHs(mol)
for atom in mol.GetAtoms():
atom_counts[atom.GetSymbol()] += 1
return dict(atom_counts)


def solve_ilp(A):
""" Solve the integer linear programming problem to find stoichiometric coefficients. """
num_vars = A.shape[1]
Expand Down Expand Up @@ -169,35 +169,60 @@ def create_reaction_string(reactants, products):
products_str = '.'.join(products)
return f"{reactants_str}>>{products_str}"


def display_svg(svg):
"""Display SVG in Streamlit using markdown with unsafe HTML."""
b64 = base64.b64encode(svg.encode('utf-8')).decode("utf-8")
html = f"<img src='data:image/svg+xml;base64,{b64}'/>"
st.markdown(html, unsafe_allow_html=True)
return html


def compound_state(compound, temp):
CAS_compound = CAS_from_any(compound)
boiling_p = Tb(CAS_compound)
melting_p = Tm(CAS_compound)
if temp <= melting_p:

if float(temp) <= float(melting_p):
return 'solid'
elif temp > melting_p and temp <= boiling_p:
return 'liquid'
else:
elif float(temp) >= float(boiling_p):
return 'gas'
else:
return 'liquid'

def enthalpy(coeff, compound, state):
Cas_compound=CAS_from_any(compound)
if state == 'solid':
return coeff * Hfs(CAS_from_any(compound))
if Hfs(Cas_compound)== None:
return 0
else:
return float(coeff) * Hfs(Cas_compound)
elif state == 'liquid':
return coeff * Hfl(CAS_from_any(compound))
if Hfl(CAS_from_any(compound))== None:
return 0
else:
return float(coeff) * Hfl(Cas_compound)
else:
return coeff * Hfg(CAS_from_any(compound))

if Hfg(CAS_from_any(compound))== None:
return 0
else:
return float(coeff) * Hfg(Cas_compound)

def entropy(coeff, compound, state):
Cas_compound=CAS_from_any(compound)
if state == 'solid':
return coeff * S0s(CAS_from_any(compound))
if S0s(Cas_compound)== None:
return 0
else:
return float(coeff) * S0s(Cas_compound)
elif state == 'liquid':
return coeff * S0l(CAS_from_any(compound))
if S0l(CAS_from_any(compound))== None:
return 0
else:
return float(coeff) * S0l(Cas_compound)
else:
return coeff * S0g(CAS_from_any(compound))
if S0g(CAS_from_any(compound))== None:
return 0
else:
return float(coeff) * S0g(Cas_compound)


17 changes: 17 additions & 0 deletions tests/test_compound_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pytest
from chemicals import CAS_from_any
from chembalancer.chembalancer import compound_state

@pytest.mark.parametrize(
"compound, temp_kelvin, expected_state",
[
("water", 298.15, "liquid"),
("water", 383.15, "gas"),
("ethanol", 298.15, "liquid"),
("ethanol", 351.15, "liquid"),
("iron", 298.15, "solid"),
("oxygen", 73.15, "liquid"),
]
)
def test_compound_state(compound, temp_kelvin, expected_state):
assert compound_state(compound, temp_kelvin) == expected_state
47 changes: 47 additions & 0 deletions tests/test_create_reaction_string.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from chembalancer.chembalancer import create_reaction_string


def test_create_reaction_string():
# Test case 1: Reaction with simple reactants and products
reactants_1 = ['CH4', 'O2']
products_1 = ['CO2', 'H2O']
expected_output_1 = "CH4.O2>>CO2.H2O"
assert create_reaction_string(reactants_1, products_1) == expected_output_1

# Test case 2: Reaction with more complex reactants and products
reactants_2 = ['C2H6', 'O2']
products_2 = ['CO2', 'H2O', 'C2H5OH']
expected_output_2 = "C2H6.O2>>CO2.H2O.C2H5OH"
assert create_reaction_string(reactants_2, products_2) == expected_output_2

# Test case 3: Reaction with a single reactant and a single product
reactants_3 = ['H2']
products_3 = ['H2O']
expected_output_3 = "H2>>H2O"
assert create_reaction_string(reactants_3, products_3) == expected_output_3

# Test case 4: Reaction with an empty list of reactants
reactants_4 = []
products_4 = ['H2O']
expected_output_4 = ">>H2O"
assert create_reaction_string(reactants_4, products_4) == expected_output_4

# Test case 5: Reaction with an empty list of products
reactants_5 = ['H2']
products_5 = []
expected_output_5 = "H2>>"
assert create_reaction_string(reactants_5, products_5) == expected_output_5

# Test case 6: Reaction with reactants and products as empty lists
reactants_6 = []
products_6 = []
expected_output_6 = ">>"
assert create_reaction_string(reactants_6, products_6) == expected_output_6

# Test case 7: Reaction with invalid input (integers instead of strings)
reactants_7 = [1, 2]
products_7 = [3, 4]
try:
create_reaction_string(reactants_7, products_7)
except TypeError as e:
assert str(e) == "sequence item 0: expected str instance, int found"
32 changes: 32 additions & 0 deletions tests/test_display_svg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# tests/test_display_svg.py

from chembalancer.chembalancer import display_svg
import base64

def test_display_svg():
# Test case 1: Display SVG with a simple circle
svg_content_1 = """
<svg height="100" width="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>
"""
expected_html_1 = f'<img src=\'data:image/svg+xml;base64,{base64.b64encode(svg_content_1.encode("utf-8")).decode("utf-8")}\'/>'
assert display_svg(svg_content_1) == expected_html_1

# Test case 2: Display SVG with a square
svg_content_2 = """
<svg height="100" width="100">
<rect width="100" height="100" style="fill:rgb(0,0,255);stroke-width:1;stroke:rgb(0,0,0)" />
</svg>
"""
expected_html_2 = f'<img src=\'data:image/svg+xml;base64,{base64.b64encode(svg_content_2.encode("utf-8")).decode("utf-8")}\'/>'
assert display_svg(svg_content_2) == expected_html_2

# Test case 3: Display SVG with a line
svg_content_3 = """
<svg height="100" width="100">
<line x1="0" y1="0" x2="100" y2="100" style="stroke:rgb(255,0,0);stroke-width:2" />
</svg>
"""
expected_html_3 = f'<img src=\'data:image/svg+xml;base64,{base64.b64encode(svg_content_3.encode("utf-8")).decode("utf-8")}\'/>'
assert display_svg(svg_content_3) == expected_html_3
34 changes: 34 additions & 0 deletions tests/test_enthalpy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import pytest
from unittest.mock import patch
from chembalancer.chembalancer import enthalpy
from chemicals import CAS_from_any, Tb, Tm, Tc, Hfs, Hfl, Hfg, S0s, S0l, S0g

@pytest.mark.parametrize(
"coeff, compound, state, expected_enthalpy",
[
(1.0, "water", "solid", 0.0),
(2.0, "water", "liquid", -571650.0),
(3.0, "water", "gas", -725466.0),
(1.0, "ethanol", "solid", 0.0),
(2.0, "ethanol", "liquid", -554060),
(3.0, "ethanol", "gas", -703710.0),
(1.0, "iron", "solid", 0.0),
(2.0, "iron", "liquid", 24800.0),
(3.0, "iron", "gas", 1248900.0),
]
)
def test_enthalpy(coeff, compound, state, expected_enthalpy):
with patch("chemicals.CAS_from_any") as mock_CAS_from_any:
mock_CAS_from_any.return_value = "dummy_cas"

with patch("chemicals.Hfs") as mock_Hfs, \
patch("chemicals.Hfl") as mock_Hfl, \
patch("chemicals.Hfg") as mock_Hfg:

mock_Hfs.return_value = 0.0
mock_Hfl.return_value = 333.55
mock_Hfg.return_value = 891.8

result = enthalpy(coeff, compound, state)

assert result == pytest.approx(expected_enthalpy, rel=1e-2)
44 changes: 44 additions & 0 deletions tests/test_entropy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import pytest
from unittest.mock import patch
from chemicals import CAS_from_any, S0s, S0l, S0g
from chembalancer.chembalancer import entropy

@pytest.mark.parametrize(
"coeff, compound, state, expected_entropy",
[
(1.0, "water", "solid", 0.0),
(2.0, "water", "liquid", 141),
(3.0, "water", "gas", 566.4),
(1.0, "ethanol", "solid", 0.0),
(2.0, "ethanol", "liquid", 321),
(3.0, "ethanol", "gas", 845),
(1.0, "iron", "solid", 27.2),
(2.0, "iron", "liquid", 69),
(3.0, "iron", "gas", 540),
]
)

def test_entropy(coeff, compound, state, expected_entropy):
with patch("chemicals.CAS_from_any") as mock_CAS_from_any:
mock_CAS_from_any.return_value = "dummy_cas"

with patch("chemicals.S0s") as mock_S0s, \
patch("chemicals.S0l") as mock_S0l, \
patch("chemicals.S0g") as mock_S0g:

if state == "solid":
mock_S0s.return_value = expected_entropy
mock_S0l.return_value = None
mock_S0g.return_value = None
elif state == "liquid":
mock_S0s.return_value = None
mock_S0l.return_value = expected_entropy
mock_S0g.return_value = None
else:
mock_S0s.return_value = None
mock_S0l.return_value = None
mock_S0g.return_value = expected_entropy

result = entropy(coeff, compound, state)

assert result == pytest.approx(expected_entropy, rel=1e-2)
Loading