Skip to content

Commit

Permalink
Build Doc (#5)
Browse files Browse the repository at this point in the history
* update rc_split

* format package

* prepare release

* Add subpackage SynChem

* fix bug version

* update readme

* fix typos

* build doc

* fix format

* fix format
  • Loading branch information
TieuLongPhan authored Oct 18, 2024
1 parent 69ebdba commit 155f869
Show file tree
Hide file tree
Showing 25 changed files with 1,838 additions and 15 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/build-doc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Build documentation for GitHub Pages

on:
push:
branches:
- main

workflow_dispatch: # Enable manual action trigger

env:
PYTHONPATH: .

jobs:
build_documentation_for_github_pages:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip' # caching pip dependencies
- run: pip install -r requirements.txt
- run: pip install sphinx sphinx-rtd-theme
- run: python3 -m sphinx ./doc docs
- name: publish doc
shell: bash
run: |
git config user.name "GitHub Action"
git config user.email "[email protected]"
git add -f docs/
git commit -m "Doc build"
git push origin `git subtree split --prefix docs/ main`:gh-pages --force
2 changes: 1 addition & 1 deletion .github/workflows/test-and-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name: Test & Lint

on:
push:
branches: [ "main", "stagging" ]
branches: [ "main", "dev" ]
pull_request:
branches: [ "main" ]

Expand Down
20 changes: 20 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
40 changes: 29 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

**Utils for Synthesis Planning**

SynUtils is a collection of tools designed to support the planning and execution of chemical synthesis. This repository provides computational resources and utilities aimed at enhancing the efficiency and accuracy of synthesis planning through the use of advanced algorithms and AI-driven models.
SynUtils is a collection of tools designed to support the planning and execution of chemical synthesis.

![SynUtils](Docs/Figure/synutils.png)
![SynUtils](https://raw.githubusercontent.com/TieuLongPhan/SynUtils/main/Docs/Figure/synutils.png)

Our tools are tailored to assist researchers and chemists in navigating complex chemical reactions and synthesis pathways, leveraging the power of modern computational chemistry. Whether you're designing novel compounds or optimizing existing processes, SynUtils aims to provide the critical tools you need.

Expand All @@ -29,18 +29,21 @@ For more details on each utility within the repository, please refer to the docu
conda activate synutils-env
```

3. **Cloning and Installing SynUtils:**
Clone the SynUtils repository from GitHub and install it:
3. **Install from PyPi:**
The easiest way to use SynTemp is by installing the PyPI package
[synutility](https://pypi.org/project/synutility/).

```bash
git clone https://github.com/TieuLongPhan/SynUtils.git
cd SynUtils
pip install -r requirements.txt
pip install black flake8 pytest # black for formating, flake8 for checking format, pytest for testing
```
pip install synutility
```
Optional if you want to install full version
```
pip install synutility[all]
```

## For contributors

## Setting Up Your Development Environment
We're welcoming new contributors to build this project better. Please not hesitate to inquire me via [email][[email protected]].

Before you start, ensure your local development environment is set up correctly. Pull the latest version of the `main` branch to start with the most recent stable code.

Expand Down Expand Up @@ -97,4 +100,19 @@ git pull
3. **Create a Pull Request**:
Open a pull request from your feature branch to the `stagging` branch. Ensure the pull request description clearly describes the changes and any additional context necessary for review.

## Important Notes
## Contributing
- [Tieu-Long Phan](https://tieulongphan.github.io/)
- [Phuoc-Chung Nguyen Van](https://github.com/phuocchung123)

## Deployment timeline

We plan to update new version quarterly.


## License

This project is licensed under MIT License - see the [License](LICENSE) file for details.

## Acknowledgments

This project has received funding from the European Unions Horizon Europe Doctoral Network programme under the Marie-Skłodowska-Curie grant agreement No 101072930 ([TACsy](https://tacsy.eu/) -- Training Alliance for Computational)
Empty file.
120 changes: 120 additions & 0 deletions Test/SynChem/Reaction/test_balance_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import unittest
from synutility.SynChem.Reaction.balance_check import BalanceReactionCheck


class TestBalanceReactionCheck(unittest.TestCase):

def test_parse_input_string(self):
input_data = "C>>O"
expected_output = [{"reactions": "C>>O"}]
result = BalanceReactionCheck.parse_input(input_data)
self.assertEqual(result, expected_output)

def test_parse_input_list_of_strings(self):
input_data = ["C>>O", "[HH]+O=O>>O"]
expected_output = [{"reactions": "C>>O"}, {"reactions": "[HH]+O=O>>O"}]
result = BalanceReactionCheck.parse_input(input_data)
self.assertEqual(result, expected_output)

def test_parse_input_list_of_dicts(self):
input_data = [{"reactions": "C>>O"}, {"reactions": "[HH]+O=O>>O"}]
expected_output = [{"reactions": "C>>O"}, {"reactions": "[HH]+O=O>>O"}]
result = BalanceReactionCheck.parse_input(input_data)
self.assertEqual(result, expected_output)

def test_parse_input_mixed_list(self):
input_data = ["C>>O", {"reactions": "[HH]+O=O>>O"}]
expected_output = [{"reactions": "C>>O"}, {"reactions": "[HH]+O=O>>O"}]
result = BalanceReactionCheck.parse_input(input_data)
self.assertEqual(result, expected_output)

def test_parse_input_invalid_type(self):
input_data = 123 # Not a string or list
with self.assertRaises(ValueError):
BalanceReactionCheck.parse_input(input_data)

def test_parse_reaction(self):
reaction_smiles = "C+C>>O+O"
expected_output = ("C+C", "O+O")
result = BalanceReactionCheck.parse_reaction(reaction_smiles)
self.assertEqual(result, expected_output)

def test_parse_reaction_single(self):
reaction_smiles = "CC>>OO"
expected_output = ("CC", "OO")
result = BalanceReactionCheck.parse_reaction(reaction_smiles)
self.assertEqual(result, expected_output)

def test_single_smiles_balanced(self):
"""Test a single balanced reaction in SMILES format."""
smiles = (
"Clc1cnc2nc1Nc1ccc(OCCC3CCNCC3)c(c1)CCc1cncc(c1)N2.O=C=NCc1ccco1"
+ ">>O=C(NCc1ccco1)N1CCC(CCOc2ccc3cc2CCc2cncc(c2)Nc2ncc(Cl)c(n2)N3)CC1"
)
checker = BalanceReactionCheck()
balanced = checker.rsmi_balance_check(smiles)
self.assertTrue(balanced)

def test_single_smiles_unbalanced(self):
"""Test a single unbalanced reaction in SMILES format."""
smiles = "CC(=O)O.CCO>>CC(=O)OCC"
checker = BalanceReactionCheck()
balanced = checker.rsmi_balance_check(smiles)
self.assertFalse(balanced)

def test_list_of_dicts_mixed(self):
"""Test a list of dictionaries with mixed balanced and unbalanced reactions."""
reactions = [
{
"R-id": "test_1",
"reactions": (
"Clc1cnc2nc1Nc1ccc(OCCC3CCNCC3)c(c1)CCc1cncc(c1)N2.O=C=NCc1ccco1"
+ ">>O=C(NCc1ccco1)N1CCC(CCOc2ccc3cc2CCc2cncc(c2)Nc2ncc(Cl)c(n2)N3)CC1"
),
},
{"R-id": "test_2", "reactions": "CC(=O)O.CCO>>CC(=O)OCC"},
]
checker = BalanceReactionCheck()
balanced, unbalanced = checker.dicts_balance_check(
reactions, rsmi_column="reactions"
)
self.assertEqual(len(balanced), 1)
self.assertEqual(len(unbalanced), 1)
self.assertEqual(balanced[0]["R-id"], "test_1")
self.assertEqual(unbalanced[0]["R-id"], "test_2")

def test_valid_smiles_single_molecule(self):
"""
Test that the function returns the correct molecular formula for a valid SMILES string.
"""
smiles = "CCO" # Ethanol
expected_formula = "C2H6O"
result = BalanceReactionCheck().get_combined_molecular_formula(smiles)
self.assertEqual(
result, expected_formula, "Molecular formula for ethanol should be C2H6O."
)

def test_valid_smiles_complex_molecule(self):
"""
Test that the function returns the correct molecular formula for a more complex molecule.
"""
smiles = "CC(C)C(=O)O" # Isobutyric acid
expected_formula = "C4H8O2"
result = BalanceReactionCheck().get_combined_molecular_formula(smiles)
self.assertEqual(
result,
expected_formula,
"Molecular formula for isobutyric acid should be C4H8O2.",
)

def test_invalid_smiles(self):
"""
Test that the function returns an empty string when an invalid SMILES string is provided.
"""
invalid_smiles = "INVALID"
result = BalanceReactionCheck().get_combined_molecular_formula(invalid_smiles)
self.assertEqual(result, "", "Invalid SMILES should return an empty string.")


if __name__ == "__main__":
unittest.main()
52 changes: 52 additions & 0 deletions Test/SynChem/Reaction/test_deionize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import unittest
from synutility.SynChem.Reaction.deionize import (
Deionize,
)


class TestDeionize(unittest.TestCase):

def test_uncharge_anion(self):
smiles = "[OH-]"
uncharged_smiles = Deionize.uncharge_anion(smiles)
self.assertEqual(uncharged_smiles, "O")

def test_uncharge_cation(self):
smiles = "[NH4+]"
uncharged_smiles = Deionize.uncharge_cation(smiles)
self.assertEqual(
uncharged_smiles, "[NH4]"
) # can uncharge to [NH4] but will not pass through

def test_ammonia_hydroxide_standardize(self):
reaction_smiles = "[NH4+].[OH-]>>N.O"
standardized_smiles = Deionize.ammonia_hydroxide_standardize(reaction_smiles)
self.assertTrue("N.O" in standardized_smiles or "O.N" in standardized_smiles)

def test_not_uncharge_smiles(self):
charge_smiles = "[NH4+].[OH-]"
uncharged_smiles = Deionize.uncharge_smiles(charge_smiles)
self.assertTrue("N" in uncharged_smiles and "O" in uncharged_smiles)
self.assertTrue(
uncharged_smiles in ["[NH4+].[OH-]", "[OH-].[NH4+]"]
) # can not uncharge amonium

def test_uncharge_smiles(self):
charge_smiles = "[Na+].[OH-]"
uncharged_smiles = Deionize.uncharge_smiles(charge_smiles)
self.assertEqual(uncharged_smiles, "O[Na]") # can not uncharge amonium

def test_apply_uncharge_smiles_to_reactions(self):
reactions = [{"reactants": "[NH4+].[OH-]", "products": "N.O"}]
uncharged_reactions = Deionize.apply_uncharge_smiles_to_reactions(
reactions, Deionize.uncharge_smiles
)
for reaction in uncharged_reactions:
self.assertTrue(
"N" in reaction["new_reactants"] and "O" in reaction["new_reactants"]
)
self.assertTrue(reaction["success"])


if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit 155f869

Please sign in to comment.