caddforge-descriptors 0.11.0__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 (30) hide show
  1. caddforge_descriptors-0.11.0/.github/workflows/release.yml +99 -0
  2. caddforge_descriptors-0.11.0/.gitignore +25 -0
  3. caddforge_descriptors-0.11.0/LICENSE +21 -0
  4. caddforge_descriptors-0.11.0/PKG-INFO +444 -0
  5. caddforge_descriptors-0.11.0/README.md +425 -0
  6. caddforge_descriptors-0.11.0/pyproject.toml +41 -0
  7. caddforge_descriptors-0.11.0/scripts/check_release.py +98 -0
  8. caddforge_descriptors-0.11.0/scripts/ci_tests.json +11 -0
  9. caddforge_descriptors-0.11.0/scripts/run_ci_tests.py +84 -0
  10. caddforge_descriptors-0.11.0/src/cadd_descriptors/__init__.py +48 -0
  11. caddforge_descriptors-0.11.0/src/cadd_descriptors/ani_conformers/__init__.py +11 -0
  12. caddforge_descriptors-0.11.0/src/cadd_descriptors/ani_conformers/pipeline.py +72 -0
  13. caddforge_descriptors-0.11.0/src/cadd_descriptors/ani_conformers/workflow.py +390 -0
  14. caddforge_descriptors-0.11.0/src/cadd_descriptors/ani_psa/__init__.py +32 -0
  15. caddforge_descriptors-0.11.0/src/cadd_descriptors/ani_psa/pipeline.py +173 -0
  16. caddforge_descriptors-0.11.0/src/cadd_descriptors/ani_psa/workflow.py +355 -0
  17. caddforge_descriptors-0.11.0/src/cadd_descriptors/ani_utils.py +225 -0
  18. caddforge_descriptors-0.11.0/src/cadd_descriptors/cluster_molecules/__init__.py +5 -0
  19. caddforge_descriptors-0.11.0/src/cadd_descriptors/cluster_molecules/workflow.py +843 -0
  20. caddforge_descriptors-0.11.0/src/cadd_descriptors/inputs.py +108 -0
  21. caddforge_descriptors-0.11.0/src/cadd_descriptors/phesa/__init__.py +5 -0
  22. caddforge_descriptors-0.11.0/src/cadd_descriptors/phesa/workflow.py +622 -0
  23. caddforge_descriptors-0.11.0/src/cadd_descriptors/tool.py +81 -0
  24. caddforge_descriptors-0.11.0/tests/conftest.py +19 -0
  25. caddforge_descriptors-0.11.0/tests/test_ani_conformers_smoke.py +60 -0
  26. caddforge_descriptors-0.11.0/tests/test_ani_psa_smoke.py +85 -0
  27. caddforge_descriptors-0.11.0/tests/test_cluster_molecules_workflow.py +142 -0
  28. caddforge_descriptors-0.11.0/tests/test_phesa_workflow.py +506 -0
  29. caddforge_descriptors-0.11.0/tests/test_placeholder_descriptors.py +12 -0
  30. caddforge_descriptors-0.11.0/tests/test_release_metadata.py +77 -0
@@ -0,0 +1,99 @@
1
+ name: CI and PyPI release
2
+ 'on':
3
+ push:
4
+ branches:
5
+ - '**'
6
+ tags:
7
+ - v*
8
+ pull_request: null
9
+ workflow_dispatch: null
10
+ permissions:
11
+ contents: read
12
+ jobs:
13
+ build:
14
+ name: Build and validate distributions
15
+ runs-on: ubuntu-24.04
16
+ steps:
17
+ - uses: actions/checkout@v7.0.1
18
+ with:
19
+ fetch-depth: 0
20
+ persist-credentials: false
21
+ - uses: actions/setup-python@v7.0.0
22
+ with:
23
+ python-version: '3.12'
24
+ - name: Install build tools
25
+ run: python -m pip --isolated install --index-url https://pypi.org/simple --upgrade build twine packaging
26
+ - name: Build wheel from source distribution
27
+ run: python -m build
28
+ - name: Validate package metadata
29
+ run: python -m twine check --strict dist/*
30
+ - name: Check distribution identity and release version
31
+ env:
32
+ IS_RELEASE: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
33
+ RELEASE_TAG: ${{ github.ref_name }}
34
+ run: python scripts/check_release.py .
35
+ - uses: actions/upload-artifact@v7.0.2
36
+ with:
37
+ name: distributions
38
+ path: dist/*
39
+ if-no-files-found: error
40
+ test:
41
+ name: Test wheel (Python ${{ matrix.python }})
42
+ needs: build
43
+ runs-on: ubuntu-24.04
44
+ strategy:
45
+ fail-fast: false
46
+ matrix:
47
+ python:
48
+ - '3.10'
49
+ - '3.12'
50
+ steps:
51
+ - uses: actions/checkout@v7.0.1
52
+ with:
53
+ persist-credentials: false
54
+ - uses: actions/setup-python@v7.0.0
55
+ with:
56
+ python-version: ${{ matrix.python }}
57
+ - name: Set up Java for PyOCL
58
+ uses: actions/setup-java@v6.0.1
59
+ with:
60
+ distribution: temurin
61
+ java-version: '21'
62
+ - uses: actions/download-artifact@v8.0.1
63
+ with:
64
+ name: distributions
65
+ path: dist
66
+ - name: Install built wheel and test dependencies
67
+ shell: bash
68
+ run: |-
69
+ python -m pip --isolated install --index-url https://pypi.org/simple dist/*.whl
70
+ python -m pip --isolated install --index-url https://pypi.org/simple pytest packaging 'tomli; python_version < "3.11"'
71
+ - name: Test installed wheel outside the checkout
72
+ env:
73
+ MPLBACKEND: Agg
74
+ OMP_NUM_THREADS: '1'
75
+ OPENBLAS_NUM_THREADS: '1'
76
+ run: python scripts/run_ci_tests.py
77
+ - name: Check installed dependencies
78
+ run: python -m pip check
79
+ publish:
80
+ name: Publish to PyPI
81
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
82
+ needs:
83
+ - build
84
+ - test
85
+ runs-on: ubuntu-24.04
86
+ environment:
87
+ name: pypi
88
+ url: https://pypi.org/project/caddforge-descriptors/
89
+ permissions:
90
+ id-token: write
91
+ concurrency:
92
+ group: pypi-${{ github.ref }}
93
+ cancel-in-progress: false
94
+ steps:
95
+ - uses: actions/download-artifact@v8.0.1
96
+ with:
97
+ name: distributions
98
+ path: dist
99
+ - uses: pypa/gh-action-pypi-publish@v1.14.2
@@ -0,0 +1,25 @@
1
+ # Byte-compiled / cache
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.so
5
+
6
+ # Environments
7
+ .venv/
8
+ .env
9
+
10
+ # Packaging / build
11
+ build/
12
+ dist/
13
+ *.egg-info/
14
+ .hatch/
15
+
16
+ # Tooling caches
17
+ .pytest_cache/
18
+ .ruff_cache/
19
+ .mypy_cache/
20
+ .coverage
21
+ runs/
22
+
23
+ # IDE/editor
24
+ .idea/
25
+ .vscode/
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 The CADDForge Authors
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,444 @@
1
+ Metadata-Version: 2.5
2
+ Name: caddforge-descriptors
3
+ Version: 0.11.0
4
+ Summary: Molecular descriptor workflows for the CADDForge toolbox.
5
+ Author-email: Manuel Sellner <manuel.sellner@idorsia.com>, Christoph Sager <christoph.sager@idorsia.com>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: caddforge-core>=0.21.0
10
+ Requires-Dist: hdbscan>=0.8.33
11
+ Requires-Dist: numpy>=1.25
12
+ Requires-Dist: pandas>=2.1
13
+ Requires-Dist: pyocl-idor>=0.8.3
14
+ Requires-Dist: rdkit>=2022.9.5
15
+ Requires-Dist: scikit-learn-extra>=0.3
16
+ Requires-Dist: scikit-learn>=1.3
17
+ Requires-Dist: torchani>=2.7.9
18
+ Description-Content-Type: text/markdown
19
+
20
+ # CADDForge Descriptors Plugin
21
+
22
+ Install this plugin with `python -m pip install caddforge-descriptors`. It depends directly
23
+ on `caddforge-core>=0.21.0`; Python imports and CLI commands still use `caddforge`.
24
+ For the complete public toolbox, including user-facing extras, install
25
+ `python -m pip install caddforge`. When migrating an existing environment, uninstall
26
+ the former `caddforge` core distribution before installing renamed core and updated plugins.
27
+
28
+ `caddforge-descriptors` provides descriptor and clustering workflows as plugins for the CADDForge framework.
29
+
30
+ ## Implemented Tools
31
+ - [`ani-psa`](#tool-ani-psa)
32
+ - [`ani-conformers`](#tool-ani-conformers)
33
+ - [`cluster-molecules`](#tool-cluster-molecules)
34
+ - [`phesa`](#tool-phesa)
35
+
36
+ ## Tool: `ani-psa`
37
+
38
+ ### What it does
39
+ The `ani-psa` tool computes ANI-weighted 3D polar surface area values from one molecule or a batch of molecules. The workflow:
40
+ - accepts one SMILES, an inline SMILES list, or a SMILES file
41
+ - generates conformers with RDKit, optimizes them with MMFF, clusters them, and re-ranks them with TorchANI
42
+ - computes 3D PSA on the selected conformers and reports the Boltzmann-weighted average
43
+ - writes standardized CADDForge outputs plus per-molecule JSON/CSV summaries
44
+
45
+ ### External dependencies
46
+ No external command-line tools are required beyond the Python environment.
47
+
48
+ Runtime notes:
49
+ - TorchANI and PyTorch must be available in the environment.
50
+ - CUDA is optional; set `device: cuda` or `device: cuda:0` if you want GPU execution.
51
+
52
+ ### CLI usage
53
+ ```bash
54
+ caddforge run descriptors ani-psa --config ani_psa.yaml
55
+ ```
56
+
57
+ ### Python API usage
58
+ ```python
59
+ from cadd_descriptors import AniPsaConfig, AniPsaWorkflow
60
+
61
+ cfg = AniPsaConfig(
62
+ smiles="CC(=O)Oc1ccccc1C(=O)O",
63
+ top_n=25,
64
+ backend="local",
65
+ )
66
+
67
+ workflow = AniPsaWorkflow(cfg)
68
+ job = workflow.prepare_job()
69
+ result = workflow.run(job)
70
+ print(result.status.value, result.metrics)
71
+ ```
72
+
73
+ ### Example config
74
+ ```yaml
75
+ smiles: "CCO"
76
+ num_confs: 100
77
+ top_n: 10
78
+ rmsd_cluster_thresh: 0.5
79
+ max_mmff_iters: 200
80
+ temperature: 298.15
81
+ model_name: ANI2x
82
+ device: cpu
83
+ ```
84
+
85
+ ### Tool-specific config options
86
+ Only one input source must be set:
87
+ - `smiles`
88
+ - `smiles_list`
89
+ - `smiles_file`
90
+
91
+ #### Input options
92
+ - `smiles` (`str | None`): Single SMILES string.
93
+ - `smiles_list` (`list[str] | None`): Inline batch of SMILES strings.
94
+ - `smiles_file` (`Path | None`): SMILES input file.
95
+ - `smiles_column` (`str`, default `smiles`): Column to read from CSV/TSV input.
96
+
97
+ #### Conformer and ANI options
98
+ - `num_confs` (`int`, default `500`): Number of conformers to generate.
99
+ - `rmsd_cluster_thresh` (`float`, default `0.5`): RMSD threshold for conformer clustering.
100
+ - `max_mmff_iters` (`int`, default `200`): MMFF optimization steps per conformer.
101
+ - `top_n` (`int`, default `50`): Number of ANI-ranked conformers kept for PSA averaging.
102
+ - `temperature` (`float`, default `298.15`): Boltzmann weighting temperature in Kelvin.
103
+ - `model_name` (`str`, default `ANI2x`): TorchANI model name.
104
+ - `device` (`str | None`, default `cpu`): Torch device string.
105
+
106
+ ### Outputs
107
+ For each run, artifacts are written under `runs/<date>/<job-id>/`:
108
+ - `results/results.json`: per-molecule PSA summary payload
109
+ - `results/results.csv`: tabular per-molecule PSA summary
110
+ - `results/result_record.json`: standardized CADDForge `Result`
111
+ - `stdout.log`: workflow log
112
+
113
+ ## Tool: `phesa`
114
+
115
+ ### What it does
116
+ The `phesa` tool computes OpenChemLib PheSA shape/pharmacophore similarities between one reference
117
+ molecule and one or more candidates. It supports:
118
+ - a reference molecule from SMILES, SDF, or an encoded PheSA text file
119
+ - candidate molecules from either SMILES strings or single-/multi-compound SDF files
120
+ - configurable maximum conformer count and pharmacophore point weight
121
+ - aligned SDF outputs for the reference and candidates
122
+ - pipeline outputs for scores and aligned candidate collections
123
+
124
+ ### External dependencies
125
+ PheSA imports `pyocl`, provided by the public `pyocl-idor>=0.8.3` distribution.
126
+ The OpenChemLib runtime requires Java 21 or newer with `JAVA_HOME` configured.
127
+
128
+ ### CLI usage
129
+ ```bash
130
+ caddforge run descriptors phesa --config phesa.yaml
131
+ ```
132
+
133
+ ### Python API usage
134
+ ```python
135
+ from cadd_descriptors import PhesaConfig, PhesaWorkflow
136
+
137
+ cfg = PhesaConfig(
138
+ reference_smiles="CCO",
139
+ candidate_smiles=["CCCO", "CCCCO"],
140
+ max_confs=200,
141
+ pp_weight=0.5,
142
+ )
143
+
144
+ workflow = PhesaWorkflow(cfg)
145
+ job = workflow.prepare_job()
146
+ result = workflow.run(job)
147
+ print(result.status.value, result.metrics)
148
+ ```
149
+
150
+ ### Example configs
151
+ ```yaml
152
+ reference_smiles: "CCO"
153
+ candidate_smiles:
154
+ - "CCCO"
155
+ - "CCCCO"
156
+ max_confs: 200
157
+ pp_weight: 0.5
158
+ ```
159
+
160
+ ```yaml
161
+ reference_sdf: reference.sdf
162
+ candidate_sdf_files:
163
+ - candidates.sdf
164
+ - extra_candidate.sdf
165
+ max_confs: 200
166
+ pp_weight: 0.5
167
+ write_merged_aligned_candidates_sdf: true
168
+ ```
169
+
170
+ ```yaml
171
+ reference_phesa_file: reference_phesa.txt
172
+ candidate_smiles:
173
+ - "CCCO"
174
+ - "CCCCO"
175
+ max_confs: 200
176
+ pp_weight: 0.5
177
+ ```
178
+
179
+ ### Tool-specific config options
180
+ Exactly one reference source must be set:
181
+ - `reference_smiles`
182
+ - `reference_sdf`
183
+ - `reference_phesa_file`
184
+
185
+ Exactly one candidate source must be set:
186
+ - `candidate_smiles`
187
+ - `candidate_sdf_files`
188
+
189
+ `candidate_sdf_files` may contain single- or multi-compound SDF files. Each valid SDF molecule
190
+ record is scored as one candidate; record names are used as candidate IDs when present, otherwise
191
+ IDs fall back to the source file stem plus the record index.
192
+
193
+ #### PheSA options
194
+ - `max_confs` (`int`, default `200`): Maximum number of conformers for generated PheSA descriptors.
195
+ - `pp_weight` (`float`, default `0.5`): Pharmacophore point weight.
196
+ - `scores_csv_name` (`str`, default `scores.csv`): Scores CSV filename under `results/`.
197
+ - `aligned_dir_name` (`str`, default `aligned`): Aligned SDF output directory under `results/`.
198
+ - `write_merged_aligned_candidates_sdf` (`bool`, default `false`): Write one SDF containing all
199
+ successfully aligned candidate molecules.
200
+ - `merged_aligned_candidates_sdf_name` (`str`, default `aligned_candidates.sdf`): Filename for the
201
+ merged aligned candidate SDF under the aligned output directory.
202
+
203
+ When `reference_smiles` is used, both the reference and candidates use OpenChemLib's generated-shape
204
+ handler with `max_confs` and `pp_weight`. When `reference_sdf` is used, the reference uses
205
+ OpenChemLib's one-conformation handler; candidates still use `max_confs` and `pp_weight`.
206
+
207
+ With `reference_phesa_file`, the entire UTF-8 text file must contain one encoded PheSA descriptor.
208
+ Surrounding whitespace is stripped before decoding with `DescriptorHandlerShape.decode()`.
209
+ The decoded reference is used directly, without generating reference conformers. Candidates still
210
+ use `max_confs` and `pp_weight`. Empty, unreadable, or invalid descriptor files fail the run.
211
+
212
+ **Reference coordinate frame:** With `reference_phesa_file`, the aligned reference and candidate
213
+ SDFs, including the optional merged candidate SDF, remain in the PheSA alignment coordinate frame.
214
+ They are not restored to the coordinates of the original molecule used to create the encoded
215
+ descriptor. If downstream calculations need alignment in an input reference SDF's coordinate frame,
216
+ use `reference_sdf`; coordinate restoration applies only to that reference mode.
217
+
218
+ ### Outputs
219
+ For each run, artifacts are written under `runs/<date>/<job-id>/`:
220
+ - `results/results.json`: per-candidate PheSA similarity payload
221
+ - `results/scores.csv`: tabular candidate scores
222
+ - `results/aligned/reference.sdf`: aligned reference structure
223
+ - `results/aligned/candidates/*.sdf`: aligned candidate structures
224
+ - `results/aligned/aligned_candidates.sdf`: optional merged aligned candidate SDF
225
+ - `results/aligned/manifest.csv`: candidate scores and aligned SDF paths
226
+ - `results/result_record.json`: standardized CADDForge `Result`
227
+
228
+ ### Pipeline artifacts
229
+ The plugin exposes:
230
+ - input `reference_sdf` mapped to config field `reference_sdf`
231
+ - input `reference_phesa_file` mapped to config field `reference_phesa_file`
232
+ - input `candidates` mapped to config field `candidate_sdf_files`
233
+ - output `scores` from `scores_csv`
234
+ - output `aligned_reference` from `aligned_reference_sdf`
235
+ - output `aligned_candidates` from `aligned_manifest_csv` using column `aligned_candidate_sdf`
236
+ - optional output `merged_aligned_candidates` from `merged_aligned_candidates_sdf`
237
+
238
+ ## Tool: `ani-conformers`
239
+
240
+ ### What it does
241
+ The `ani-conformers` tool generates ANI re-ranked conformers and writes the selected conformers to SDF. It supports:
242
+ - one SMILES, an inline SMILES list, a SMILES file, or an input SDF file
243
+ - RDKit conformer generation and MMFF optimization
244
+ - ANI re-ranking of clustered conformer representatives
245
+ - batch processing with per-molecule summaries and a combined ranked-conformer SDF
246
+
247
+ ### External dependencies
248
+ No external command-line tools are required beyond the Python environment.
249
+
250
+ Runtime notes:
251
+ - TorchANI and PyTorch must be available in the environment.
252
+ - CUDA is optional.
253
+
254
+ ### CLI usage
255
+ ```bash
256
+ caddforge run descriptors ani-conformers --config ani_conformers.yaml
257
+ ```
258
+
259
+ ### Python API usage
260
+ ```python
261
+ from cadd_descriptors import AniConformersConfig, AniConformersWorkflow
262
+
263
+ cfg = AniConformersConfig(
264
+ smiles="CCO",
265
+ top_n=10,
266
+ num_confs=50,
267
+ backend="local",
268
+ )
269
+
270
+ workflow = AniConformersWorkflow(cfg)
271
+ job = workflow.prepare_job()
272
+ result = workflow.run(job)
273
+ print(result.status.value, result.paths["ranked_conformers_sdf"])
274
+ ```
275
+
276
+ ### Example config
277
+ ```yaml
278
+ smiles: "CCO"
279
+ num_confs: 100
280
+ top_n: 10
281
+ rmsd_cluster_thresh: 0.5
282
+ max_mmff_iters: 200
283
+ model_name: ANI2x
284
+ device: cpu
285
+ output_sdf_name: ranked_conformers.sdf
286
+ ```
287
+
288
+ ### Tool-specific config options
289
+ Only one input source must be set:
290
+ - `smiles`
291
+ - `smiles_list`
292
+ - `smiles_file`
293
+ - `structure_file`
294
+
295
+ #### Input options
296
+ - `smiles` (`str | None`): Single SMILES string.
297
+ - `smiles_list` (`list[str] | None`): Inline batch of SMILES strings.
298
+ - `smiles_file` (`Path | None`): SMILES input file.
299
+ - `structure_file` (`Path | None`): Input SDF file.
300
+ - `smiles_column` (`str`, default `smiles`): Column to read from CSV/TSV SMILES input.
301
+
302
+ #### Conformer and ANI options
303
+ - `num_confs` (`int`, default `500`): Number of conformers to generate.
304
+ - `rmsd_cluster_thresh` (`float`, default `0.5`): RMSD threshold for conformer clustering.
305
+ - `max_mmff_iters` (`int`, default `200`): MMFF optimization steps per conformer.
306
+ - `top_n` (`int`, default `50`): Number of ANI-ranked conformers to keep.
307
+ - `model_name` (`str`, default `ANI2x`): TorchANI model name.
308
+ - `device` (`str | None`, default `cpu`): Torch device string.
309
+ - `output_sdf_name` (`str`, default `ranked_conformers.sdf`): Output SDF filename under `results/`.
310
+
311
+ ### Outputs
312
+ For each run, artifacts are written under `runs/<date>/<job-id>/`:
313
+ - `results/results.json`: per-molecule conformer summary payload
314
+ - `results/<output_sdf_name>`: ranked conformers SDF
315
+ - `results/result_record.json`: standardized CADDForge `Result`
316
+ - `stdout.log`: workflow log
317
+
318
+ ## Tool: `cluster-molecules`
319
+
320
+ ### What it does
321
+ The `cluster-molecules` tool clusters molecules from SMILES input using fingerprint-based and scaffold-based algorithms. It supports:
322
+ - SMILES input from `.smi`, `.smiles`, `.sdf`, `.csv`, or `.tsv`
323
+ - multiple clustering algorithms: Butina, agglomerative, DBSCAN, HDBSCAN, k-medoids, scaffold, and affinity propagation
324
+ - medoid/centroid reporting for each cluster
325
+ - automatic k-medoids planning that chooses exact in-memory, exact disk-backed, or approximate execution modes
326
+
327
+ ### External dependencies
328
+ No external command-line tools are required beyond the Python environment.
329
+
330
+ Runtime notes:
331
+ - `hdbscan` is required only for `algorithm: hdbscan`.
332
+ - The approximate `kmedoids` fallback uses sparse `MiniBatchKMeans` and then snaps cluster centers back to actual medoids.
333
+
334
+ ### CLI usage
335
+ ```bash
336
+ caddforge run descriptors cluster-molecules --config cluster.yaml
337
+ ```
338
+
339
+ ### Python API usage
340
+ ```python
341
+ from cadd_descriptors import ClusterMoleculesConfig, ClusterMoleculesWorkflow
342
+
343
+ cfg = ClusterMoleculesConfig(
344
+ input_path="molecules.csv",
345
+ smiles_column="smiles",
346
+ algorithm="kmedoids",
347
+ kmedoids_n_clusters=100,
348
+ kmedoids_mode="auto",
349
+ backend="local",
350
+ )
351
+
352
+ workflow = ClusterMoleculesWorkflow(cfg)
353
+ job = workflow.prepare_job()
354
+ result = workflow.run(job)
355
+ print(result.status.value, result.metrics)
356
+ ```
357
+
358
+ ### Example config
359
+ ```yaml
360
+ input_path: molecules.csv
361
+ smiles_column: smiles
362
+ algorithm: kmedoids # butina|agglomerative|dbscan|hdbscan|kmedoids|scaffold|affinity_propagation
363
+ fingerprint_radius: 2
364
+ fingerprint_bits: 2048
365
+ kmedoids_n_clusters: 100
366
+ kmedoids_mode: auto # auto|exact_ram|exact_disk|approximate
367
+ kmedoids_memory_budget_gb: null
368
+ kmedoids_disk_budget_gb: null
369
+ kmedoids_max_exact_n: 5000
370
+ kmedoids_max_exact_pairs: 25000000
371
+ kmedoids_max_exact_disk_n: 20000
372
+ kmedoids_max_exact_disk_pairs: 100000000
373
+ kmedoids_batch_size: 1024
374
+ kmedoids_max_iter: 20
375
+ kmedoids_minibatch_size: 4096
376
+ kmedoids_local_refine_limit: 128
377
+ kmedoids_temp_dir: null
378
+ ```
379
+
380
+ ### Tool-specific config options
381
+ #### Input options
382
+ - `input_path` (`Path`): Input file containing molecules.
383
+ - `smiles_column` (`str`, default `smiles`): SMILES column for CSV/TSV input.
384
+ - `id_column` (`str | None`): Optional identifier column copied into outputs.
385
+
386
+ #### Fingerprint options
387
+ - `fingerprint_radius` (`int`, default `2`): Morgan fingerprint radius.
388
+ - `fingerprint_bits` (`int`, default `2048`): Morgan fingerprint size.
389
+
390
+ #### Algorithm selection
391
+ - `algorithm` (`butina | agglomerative | dbscan | hdbscan | kmedoids | scaffold | affinity_propagation`): Clustering algorithm.
392
+
393
+ #### Algorithm-specific options
394
+ - `butina_cutoff` (`float`, default `0.6`): Butina Tanimoto similarity cutoff.
395
+ - `agglomerative_n_clusters` (`int | None`): Agglomerative cluster count.
396
+ - `agglomerative_distance_threshold` (`float | None`): Agglomerative distance threshold.
397
+ - `dbscan_eps` (`float`, default `0.4`): DBSCAN epsilon in distance space.
398
+ - `dbscan_min_samples` (`int`, default `3`): DBSCAN minimum samples.
399
+ - `hdbscan_min_cluster_size` (`int`, default `5`): HDBSCAN minimum cluster size.
400
+ - `affinity_preference` (`float | None`): Affinity propagation preference.
401
+ - `scaffold_use_generic` (`bool`, default `true`): Use generic Bemis-Murcko scaffolds.
402
+
403
+ #### K-medoids planning options
404
+ - `kmedoids_n_clusters` (`int`, default `5`): Target number of medoid clusters.
405
+ - `kmedoids_mode` (`auto | exact_ram | exact_disk | approximate`, default `auto`): Force or auto-select the execution strategy.
406
+ - `kmedoids_memory_budget_gb` (`float | None`): Optional RAM budget for planning.
407
+ - `kmedoids_disk_budget_gb` (`float | None`): Optional temp-disk budget for planning.
408
+ - `kmedoids_max_exact_n` (`int`, default `5000`): Max size for exact in-memory planning.
409
+ - `kmedoids_max_exact_pairs` (`int`, default `25000000`): Max pair count for exact in-memory planning.
410
+ - `kmedoids_max_exact_disk_n` (`int`, default `20000`): Max size for exact disk-backed planning.
411
+ - `kmedoids_max_exact_disk_pairs` (`int`, default `100000000`): Max pair count for exact disk-backed planning.
412
+ - `kmedoids_batch_size` (`int`, default `1024`): Batch size for exact assignment/cost evaluation.
413
+ - `kmedoids_max_iter` (`int`, default `20`): PAM swap-iteration limit.
414
+ - `kmedoids_minibatch_size` (`int`, default `4096`): Mini-batch size for approximate mode.
415
+ - `kmedoids_local_refine_limit` (`int`, default `128`): Max cluster size for exact local medoid refinement in approximate mode.
416
+ - `kmedoids_temp_dir` (`Path | None`): Optional temp directory for disk-backed exact mode.
417
+
418
+ ### Outputs
419
+ For each run, artifacts are written under `runs/<date>/<job-id>/`:
420
+ - `results/assignments.csv`: molecule-to-cluster assignments
421
+ - `results/centroids.csv`: per-cluster medoid/centroid summary
422
+ - `results/summary.json`: clustering summary
423
+ - `results/planning.json`: k-medoids planning summary when `algorithm: kmedoids`
424
+ - `results/result_record.json`: standardized CADDForge `Result`
425
+ - `stdout.log`: workflow log
426
+ ## Compressed inputs
427
+
428
+ File-based SMILES, SDF, CSV, and TSV inputs may be supplied with a trailing `.gz`. CADDForge
429
+ expands these wrappers in job-local runtime scratch before invoking the descriptor workflow. Plain
430
+ inputs and native tool formats retain their existing behavior.
431
+
432
+ ## Continuous integration and releases
433
+
434
+ `.github/workflows/release.yml` builds a wheel and source archive, validates
435
+ metadata, and tests the installed wheel on Linux with Python 3.10 and 3.12.
436
+ The lightweight test selection is recorded in `scripts/ci_tests.json`;
437
+ external scientific engines are validated separately.
438
+
439
+ Pushing a matching `v*` release tag publishes the tested artifacts to PyPI.
440
+ Branch pushes, pull requests and manual runs never publish. Configure a
441
+ PyPI Trusted Publisher for the `idorsia` repository, `release.yml` workflow
442
+ and `pypi` environment before pushing release tags. Runtime CADDForge
443
+ dependencies must already be available publicly; publish core first,
444
+ and publish docking/descriptors before MD.