Skip to content

Commit 77c05cd

Browse files
committed
MGR: migrating code
0 parents  commit 77c05cd

36 files changed

Lines changed: 10824 additions & 0 deletions

.github/workflows/tests.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: testing & quality
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
qa:
11+
name: linting and tests
12+
runs-on: ${{ matrix.os }}
13+
strategy:
14+
matrix:
15+
os: [ ubuntu-latest ]
16+
python-version: [ "3.10" ]
17+
18+
steps:
19+
- name: check out code
20+
uses: actions/checkout@v4
21+
22+
- name: set up Python ${{ matrix.python-version }}
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: ${{ matrix.python-version }}
26+
cache: "pip"
27+
28+
- name: install package with dev dependencies
29+
run: |
30+
python -m pip install --upgrade pip
31+
pip install -e .[dev]
32+
33+
- name: ruff lint
34+
run: ruff check src
35+
36+
- name: ruff format check
37+
run: ruff format --check src
38+
39+
- name: run tests
40+
run: pytest -q

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
.pytest_cache/
3+
.ruff_cache/
4+
.vscode/
5+
.idea/
6+
scratch/
7+
__pycache__/
8+
dist/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 David Meijer
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<p align="center">
2+
<img
3+
src="https://raw.githubusercontent.com/moltools/RetroMol/main/logo.png"
4+
height="150"
5+
alt="RetroMol logo"
6+
>
7+
</p>
8+
9+
<h1 align="center">
10+
RetroMol
11+
</h1>
12+
13+
<p align="center">
14+
<a href="https://github.com/MolTools/RetroMol/actions/workflows/tests.yml">
15+
<img alt="testing & quality" src="https://github.com/MolTools/RetroMol/actions/workflows/tests.yml/badge.svg" /></a>
16+
<a href="https://pypi.org/project/retromol">
17+
<img alt="PyPI" src="https://img.shields.io/pypi/v/retromol" /></a>
18+
<a href="https://pypi.org/project/retromol">
19+
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/retromol" /></a>
20+
<!-- <a href="https://doi.org/10.5281/zenodo.11216453">
21+
<img src="https://zenodo.org/badge/DOI/10.5281/zenodo.11216453.svg" alt="DOI" /></a> -->
22+
</p>
23+
24+
RetroMol is retrosynthetic parsing and fingerprinting tool for modular natural products.
25+
26+
RetroMol is designed to facilitate clustering modular natural products based on biosynthetic similarity, and to enable cross-modal retrieval between modular natural products and their coding biosynthetic gene clusters.
27+
28+
RetroMol powers the RetroMol webapp, available at [here](https://retromol.bioinformatics.nl/). The webapp can be used to query NPAtlas, MIBiG, and antiSMASH database entries for modular natural products based on biosynthetic similarity.
29+
30+
## Installation
31+
32+
The most recent code and data can be installed directly from GitHub with:
33+
34+
```shell
35+
pip install git+https://github.com/MolTools/RetroMol.git
36+
```
37+
38+
The latest stable release can be installed from PyPI with:
39+
40+
```shell
41+
pip install retromol
42+
```
43+
44+
RetroMol has been developed for Linux and MacOS.
45+
46+
## Getting started
47+
48+
The retromol command line tool is automatically installed alongside installation of the package. The command line tool can be used from the shell with the `--help` flag to show all subcommands:
49+
50+
```shell
51+
python3 -m retromol --help
52+
```
53+
54+
The RetroMol CLI has two modes, single and batch:
55+
* `single`: process a single compound at a time.
56+
* `batch`: process multiple compounds in a single command.
57+
58+
In either case a the output folder will contain a log file together with the results in either JSON or JSONL format. JSONL is standard output mode for batch mode to allow for easy parsing of large result sets. Batch mode also supports parallel processing.
59+
60+
Any column, field, or property in the input file, either CSV, TSV, SDF, or JSON, is preserved as props in the output JSON or JSONL.
61+
62+
Stereochemistry parsing is supported by supplying the `--matchstereochem` flag. This flag annotates the identifier of every identified monomer with R/S and E/Z annotation where applicable.
63+
64+
Result JSONs or lines from a JSONL file can be loaded into Python using RetroMol's `Result` class for further downstream analyses:
65+
66+
```python
67+
from retromol.io import Result
68+
69+
result = Result.from_serialized(<json_dict>)
70+
71+
# e.g., calculate coverage
72+
coverage = result.best_total_coverage()
73+
```
74+
75+
Check out the [examples](https://github.com/moltools/RetroMol/tree/main/examples) folder for example scripts demonstrating how to use RetroMol as a library:
76+
* [Read out and align linear monomer readouts](https://github.com/moltools/RetroMol/tree/main/examples/align_compounds.py)
77+
* [Calculate and cluster monomer fingerprints](https://github.com/moltools/RetroMol/tree/main/examples/cluster_compounds.py)
78+
79+
### Using custom rules
80+
81+
RetroMol comes with a default set of retrosynthetic rules for modular natural products.
82+
83+
You can also provide your own custom rules and configurations. See [the custom rules documentation](docs/customize_rules.md) for details on the YAML formats supported.
84+
85+
## Attribution
86+
87+
### License
88+
89+
The code in this package is licensed under the MIT License.
90+
91+
## For Developers
92+
93+
The final section of the README is for if you want to get involved by making a code contribution.
94+
95+
### Development installation
96+
97+
First fork the repository on GitHub, then clone your fork locally and install the package
98+
in "editable" mode with the development dependencies:
99+
100+
```bash
101+
git clone git+https://github.com/MolTools/RetroMol.git
102+
cd RetroMol
103+
pip install -e .[dev]
104+
pip install hatch # if you don't have hatch installed yet; needed for building the package
105+
hatch env create dev # create the development environment
106+
```
107+
108+
You can now make code changes locally and have them immediately available for testing.
109+
110+
After testing your changes, you can commit and push them to your fork, and then open a pull request
111+
on the main repository explaining your changes.
112+
113+
### Testing
114+
115+
After cloning the repository, the unit tests in the `tests/` folder can be run
116+
reproducibly with:
117+
118+
```shell
119+
pytest tests
120+
```
121+
122+
Additionally, these tests are automatically re-run with each push and pull request on `main` in a
123+
[GitHub Action](https://github.com/MolTools/RetroMol/actions?query=workflow%3ATests).

docs/customize_rules.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Custom rule file formats
2+
3+
Below are the specifications for custom reaction and matching rule files, as well as wave configuration file, used by RetroMol. These files are written in YAML format. Reaction and matching rule files are dumps of lists of rules, while the configuration file structures them into a workflow.
4+
5+
## Wave configuration
6+
7+
### Creating a wave_config.yml
8+
9+
RetroMol processes molecules in waves: ordered stages that (1) apply selected reaction rules to split or transform the current “frontier” structures, and then (2) match the resulting fragments to motif classes. The behavior of each stage is controlled by a YAML file, wave_config.yml.
10+
11+
If you don’t pass a config, RetroMol uses `retromol/data/default_wave_config.yml` (the same structure as the example below). You can point the CLI at a custom file with --wave-config (see `src/retromol/cli.py`) or pass it via the API (api.run_retromol(..., wave_configs=...)).
12+
13+
### File structure
14+
15+
A wave config is a YAML list, where each item is one wave. Waves run top-to-bottom. Each wave supports the keys below.
16+
17+
Supported keys (per wave):
18+
- `wave_name` (`string`, required): Human-readable label stored on nodes produced in that wave (see `resolve_mol` in `src/retromol/apply.py`).
19+
20+
- `reaction_groups` (`list[string]`, required): Names of reaction-rule groups to apply in this wave. These must correspond to groups defined in your reaction rules YAML (loaded via `rules.load_rules_from_files(...)`). Examples: preprocessing, linearization, NRP disassembly, PK disassembly, etc.
21+
22+
- `matching_groups` (`list[string]`, optional): If provided, only matching rules whose groups intersect this list are considered when assigning node identities in this wave (see filtering in `apply.resolve_mol`). If omitted, all matching rules are eligible. Names correspond to groups defined in your matching rules YAML (loaded via `rules.load_rules_from_files(...)`). Examples: amino acid, polyketide building block, glycosylation, etc.
23+
24+
- `only_leaf_nodes` (`bool`, optional; default: true): After applying the reaction rules in this wave, RetroMol selects which reaction-graph nodes to turn into motif graph nodes:
25+
* `true`: use leaf nodes created in the previous wave only (preferred; avoids duplicating intermediate steps).
26+
* `false`: use all nodes created in the previous wave.
27+
28+
Note: If a “leafs only” wave produces no leaves (e.g., an uncontested rule returns itself), RetroMol falls back to all nodes for that wave.
29+
30+
- `parse_identified_nodes` (`bool`, optional; default: `false`):
31+
Controls eligibility of nodes for re-parsing at the current deepest nesting level (see `find_eligible_nodes` in `src/retromol/api.py`):
32+
* `false`: skip nodes that already have an identity.
33+
* `true`: allow re-parsing nodes even if they already carry an identity. This is useful when one wave (e.g., linearization) changes structures in a way that reveals new tailoring motifs for the next wave.
34+
35+
36+
### Execution model (what happens under the hood)
37+
38+
- Wave 1 operates on the input molecule to produce the initial motif graph (see `api.run_retromol` > first call to `apply.resolve_mol`).
39+
40+
- Subsequent waves operate on the current frontier: nodes at the deepest nesting level whose graph is `None` (and, unless `parse_identified_nodes: true`, also have no identity).
41+
42+
- In each wave:
43+
* Reaction rules are chosen from `reaction_groups`.
44+
* The reaction graph is computed; RetroMol selects leaf or all nodes according to `only_leaf_nodes`.
45+
* Selected nodes are matched to motifs using the (optionally filtered) `matching_groups`.
46+
* Each selected node becomes a node in the motif graph and is annotated with `wave_name`, `identity`, `props`, `smiles`, `smiles_no_tags`, and `tags`.
47+
* For nodes that should be further expanded in later waves, a nested graph is attached.
48+
49+
Note: Stereochemistry matching is controlled separately (CLI `--matchstereochem` or API `match_stereochemistry`), not in the wave config.
50+
51+
### Minimal example
52+
53+
A minimal file needs just a name and at least one reaction group:
54+
55+
```yaml
56+
- wave_name: preprocessing
57+
reaction_groups:
58+
- preprocessing
59+
```
60+
61+
This will apply the preprocessing reaction rules and match against all matching rules.
62+
63+
## Reaction rules
64+
65+
The default reaction rules can be found at `retromol/data/default_reaction_rules.yml`.
66+
67+
A minimal reaction rules file looks like:
68+
69+
```yaml
70+
# reactions.yml
71+
- rid: reverse O-methylation
72+
smarts: "[O;D2:1][CH3:2]>>[O:1].[C:2]"
73+
groups: [preprocessing]
74+
props: {} # optional
75+
76+
- rid: break ester bond (intermolecular)
77+
smarts: "[C:1][C;!R:2](=[O:3])[O;!R:4][C:5]>>[C:1][C:2](=[O:3])[OH].[OH:4][C:5]"
78+
groups: [linearization]
79+
```
80+
81+
```yaml
82+
- rid: <string> # unique human-readable identifier
83+
smarts: <reaction SMARTS> # RDKit reaction SMARTS (LHS>>RHS)
84+
groups: [<string>, ...] # arbitrary grouping labels
85+
props: # optional metadata and global conditions
86+
conditions:
87+
# pre-conditions (whole molecule)
88+
reactant:
89+
requires_any: ["<SMARTS>", ...]
90+
requires_all: ["<SMARTS>", ...]
91+
forbids_any: ["<SMARTS>", ...]
92+
min_counts: {"<SMARTS>": <int>, ...}
93+
max_counts: {"<SMARTS>": <int>, ...}
94+
ring_count: {min: <int>, max: <int>}
95+
atom_count: {min: <int>, max: <int>}
96+
total_charge: {min: <int>, max: <int>}
97+
custom_props: {has_metal: <bool>, is_macrocycle: <bool>}
98+
# post-conditions (applied to each product)
99+
product:
100+
requires_any: ["<SMARTS>", ...]
101+
requires_all: ["<SMARTS>", ...]
102+
forbids_any: ["<SMARTS>", ...]
103+
min_counts: {"<SMARTS>": <int>, ...}
104+
max_counts: {"<SMARTS>": <int>, ...}
105+
ring_count: {min: <int>, max: <int>}
106+
atom_count: {min: <int>, max: <int>}
107+
total_charge: {min: <int>, max: <int>}
108+
custom_props: {has_metal: <bool>, is_macrocycle: <bool>}
109+
```
110+
111+
What each field means:
112+
* `rid`: A stable, unique identifier (used for logging/debugging).
113+
* `smarts`: Reaction SMARTS using RDKit’s format, including mapped atoms on both sides. Element identities and multiplicities for each map number must match (the loader enforces this).
114+
* `groups`: Tags used to select rule subsets (preprocessing, linearization, NRP disassembly, etc.).
115+
* `props.conditions.reactant`: “Global pre-filter” — the reaction only runs if the whole reactant satisfies these.
116+
* `props.conditions.product`: “Global post-filter” — each product must satisfy these (result dropped otherwise).
117+
118+
Tip: Use local constraints inside the SMARTS for atom-level logic (e.g., “:1 must not be acyl”), and global conditions for whole-molecule predicates (e.g., “forbid nitro anywhere”).
119+
120+
## Matching rules
121+
122+
The default matching rules can be found at `retromol/data/default_matching_rules.yml`.
123+
124+
A minimal matching rules file looks like:
125+
126+
```yaml
127+
- rid: valine
128+
mol: "CC(C)C(N)C(=O)O"
129+
groups: [amino_acids]
130+
props: {}
131+
```
132+
133+
Matching uses exact topology equality (same atom/bond counts) plus substructure match.
134+
135+
If you need stereochemistry matching, it’s supported by adding ste_mols in code (optional advanced usage).
136+
137+
```yaml
138+
- rid: <string>
139+
mol: "<SMILES>" # exact structure to match (full match, not subgraph)
140+
groups: [<string>, ...]
141+
props: {}
142+
```

0 commit comments

Comments
 (0)