helmkit 0.2.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. helmkit-0.3.1/.github/workflows/main.yml +22 -0
  2. {helmkit-0.2.0 → helmkit-0.3.1}/PKG-INFO +74 -4
  3. {helmkit-0.2.0 → helmkit-0.3.1}/README.md +73 -3
  4. {helmkit-0.2.0 → helmkit-0.3.1}/benchmarks/README.md +10 -7
  5. {helmkit-0.2.0 → helmkit-0.3.1}/benchmarks/helmkit-benchmark.py +2 -2
  6. helmkit-0.3.1/benchmarks/helmkit-parallel-benchmark.py +29 -0
  7. {helmkit-0.2.0 → helmkit-0.3.1}/benchmarks/helmkit-with-reload-benchmark.py +2 -2
  8. {helmkit-0.2.0 → helmkit-0.3.1}/benchmarks/pypept-benchmark.py +2 -2
  9. {helmkit-0.2.0 → helmkit-0.3.1}/pyproject.toml +2 -1
  10. helmkit-0.3.1/src/helmkit/__init__.py +11 -0
  11. {helmkit-0.2.0 → helmkit-0.3.1}/src/helmkit/molecule.py +21 -5
  12. helmkit-0.3.1/tests/backup +66 -0
  13. helmkit-0.3.1/tests/test_cycpeptmpdb.py +29 -0
  14. helmkit-0.3.1/tests/test_parallel_load.py +28 -0
  15. helmkit-0.2.0/tests/random_peptides.py → helmkit-0.3.1/tests/test_random_peptides.py +3 -4
  16. {helmkit-0.2.0 → helmkit-0.3.1}/uv.lock +55 -1
  17. helmkit-0.2.0/src/helmkit/__init__.py +0 -5
  18. helmkit-0.2.0/tests/cycpeptmpdb.py +0 -43
  19. {helmkit-0.2.0 → helmkit-0.3.1}/.gitignore +0 -0
  20. {helmkit-0.2.0 → helmkit-0.3.1}/.python-version +0 -0
  21. {helmkit-0.2.0 → helmkit-0.3.1}/LICENSE +0 -0
  22. {helmkit-0.2.0 → helmkit-0.3.1}/benchmarks/data/monomers.sdf +0 -0
  23. {helmkit-0.2.0 → helmkit-0.3.1}/benchmarks/data/peptides.csv +0 -0
  24. {helmkit-0.2.0 → helmkit-0.3.1}/src/helmkit/data/monomers.sdf +0 -0
  25. {helmkit-0.2.0 → helmkit-0.3.1}/src/helmkit/py.typed +0 -0
  26. {helmkit-0.2.0 → helmkit-0.3.1}/tests/data/generate_sdf.py +0 -0
  27. {helmkit-0.2.0 → helmkit-0.3.1}/tests/data/monomers.csv +0 -0
  28. {helmkit-0.2.0 → helmkit-0.3.1}/tests/data/monomers.sdf +0 -0
  29. {helmkit-0.2.0 → helmkit-0.3.1}/tests/data/peptides.csv +0 -0
@@ -0,0 +1,22 @@
1
+ name: Run tests
2
+
3
+ on:
4
+ push:
5
+ paths:
6
+ - '**/*.py'
7
+ pull_request:
8
+ paths:
9
+ - '**/*.py'
10
+ workflow_dispatch:
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - name: Set up uv
18
+ uses: astral-sh/setup-uv@v6
19
+ - name: Install dependencies
20
+ run: uv sync -U
21
+ - name: Test with pytest
22
+ run: uv run pytest
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: helmkit
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: Parse HELM strings into RDKit molecules
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -11,19 +11,53 @@ Description-Content-Type: text/markdown
11
11
 
12
12
  A Python library for converting HELM (Hierarchical Editing Language for Macromolecules) notation to RDKit molecules.
13
13
 
14
+ ## Table of Contents
15
+
16
+ - [Basic Usage](#basic-usage)
17
+ - [Installation](#installation)
18
+ - [Quick Example](#quick-example)
19
+ - [Understanding HELM Notation](#understanding-helm-notation)
20
+ - [Using Custom Monomer Data](#using-custom-monomer-data)
21
+ - [SDF File Structure Requirements](#sdf-file-structure-requirements)
22
+ - [Parallel Processing of Peptides](#parallel-processing-of-peptides)
23
+ - [Development Setup](#development-setup)
24
+ - [Running Tests](#running-tests)
25
+
14
26
  ## Basic Usage
15
27
 
16
28
  ```python
17
29
  from helmkit import Molecule
18
30
 
19
31
  # Create a molecule from a HELM string
20
- helm_string = "PEPTIDE1{A.R.G}$$$"
32
+ helm_string = "PEPTIDE1{A.R.G}$$$$"
21
33
  molecule = Molecule(helm_string)
22
34
 
23
35
  # Access the RDKit molecule object
24
36
  rdkit_mol = molecule.mol
25
37
  ```
26
38
 
39
+ ## Installation
40
+
41
+ To install `helmkit`, you can use either [`uv`](https://github.com/astral-sh/uv) or `pip`.
42
+
43
+ ### With [`uv`](https://github.com/astral-sh/uv)
44
+
45
+ ```bash
46
+ uv pip install helmkit
47
+ ```
48
+
49
+ or if you have added it as a dependency to your `pyproject.toml`:
50
+
51
+ ```bash
52
+ uv add helmkit
53
+ ```
54
+
55
+ ### Without `uv`
56
+
57
+ ```bash
58
+ pip install helmkit
59
+ ```
60
+
27
61
  ## Quick Example
28
62
 
29
63
  ```python
@@ -31,7 +65,7 @@ from helmkit import Molecule
31
65
  from rdkit.Chem import AllChem, Draw
32
66
 
33
67
  # Create a simple tripeptide (Ala-Arg-Gly)
34
- molecule = Molecule("PEPTIDE1{A.R.G}$$$")
68
+ molecule = Molecule("PEPTIDE1{A.R.G}$$$$")
35
69
 
36
70
  # Generate 2D coordinates for visualization
37
71
  AllChem.Compute2DCoords(molecule.mol)
@@ -67,7 +101,7 @@ custom_sdf_path = "/path/to/your/custom_monomers.sdf"
67
101
  custom_monomers = load_monomer_library(custom_sdf_path)
68
102
 
69
103
  # Create molecule with custom monomer data
70
- molecule = Molecule("PEPTIDE1{A.R.G}$$$", monomer_df=custom_monomers)
104
+ molecule = Molecule("PEPTIDE1{A.R.G}$$$$", monomer_df=custom_monomers)
71
105
  ```
72
106
 
73
107
  ## SDF File Structure Requirements
@@ -110,3 +144,39 @@ Ala
110
144
 
111
145
  $$$$
112
146
  ```
147
+
148
+ ## Parallel Processing of Peptides
149
+
150
+ For workflows involving a large number of peptides, `helmkit` provides a function to process them in parallel, significantly improving performance.
151
+
152
+ ```python
153
+ from helmkit import load_monomer_library
154
+ from helmkit import load_peptides_in_parallel
155
+
156
+ # Load your custom monomer data (optional)
157
+ custom_sdf_path = "/path/to/your/custom_monomers.sdf"
158
+ monomer_db = load_monomer_library(custom_sdf_path)
159
+
160
+ # A list of HELM strings
161
+ helm_strings = ["PEPTIDE1{A.R.G}$$$$", "PEPTIDE1{S.G.T}$$$$"]
162
+
163
+ # Process peptides in parallel
164
+ molecules = load_peptides_in_parallel(helm_strings, monomer_db)
165
+ ```
166
+
167
+ ## Development Setup
168
+
169
+ To set up a development environment, first clone the repository.
170
+ Then, from the root of the repository, use `uv` to sync the environment:
171
+
172
+ ```bash
173
+ uv sync -U
174
+ ```
175
+
176
+ ## Running Tests
177
+
178
+ To run the test suite, execute `pytest` from the root of the repository:
179
+
180
+ ```bash
181
+ pytest
182
+ ```
@@ -2,19 +2,53 @@
2
2
 
3
3
  A Python library for converting HELM (Hierarchical Editing Language for Macromolecules) notation to RDKit molecules.
4
4
 
5
+ ## Table of Contents
6
+
7
+ - [Basic Usage](#basic-usage)
8
+ - [Installation](#installation)
9
+ - [Quick Example](#quick-example)
10
+ - [Understanding HELM Notation](#understanding-helm-notation)
11
+ - [Using Custom Monomer Data](#using-custom-monomer-data)
12
+ - [SDF File Structure Requirements](#sdf-file-structure-requirements)
13
+ - [Parallel Processing of Peptides](#parallel-processing-of-peptides)
14
+ - [Development Setup](#development-setup)
15
+ - [Running Tests](#running-tests)
16
+
5
17
  ## Basic Usage
6
18
 
7
19
  ```python
8
20
  from helmkit import Molecule
9
21
 
10
22
  # Create a molecule from a HELM string
11
- helm_string = "PEPTIDE1{A.R.G}$$$"
23
+ helm_string = "PEPTIDE1{A.R.G}$$$$"
12
24
  molecule = Molecule(helm_string)
13
25
 
14
26
  # Access the RDKit molecule object
15
27
  rdkit_mol = molecule.mol
16
28
  ```
17
29
 
30
+ ## Installation
31
+
32
+ To install `helmkit`, you can use either [`uv`](https://github.com/astral-sh/uv) or `pip`.
33
+
34
+ ### With [`uv`](https://github.com/astral-sh/uv)
35
+
36
+ ```bash
37
+ uv pip install helmkit
38
+ ```
39
+
40
+ or if you have added it as a dependency to your `pyproject.toml`:
41
+
42
+ ```bash
43
+ uv add helmkit
44
+ ```
45
+
46
+ ### Without `uv`
47
+
48
+ ```bash
49
+ pip install helmkit
50
+ ```
51
+
18
52
  ## Quick Example
19
53
 
20
54
  ```python
@@ -22,7 +56,7 @@ from helmkit import Molecule
22
56
  from rdkit.Chem import AllChem, Draw
23
57
 
24
58
  # Create a simple tripeptide (Ala-Arg-Gly)
25
- molecule = Molecule("PEPTIDE1{A.R.G}$$$")
59
+ molecule = Molecule("PEPTIDE1{A.R.G}$$$$")
26
60
 
27
61
  # Generate 2D coordinates for visualization
28
62
  AllChem.Compute2DCoords(molecule.mol)
@@ -58,7 +92,7 @@ custom_sdf_path = "/path/to/your/custom_monomers.sdf"
58
92
  custom_monomers = load_monomer_library(custom_sdf_path)
59
93
 
60
94
  # Create molecule with custom monomer data
61
- molecule = Molecule("PEPTIDE1{A.R.G}$$$", monomer_df=custom_monomers)
95
+ molecule = Molecule("PEPTIDE1{A.R.G}$$$$", monomer_df=custom_monomers)
62
96
  ```
63
97
 
64
98
  ## SDF File Structure Requirements
@@ -101,3 +135,39 @@ Ala
101
135
 
102
136
  $$$$
103
137
  ```
138
+
139
+ ## Parallel Processing of Peptides
140
+
141
+ For workflows involving a large number of peptides, `helmkit` provides a function to process them in parallel, significantly improving performance.
142
+
143
+ ```python
144
+ from helmkit import load_monomer_library
145
+ from helmkit import load_peptides_in_parallel
146
+
147
+ # Load your custom monomer data (optional)
148
+ custom_sdf_path = "/path/to/your/custom_monomers.sdf"
149
+ monomer_db = load_monomer_library(custom_sdf_path)
150
+
151
+ # A list of HELM strings
152
+ helm_strings = ["PEPTIDE1{A.R.G}$$$$", "PEPTIDE1{S.G.T}$$$$"]
153
+
154
+ # Process peptides in parallel
155
+ molecules = load_peptides_in_parallel(helm_strings, monomer_db)
156
+ ```
157
+
158
+ ## Development Setup
159
+
160
+ To set up a development environment, first clone the repository.
161
+ Then, from the root of the repository, use `uv` to sync the environment:
162
+
163
+ ```bash
164
+ uv sync -U
165
+ ```
166
+
167
+ ## Running Tests
168
+
169
+ To run the test suite, execute `pytest` from the root of the repository:
170
+
171
+ ```bash
172
+ pytest
173
+ ```
@@ -33,15 +33,18 @@ that `pyPept` cannot handle were excluded (monomers with names containing whites
33
33
  hyphens or parenthesis), resulting in a total of 4694 peptides. We measured the total
34
34
  and average parsing time:
35
35
 
36
- | Tool | Total Time (s) | Avg Time per Peptide (s) |
37
- |-------------------------------------|----------------|--------------------------|
38
- | `pyPept` | 676.65 | 0.144 |
39
- | `helmkit` | 3.60 | 0.00077 |
40
- | `helmkit` (DB reload every peptide) | 261.81 | 0.056 |
36
+ | Tool | Total Time (s) | Avg Time per Peptide (s) | Peptides per Second |
37
+ |-------------------------------------|---------------:|-------------------------:|--------------------:|
38
+ | `pyPept` | 676.65 | 0.14400 | 6.94 |
39
+ | `helmkit` (DB reload every peptide) | 261.81 | 0.05600 | 17.93 |
40
+ | `helmkit` | 3.60 | 0.00077 | 1303.89 |
41
+ | `helmkit` (parallel loading) | 1.42 | 0.00030 | 3305.63 |
41
42
 
42
43
  `helmkit` outperforms `pyPept` by approximately 188× when loading the monomer library
43
44
  once per session. When forced to reload the library for every peptide (needs disabling
44
- memoization in the source code), `helmkit` is still about 3× faster.
45
+ memoization in the source code), `helmkit` is still about 3× faster. If we use
46
+ parallelized loading in `helmkit`, we achieve an additional 2.5× speedup, resulting in a
47
+ total speedup of approximately 477× over `pyPept`.
45
48
 
46
49
  ## Environment
47
50
 
@@ -52,4 +55,4 @@ versions:
52
55
  - `polars 1.31.0` (CSV parsing and dataframe processing)
53
56
  - `rdkit 2025.3.3` (target output format and structure processing)
54
57
  - `pypept 1.0.0` (commit `ade9f5840691ad1f8fa22d13939a665c25175d5a`)
55
- - `helmkit 0.1.0` (local development version)
58
+ - `helmkit 0.3.0` (local development version)
@@ -14,12 +14,12 @@ def main():
14
14
  # (as these do not work with pyPept)
15
15
  regex = r"\[[^\]]*[\(\s-][^\]]*\]"
16
16
  df = df.filter(pl.col("HELM").str.contains(regex).not_())
17
+ helms = df["HELM"].to_list()
17
18
 
18
19
  monomer_db = load_monomer_library(data_dir / "monomers.sdf")
19
20
 
20
21
  start = time.perf_counter()
21
- for row in df.iter_rows(named=True):
22
- helm = row["HELM"]
22
+ for helm in helms:
23
23
  Molecule(helm, monomer_db)
24
24
  end = time.perf_counter()
25
25
  print(f"Processed {df.height} peptides in {end - start:.2f} seconds")
@@ -0,0 +1,29 @@
1
+ import time
2
+ from pathlib import Path
3
+
4
+ import polars as pl
5
+ from helmkit import load_monomer_library
6
+ from helmkit import load_peptides_in_parallel
7
+
8
+
9
+ def main():
10
+ data_dir = Path(__file__).parent / "data"
11
+ df = pl.read_csv(data_dir / "peptides.csv")
12
+
13
+ # Remove peptides with monomers containing parentheses, spaces or hyphens
14
+ # (as these do not work with pyPept)
15
+ regex = r"\[[^\]]*[\(\s-][^\]]*\]"
16
+ df = df.filter(pl.col("HELM").str.contains(regex).not_())
17
+ helms = df["HELM"].to_list()
18
+
19
+ monomer_db = load_monomer_library(data_dir / "monomers.sdf")
20
+
21
+ start = time.perf_counter()
22
+ load_peptides_in_parallel(helms, monomer_db)
23
+ end = time.perf_counter()
24
+ print(f"Processed {df.height} peptides in {end - start:.2f} seconds")
25
+ print(f"Average time per peptide: {(end - start) / df.height:.6f} seconds")
26
+
27
+
28
+ if __name__ == "__main__":
29
+ main()
@@ -14,10 +14,10 @@ def main():
14
14
  # (as these do not work with pyPept)
15
15
  regex = r"\[[^\]]*[\(\s-][^\]]*\]"
16
16
  df = df.filter(pl.col("HELM").str.contains(regex).not_())
17
+ helms = df["HELM"].to_list()
17
18
 
18
19
  start = time.perf_counter()
19
- for row in df.iter_rows(named=True):
20
- helm = row["HELM"]
20
+ for helm in helms:
21
21
  monomer_db = load_monomer_library(data_dir / "monomers.sdf")
22
22
  Molecule(helm, monomer_db)
23
23
  end = time.perf_counter()
@@ -15,13 +15,13 @@ def main():
15
15
  # (as these do not work with pyPept)
16
16
  regex = r"\[[^\]]*[\(\s-][^\]]*\]"
17
17
  df = df.filter(pl.col("HELM").str.contains(regex).not_())
18
+ helms = df["HELM"].to_list()
18
19
 
19
20
  monomer_lib_dir = str(data_dir.relative_to(Path.cwd()))
20
21
  monomer_lib = "monomers.sdf"
21
22
 
22
23
  start = time.perf_counter()
23
- for row in df.iter_rows(named=True):
24
- helm = row["HELM"]
24
+ for helm in helms:
25
25
  converter = Converter(helm=helm)
26
26
  sequence = Sequence(converter.get_biln(), monomer_lib_dir, monomer_lib)
27
27
  Molecule(sequence)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "helmkit"
3
- version = "0.2.0"
3
+ version = "0.3.1"
4
4
  description = "Parse HELM strings into RDKit molecules"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -16,6 +16,7 @@ build-backend = "hatchling.build"
16
16
  dev = [
17
17
  "polars>=1.31.0",
18
18
  "pypept",
19
+ "pytest>=8.4.1",
19
20
  "pyupgrade>=3.20.0",
20
21
  "reorder-python-imports>=3.15.0",
21
22
  "tqdm>=4.67.1",
@@ -0,0 +1,11 @@
1
+ from .molecule import load_monomer_library
2
+ from .molecule import load_peptides_in_parallel
3
+ from .molecule import Molecule
4
+ from .molecule import SequenceConstants
5
+
6
+ __all__ = [
7
+ "load_monomer_library",
8
+ "load_peptides_in_parallel",
9
+ "Molecule",
10
+ "SequenceConstants",
11
+ ]
@@ -1,4 +1,5 @@
1
1
  import copy
2
+ import multiprocessing
2
3
  import re
3
4
  import warnings
4
5
  from functools import lru_cache
@@ -68,8 +69,14 @@ def infer_attachment_points(molecule: Chem.Mol, rgroup_indices: List[int]) -> Li
68
69
 
69
70
 
70
71
  @lru_cache
71
- def load_monomer_library(library_path: str) -> Dict:
72
+ def load_monomer_library(library_path: Optional[str] = None) -> Dict:
72
73
  """Load and prepare monomer data from SDF file."""
74
+ if library_path is None:
75
+ library_path = str(
76
+ files(SequenceConstants.def_path).joinpath(
77
+ SequenceConstants.def_lib_filename
78
+ )
79
+ )
73
80
  monomers_dict = {}
74
81
  supplier = Chem.SDMolSupplier(library_path)
75
82
 
@@ -111,10 +118,7 @@ class Molecule:
111
118
  self.chain_offset = {}
112
119
 
113
120
  if monomer_df is None:
114
- default_monomer_df_filepath = files(SequenceConstants.def_path).joinpath(
115
- SequenceConstants.def_lib_filename
116
- )
117
- self.monomer_df = load_monomer_library(str(default_monomer_df_filepath))
121
+ self.monomer_df = load_monomer_library()
118
122
  else:
119
123
  self.monomer_df = monomer_df
120
124
 
@@ -456,3 +460,15 @@ class Molecule:
456
460
  """Clean up the molecule by removing dummy atoms and sanitizing."""
457
461
  self.mol = Chem.DeleteSubstructs(self.mol, Chem.MolFromSmarts("[#0]"))
458
462
  Chem.SanitizeMol(self.mol)
463
+
464
+
465
+ def _load_peptide(helm: str, monomer_df: Optional[Dict] = None) -> Molecule:
466
+ return Molecule(helm, monomer_df)
467
+
468
+
469
+ def load_peptides_in_parallel(
470
+ helms: List[str], monomer_df: Optional[Dict] = None
471
+ ) -> List[Molecule]:
472
+ args = [(helm, monomer_df) for helm in helms]
473
+ with multiprocessing.Pool() as pool:
474
+ return pool.starmap(_load_peptide, args)
@@ -0,0 +1,66 @@
1
+ import pytest
2
+ from pathlib import Path
3
+ from rdkit import Chem
4
+ from helmkit.molecule import (
5
+ Molecule,
6
+ load_monomer_library,
7
+ load_peptides_in_parallel,
8
+ )
9
+
10
+
11
+ @pytest.fixture(scope="module")
12
+ def monomer_library():
13
+ """Fixture to load the monomer library for testing."""
14
+ path = Path(__file__).parent / "data" / "monomers.sdf"
15
+ return load_monomer_library(path)
16
+
17
+
18
+ def test_load_monomer_library(monomer_library):
19
+ """Test loading the monomer library."""
20
+ assert "A" in monomer_library
21
+ assert "G" in monomer_library
22
+ assert "T" in monomer_library
23
+ assert monomer_library["A"]["m_type"] == "aa"
24
+ assert monomer_library["A"]["m_romol"].GetNumAtoms() > 0
25
+
26
+
27
+ def test_simple_peptide(monomer_library):
28
+ """Test creating a simple peptide from a HELM string."""
29
+ helm = "PEPTIDE1{A.G.T}$$$$"
30
+ mol = Molecule(helm, monomer_df=monomer_library)
31
+ assert isinstance(mol.mol, Chem.Mol)
32
+ # Number of atoms will vary based on monomer definitions, but it should be > 0
33
+ assert mol.mol.GetNumAtoms() > 0
34
+
35
+
36
+ def test_peptide_with_connection(monomer_library):
37
+ """Test a peptide with a connection."""
38
+ helm = "PEPTIDE1{A.G.G}$PEPTIDE1,PEPTIDE1,1:R3-3:R1$$$"
39
+ mol = Molecule(helm, monomer_df=monomer_library)
40
+ assert isinstance(mol.mol, Chem.Mol)
41
+ assert mol.mol.GetNumAtoms() > 0
42
+
43
+
44
+ def test_invalid_monomer(monomer_library):
45
+ """Test HELM string with an invalid monomer."""
46
+ helm = "PEPTIDE1{X.Y.Z}$$$$"
47
+ with pytest.raises(ValueError, match="Monomer X not found in monomer library"):
48
+ Molecule(helm, monomer_df=monomer_library)
49
+
50
+
51
+ def test_invalid_helm_string(monomer_library):
52
+ """Test an invalid HELM string."""
53
+ helm = "PEPTIDE1{A.G.T}"
54
+ mol = Molecule(helm, monomer_df=monomer_library)
55
+ assert len(mol.monomers) == 3
56
+
57
+
58
+ def test_load_peptides_in_parallel(monomer_library):
59
+ """Test loading peptides in parallel."""
60
+ helms = ["PEPTIDE1{A.G.T}$$$$", "PEPTIDE1{G.A}$$$$"]
61
+ molecules = load_peptides_in_parallel(helms, monomer_df=monomer_library)
62
+ assert len(molecules) == 2
63
+ assert isinstance(molecules[0].mol, Chem.Mol)
64
+ assert isinstance(molecules[1].mol, Chem.Mol)
65
+ assert molecules[0].mol.GetNumAtoms() > 0
66
+ assert molecules[1].mol.GetNumAtoms() > 0
@@ -0,0 +1,29 @@
1
+ from pathlib import Path
2
+
3
+ import polars as pl
4
+ from helmkit import load_monomer_library
5
+ from helmkit import Molecule
6
+ from rdkit import Chem
7
+ from tqdm import tqdm
8
+
9
+
10
+ def test():
11
+ data_dir = Path(__file__).parent / "data"
12
+ df = pl.read_csv(data_dir / "peptides.csv")
13
+ monomer_db = load_monomer_library(data_dir / "monomers.sdf")
14
+ for row in tqdm(df.iter_rows(named=True), total=df.height):
15
+ helm = row["HELM"]
16
+ smiles = row["SMILES"]
17
+ try:
18
+ m = Molecule(helm, monomer_db)
19
+ except:
20
+ print(row)
21
+ raise
22
+ inchi1 = Chem.MolToInchi(m.mol)
23
+ other = Chem.MolFromSmiles(smiles)
24
+ inchi2 = Chem.MolToInchi(other)
25
+ assert inchi1 == inchi2
26
+
27
+
28
+ if __name__ == "__main__":
29
+ test()
@@ -0,0 +1,28 @@
1
+ from pathlib import Path
2
+
3
+ import polars as pl
4
+ from helmkit import load_monomer_library
5
+ from helmkit import load_peptides_in_parallel
6
+ from rdkit import Chem
7
+ from tqdm import tqdm
8
+
9
+
10
+ def test():
11
+ data_dir = Path(__file__).parent / "data"
12
+ df = pl.read_csv(data_dir / "peptides.csv")
13
+ monomer_db = load_monomer_library(str(data_dir / "monomers.sdf"))
14
+ helms = df["HELM"].to_list()
15
+
16
+ molecules_parallel = load_peptides_in_parallel(helms, monomer_db)
17
+ for m, row in tqdm(
18
+ zip(molecules_parallel, df.iter_rows(named=True)), total=df.height
19
+ ):
20
+ smiles = row["SMILES"]
21
+ inchi1 = Chem.MolToInchi(m.mol)
22
+ other = Chem.MolFromSmiles(smiles)
23
+ inchi2 = Chem.MolToInchi(other)
24
+ assert inchi1 == inchi2
25
+
26
+
27
+ if __name__ == "__main__":
28
+ test()
@@ -5,7 +5,7 @@ from rdkit import Chem
5
5
  from tqdm import tqdm
6
6
 
7
7
 
8
- def main():
8
+ def test():
9
9
  random.seed(0)
10
10
  aminoacids = "ACDEFGHIKLMNPQRSTVWY"
11
11
  for _ in tqdm(range(1000)):
@@ -15,9 +15,8 @@ def main():
15
15
  molecule = Molecule(helm)
16
16
  inchi1 = Chem.MolToInchi(molecule.mol)
17
17
  inchi2 = Chem.MolToInchi(Chem.MolFromSequence(peptide))
18
- if inchi1 != inchi2:
19
- raise ValueError(f"{inchi1} != {inchi2}")
18
+ assert inchi1 == inchi2
20
19
 
21
20
 
22
21
  if __name__ == "__main__":
23
- main()
22
+ test()
@@ -113,7 +113,7 @@ wheels = [
113
113
 
114
114
  [[package]]
115
115
  name = "helmkit"
116
- version = "0.1.0"
116
+ version = "0.3.0"
117
117
  source = { editable = "." }
118
118
  dependencies = [
119
119
  { name = "rdkit" },
@@ -123,6 +123,7 @@ dependencies = [
123
123
  dev = [
124
124
  { name = "polars" },
125
125
  { name = "pypept" },
126
+ { name = "pytest" },
126
127
  { name = "pyupgrade" },
127
128
  { name = "reorder-python-imports" },
128
129
  { name = "tqdm" },
@@ -135,6 +136,7 @@ requires-dist = [{ name = "rdkit", specifier = ">=2025.3.3" }]
135
136
  dev = [
136
137
  { name = "polars", specifier = ">=1.31.0" },
137
138
  { name = "pypept", git = "https://github.com/Boehringer-Ingelheim/pyPept.git" },
139
+ { name = "pytest", specifier = ">=8.4.1" },
138
140
  { name = "pyupgrade", specifier = ">=3.20.0" },
139
141
  { name = "reorder-python-imports", specifier = ">=3.15.0" },
140
142
  { name = "tqdm", specifier = ">=4.67.1" },
@@ -177,6 +179,15 @@ wheels = [
177
179
  { url = "https://files.pythonhosted.org/packages/d7/91/c1b597004248bd7ce6c9593465308a1a5f0467c4ec4056aa51a6c017a669/igraph-0.11.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f35694100691bf8ef0c370615d87bcf1d6c0f15e356269c6357f8f78a9f1acea", size = 2926242, upload-time = "2025-06-11T09:27:18.553Z" },
178
180
  ]
179
181
 
182
+ [[package]]
183
+ name = "iniconfig"
184
+ version = "2.1.0"
185
+ source = { registry = "https://pypi.org/simple" }
186
+ sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
187
+ wheels = [
188
+ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
189
+ ]
190
+
180
191
  [[package]]
181
192
  name = "numpy"
182
193
  version = "2.3.1"
@@ -235,6 +246,15 @@ wheels = [
235
246
  { url = "https://files.pythonhosted.org/packages/48/6b/1c6b515a83d5564b1698a61efa245727c8feecf308f4091f565988519d20/numpy-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e610832418a2bc09d974cc9fecebfa51e9532d6190223bc5ef6a7402ebf3b5cb", size = 12927246, upload-time = "2025-06-21T12:27:38.618Z" },
236
247
  ]
237
248
 
249
+ [[package]]
250
+ name = "packaging"
251
+ version = "25.0"
252
+ source = { registry = "https://pypi.org/simple" }
253
+ sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
254
+ wheels = [
255
+ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
256
+ ]
257
+
238
258
  [[package]]
239
259
  name = "pandas"
240
260
  version = "2.3.1"
@@ -360,6 +380,15 @@ wheels = [
360
380
  { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" },
361
381
  ]
362
382
 
383
+ [[package]]
384
+ name = "pluggy"
385
+ version = "1.6.0"
386
+ source = { registry = "https://pypi.org/simple" }
387
+ sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
388
+ wheels = [
389
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
390
+ ]
391
+
363
392
  [[package]]
364
393
  name = "polars"
365
394
  version = "1.31.0"
@@ -374,6 +403,15 @@ wheels = [
374
403
  { url = "https://files.pythonhosted.org/packages/40/4b/0673a68ac4d6527fac951970e929c3b4440c654f994f0c957bd5556deb38/polars-1.31.0-cp39-abi3-win_arm64.whl", hash = "sha256:62ef23bb9d10dca4c2b945979f9a50812ac4ace4ed9e158a6b5d32a7322e6f75", size = 31469078, upload-time = "2025-06-18T11:59:59.242Z" },
375
404
  ]
376
405
 
406
+ [[package]]
407
+ name = "pygments"
408
+ version = "2.19.2"
409
+ source = { registry = "https://pypi.org/simple" }
410
+ sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
411
+ wheels = [
412
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
413
+ ]
414
+
377
415
  [[package]]
378
416
  name = "pypept"
379
417
  version = "1.0.0"
@@ -387,6 +425,22 @@ dependencies = [
387
425
  { name = "requests" },
388
426
  ]
389
427
 
428
+ [[package]]
429
+ name = "pytest"
430
+ version = "8.4.1"
431
+ source = { registry = "https://pypi.org/simple" }
432
+ dependencies = [
433
+ { name = "colorama", marker = "sys_platform == 'win32'" },
434
+ { name = "iniconfig" },
435
+ { name = "packaging" },
436
+ { name = "pluggy" },
437
+ { name = "pygments" },
438
+ ]
439
+ sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
440
+ wheels = [
441
+ { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
442
+ ]
443
+
390
444
  [[package]]
391
445
  name = "python-dateutil"
392
446
  version = "2.9.0.post0"
@@ -1,5 +0,0 @@
1
- from .molecule import load_monomer_library
2
- from .molecule import Molecule
3
- from .molecule import SequenceConstants
4
-
5
- __all__ = ["Molecule", "load_monomer_library", "SequenceConstants"]
@@ -1,43 +0,0 @@
1
- from pathlib import Path
2
-
3
- import polars as pl
4
- from helmkit import load_monomer_library
5
- from helmkit import Molecule
6
- from rdkit import Chem
7
- from rdkit.Chem import AllChem
8
- from rdkit.Chem import Draw
9
- from tqdm import tqdm
10
-
11
-
12
- def main():
13
- data_dir = Path(__file__).parent / "data"
14
- df = pl.read_csv(data_dir / "peptides.csv")
15
- monomer_db = load_monomer_library(data_dir / "monomers.sdf")
16
- for row in tqdm(df.iter_rows(named=True), total=df.height):
17
- helm = row["HELM"]
18
- smiles = row["SMILES"]
19
- try:
20
- m = Molecule(helm, monomer_db)
21
- except:
22
- print(row)
23
- raise
24
- inchi1 = Chem.MolToInchi(m.mol)
25
- other = Chem.MolFromSmiles(smiles)
26
- inchi2 = Chem.MolToInchi(other)
27
- if inchi1 != inchi2:
28
- mistmatches_dir = Path(__file__).parent / "mismatches"
29
- mistmatches_dir.mkdir(exist_ok=True)
30
- AllChem.Compute2DCoords(m.mol, clearConfs=True)
31
- Chem.AssignAtomChiralTagsFromStructure(m.mol)
32
- AllChem.Compute2DCoords(other, clearConfs=True)
33
- Chem.AssignAtomChiralTagsFromStructure(other)
34
- # Draw the two molecules to an image
35
- img = Draw.MolsToGridImage(
36
- [m.mol, other], molsPerRow=2, subImgSize=(800, 800)
37
- )
38
- img.save(mistmatches_dir / f"{row['ID']}.png")
39
- raise ValueError(f"{inchi1} != {inchi2} for row {row}")
40
-
41
-
42
- if __name__ == "__main__":
43
- main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes