Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
10 changes: 2 additions & 8 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python 3.11
- name: Set up Python 3.12
uses: actions/setup-python@v4
with:
python-version: '3.11'
python-version: '3.12'

- name: Install uv
run: |
Expand Down Expand Up @@ -45,11 +45,5 @@ jobs:
- name: Run ruff (formatter)
run: ruff format --check

- name: Run isort
run: isort --check --profile black .

- name: Run mypy
run: mypy .

- name: Run tests
run: pytest -v
43 changes: 43 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Tests

on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python 3.12
uses: actions/setup-python@v4
with:
python-version: '3.12'

- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH

- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/uv
~/.uv
.venv
key: ${{ runner.os }}-uv-${{ hashFiles('pyproject.toml') }}
restore-keys: |
${{ runner.os }}-uv-

- name: Create and activate virtual environment
run: |
uv venv
echo "$PWD/.venv/bin" >> $GITHUB_PATH

- name: Install dependencies
run: uv pip install -e ".[dev]"

- name: Run tests with coverage
run: |
pytest -v --cov=fragmentretro --cov-report=xml --cov-report=term
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ wheels/
*.egg-info/
.installed.cfg
*.egg
.mypy_cache/
.ruff_cache/

# Virtual Environment
.venv/
Expand Down
13 changes: 3 additions & 10 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,16 @@ repos:
hooks:
- id: ruff
name: ruff (linter)
entry: ruff check
language: system
entry: ruff check --fix
language: python
types: [python]

- id: ruff-format
name: ruff (formatter)
entry: ruff format
language: system
language: python
types: [python]

- id: isort
name: isort
entry: isort
language: system
types: [python]
args: [--profile=black]

# - id: mypy
# name: mypy
# entry: mypy
Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12.10
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2025 Batista Lab (Yale University)
Copyright (c) 2025 Yu Shee

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
16 changes: 9 additions & 7 deletions data/process_buyables.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,23 @@

from tqdm import tqdm

from FragmentRetro.utils.filter_compound import precompute_properties
from FragmentRetro.utils.helpers import canonicalize_smiles
from FragmentRetro.utils.logging_config import logger
from fragmentretro.utils.filter_compound import precompute_properties
from fragmentretro.utils.helpers import canonicalize_smiles
from fragmentretro.utils.logging_config import logger

DATA_PATH = Path(__file__).parent
BUYABLES_PATH = DATA_PATH / "buyables"
PRECOMPUTE_PATH = DATA_PATH / "precompute"

if __name__ == "__main__":
logger.info("Decompressing buyables data...")
with gzip.open(BUYABLES_PATH / "buyables_all.json.gz", "rb") as f_in:
with open(BUYABLES_PATH / "buyables_all.json", "wb") as f_out:
shutil.copyfileobj(f_in, f_out)
with (
gzip.open(BUYABLES_PATH / "buyables_all.json.gz", "rb") as f_in,
open(BUYABLES_PATH / "buyables_all.json", "wb") as f_out,
):
shutil.copyfileobj(f_in, f_out)
logger.info("Loading buyables data...")
with open(BUYABLES_PATH / "buyables_all.json", "r") as f:
with open(BUYABLES_PATH / "buyables_all.json") as f:
buyables = json.load(f)
buyables_smiles = [buyable["smiles"] for buyable in buyables]

Expand Down
6 changes: 3 additions & 3 deletions data/process_stock.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
from pathlib import Path

from FragmentRetro.utils.filter_compound import precompute_properties
from fragmentretro.utils.filter_compound import precompute_properties

DATA_PATH = Path(__file__).parent
PAROUTES_PATH = DATA_PATH / "paroutes"
PRECOMPUTE_PATH = DATA_PATH / "precompute"


if __name__ == "__main__":
with open(PAROUTES_PATH / "n1-stock.txt", "r") as f:
with open(PAROUTES_PATH / "n1-stock.txt") as f:
n1_stock = [line.strip() for line in f.readlines()]
PRECOMPUTE_PATH.mkdir(parents=True, exist_ok=True)
precompute_properties(n1_stock, PRECOMPUTE_PATH / "n1_stock_properties.json")
# precompute_properties(n1_stock, PRECOMPUTE_PATH / "n1_stock_properties_fp1024.json", fpSize=1024)

with open(PAROUTES_PATH / "n5-stock.txt", "r") as f:
with open(PAROUTES_PATH / "n5-stock.txt") as f:
n5_stock = [line.strip() for line in f.readlines()]
PRECOMPUTE_PATH.mkdir(parents=True, exist_ok=True)
precompute_properties(n5_stock, PRECOMPUTE_PATH / "n5_stock_properties.json")
Expand Down
1 change: 1 addition & 0 deletions docs/CNAME
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fragment.batistalab.com
4 changes: 2 additions & 2 deletions docs/FragmentRetro/fragmenter-base.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
You will only have to define the abstract methods to find fragmentation bonds and to break these bonds. Other methods like building fragment graph (`networkx` graph), visualization, and how to get SMILES string given a combination of fragments are provided in this base class.

```python
from FragmentRetro.fragmenter_base import Fragmenter
from fragmentretro.fragmenter_base import Fragmenter

# suppose you have a new way to find and break bonds: `FindTestBonds` and `BreakTestBonds`

Expand All @@ -28,7 +28,7 @@ class TestFragmenter(Fragmenter):

## Source Code

::: FragmentRetro.fragmenter_base
::: fragmentretro.fragmenter_base
handler: python
options:
show_root_heading: true
Expand Down
4 changes: 2 additions & 2 deletions docs/FragmentRetro/fragmenter.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Once you have the fragmentation algorithms such as BRICS, you can get derived cl
## Example Use

```python
from FragmentRetro.fragmenter import BRICSFragmenter
from fragmentretro.fragmenter import BRICSFragmenter

smiles = "COc1ccc(-n2nccn2)c(C(=O)N2CCC[C@@]2(C)c2nc3c(C)c(Cl)ccc3[nH]2)c1"
fragmenter = BRICSFragmenter(smiles)
Expand All @@ -16,7 +16,7 @@ fragmenter.visualize(figsize=(20, 10), verbose=False, with_indices=True)

## Source Code

::: FragmentRetro.fragmenter
::: fragmentretro.fragmenter
handler: python
options:
show_root_heading: true
Expand Down
8 changes: 4 additions & 4 deletions docs/FragmentRetro/retrosynthesis.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ The `Retrosynthesis` class identifies all possible retrosynthesis solutions for
## Example Use

```python
from FragmentRetro.fragmenter import BRICSFragmenter
from FragmentRetro.retrosynthesis import Retrosynthesis
from FragmentRetro.solutions import RetrosynthesisSolution
from fragmentretro.fragmenter import BRICSFragmenter
from fragmentretro.retrosynthesis import Retrosynthesis
from fragmentretro.solutions import RetrosynthesisSolution

# Define your building block set (SMILES strings)
original_BBs = set(['Brc1cc(OC)ccc1-n1nccn1',
Expand Down Expand Up @@ -44,7 +44,7 @@ retro_solution.visualize_solutions(retro_solution.solutions, molsPerRow=4)[-1]

## Source Code

::: FragmentRetro.retrosynthesis
::: fragmentretro.retrosynthesis
handler: python
options:
show_root_heading: true
Expand Down
8 changes: 4 additions & 4 deletions docs/FragmentRetro/solutions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ The `RetrosynthesisSolution` class is designed to find and visualize all possibl
## Example Use

```python
from FragmentRetro.retrosynthesis import Retrosynthesis
from FragmentRetro.fragmenter import BRICSFragmenter
from FragmentRetro.solutions import RetrosynthesisSolution
from fragmentretro.retrosynthesis import Retrosynthesis
from fragmentretro.fragmenter import BRICSFragmenter
from fragmentretro.solutions import RetrosynthesisSolution

# Example SMILES
smiles = "COc1ccc(-n2nccn2)c(C(=O)N2CCC[C@@]2(C)c2nc3c(C)c(Cl)ccc3[nH]2)c1"
Expand All @@ -32,7 +32,7 @@ for i, img in enumerate(images):

## Source Code

::: FragmentRetro.solutions
::: fragmentretro.solutions
handler: python
options:
show_root_heading: true
Expand Down
4 changes: 2 additions & 2 deletions docs/FragmentRetro/substructure-matcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Check if a fragment (which can contain dummy or any atoms represented by "*") is
## Example Use

```python
from FragmentRetro.substructure_matcher import SubstructureMatcher
from fragmentretro.substructure_matcher import SubstructureMatcher

fragment_smiles = "[4*]CCN[5*]",
molecule_smiles = "CCCNC"
Expand All @@ -16,7 +16,7 @@ print(SubstructureMatcher.is_strict_substructure(fragment_smiles, molecule_smile

## Source Code

::: FragmentRetro.substructure_matcher
::: fragmentretro.substructure_matcher
handler: python
options:
show_root_heading: true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
# Type Definitions

[type_definitions.py](/src/FragmentRetro/type_definitions.py) is a good place to store your type definitions to make code clean and pass mypy tests.
`fragmentretro.typing` is a good place to store your type definitions to make code clean and pass mypy tests.

## Example Usage

```python
from fragmentretro.typing import BondType

def _find_fragmentation_bonds(mol: Mol) -> list[BondType]:
return list(FindBRICSBonds(mol))
```

where `BondType` is defined in [type_definitions.py](/src/FragmentRetro/utils/type_definitions.py) as:
where `BondType` is defined in `fragmentretro.typing` as:

```python
BondType: TypeAlias = tuple[tuple[int, int], tuple[str, str]]
type BondType = tuple[tuple[int, int], tuple[str, str]]
```

## Source Code

::: FragmentRetro.utils.type_definitions
::: fragmentretro.typing
handler: python
options:
show_root_heading: true
Expand Down
8 changes: 4 additions & 4 deletions docs/FragmentRetro/utils/filter-compound.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The `precompute_properties` function computes properties for a large set of comp

```python
from pathlib import Path
from FragmentRetro.utils.filter_compound import precompute_properties
from fragmentretro.utils.filter_compound import precompute_properties

DATA_PATH = Path(__file__).parent.parent / "data"
PAROUTES_PATH = DATA_PATH / "paroutes"
Expand All @@ -27,8 +27,8 @@ precompute_properties(n1_stock_subset, MOL_PROPERTIES_PATH, fpSize=2048)
The `CompoundFilter` class is used to initialize a filter, and the `get_filtered_BBs` method retrieves the screened building blocks. The following example demonstrates filtering using a specific fragment SMILES string.

```python
from FragmentRetro.utils.filter_compound import CompoundFilter
from FragmentRetro.utils.helpers import replace_dummy_atoms_regex
from fragmentretro.utils.filter_compound import CompoundFilter
from fragmentretro.utils.helpers import replace_dummy_atoms_regex

fragment_smiles_list = ["[5*]N1CCC[C@@]1([13*])C", "[4*]CCN[5*]", "[4*]C[8*]", "[*]C[*]", "[3*]O[3*]"]
fragment_smiles = fragment_smiles_list[1]
Expand All @@ -39,7 +39,7 @@ filtered_indices, filtered_BBs = compound_filter.get_filtered_BBs(fragment_smile

## Source Code

::: FragmentRetro.utils.filter_compound
::: fragmentretro.utils.filter_compound
handler: python
options:
show_root_heading: true
Expand Down
8 changes: 4 additions & 4 deletions docs/FragmentRetro/utils/helper.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This module provides helper functions for working with SMILES strings.
`canonicalize_smiles` is for canonicalizing SMILES strings.

```python
from FragmentRetro.utils.helpers import canonicalize_smiles
from fragmentretro.utils.helpers import canonicalize_smiles

smiles = 'C[C@@H](O)C(=O)O'
canonical_smiles = canonicalize_smiles(smiles)
Expand All @@ -19,7 +19,7 @@ print(f"Canonical SMILES: {canonical_smiles}")
`replace_dummy_atoms_regex` is for replacing dummy atoms with hydrogen atoms for pattern fingerprint screening. See how it's used in the `filter_compounds` function in the `CompoundFilter` class.

```python
from FragmentRetro.utils.helpers import replace_dummy_atoms_regex
from fragmentretro.utils.helpers import replace_dummy_atoms_regex

smiles_with_dummy = '[5*]N1CCC[C@@]1([13*])C'
smiles_without_dummy = replace_dummy_atoms_regex(smiles_with_dummy)
Expand All @@ -30,7 +30,7 @@ print(f"SMILES without dummy atoms: {smiles_without_dummy}")
`remove_indices_before_dummy` is for removing indices before dummy atoms. This is to record processed fragment SMILES strings in the most general format. See how it's used in the `Retrosythesis` class as well.

```python
from FragmentRetro.utils.helpers import remove_indices_before_dummy
from fragmentretro.utils.helpers import remove_indices_before_dummy

smiles_with_indices = '[5*]N1CCC[C@@]1([13*])C'
smiles_without_indices = remove_indices_before_dummy(smiles_with_indices)
Expand All @@ -40,7 +40,7 @@ print(f"SMILES without indices: {smiles_without_indices}")

## Source Code

::: FragmentRetro.utils.helpers
::: fragmentretro.utils.helpers
handler: python
options:
show_root_heading: true
Expand Down
Loading