rdkit-cli 0.3.1__tar.gz → 0.3.2__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 (125) hide show
  1. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/CHANGELOG.md +25 -0
  2. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/PKG-INFO +5 -2
  3. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/README.md +4 -1
  4. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/pyproject.toml +1 -1
  5. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/__init__.py +1 -1
  6. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/cli.py +6 -0
  7. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/conformers.py +174 -0
  8. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/depict.py +111 -0
  9. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/descriptors.py +22 -3
  10. rdkit_cli-0.3.2/src/rdkit_cli/commands/energy.py +151 -0
  11. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/filter.py +47 -8
  12. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/fingerprints.py +4 -1
  13. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/fragment.py +103 -1
  14. rdkit_cli-0.3.2/src/rdkit_cli/commands/pharmacophore.py +151 -0
  15. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/props.py +163 -0
  16. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/reactions.py +158 -0
  17. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/scaffold.py +103 -1
  18. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/similarity.py +143 -4
  19. rdkit_cli-0.3.2/src/rdkit_cli/commands/stereo.py +236 -0
  20. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/conformers.py +146 -0
  21. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/descriptors.py +80 -4
  22. rdkit_cli-0.3.2/src/rdkit_cli/core/energy.py +118 -0
  23. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/filters.py +33 -4
  24. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/fingerprints.py +37 -1
  25. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/fragment.py +38 -0
  26. rdkit_cli-0.3.2/src/rdkit_cli/core/pharmacophore.py +115 -0
  27. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/reactions.py +78 -0
  28. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/scaffold.py +54 -0
  29. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/similarity.py +134 -13
  30. rdkit_cli-0.3.2/src/rdkit_cli/core/stereo.py +106 -0
  31. rdkit_cli-0.3.2/tests/integration/test_new_features.py +490 -0
  32. rdkit_cli-0.3.2/tests/unit/test_extended_features.py +457 -0
  33. rdkit_cli-0.3.2/tests/unit/test_shape_constrained_network.py +391 -0
  34. rdkit_cli-0.3.2/tests/unit/test_stereo_energy_pharmacophore.py +401 -0
  35. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/.github/workflows/publish.yml +0 -0
  36. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/LICENSE +0 -0
  37. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/docs/commands.md +0 -0
  38. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/__main__.py +0 -0
  39. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/__init__.py +0 -0
  40. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/align.py +0 -0
  41. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/convert.py +0 -0
  42. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/deduplicate.py +0 -0
  43. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/diversity.py +0 -0
  44. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/enumerate.py +0 -0
  45. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/info.py +0 -0
  46. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/mcs.py +0 -0
  47. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/merge.py +0 -0
  48. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/mmp.py +0 -0
  49. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/protonate.py +0 -0
  50. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/rgroup.py +0 -0
  51. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/rings.py +0 -0
  52. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/rmsd.py +0 -0
  53. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/sample.py +0 -0
  54. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/sascorer.py +0 -0
  55. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/split.py +0 -0
  56. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/standardize.py +0 -0
  57. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/stats.py +0 -0
  58. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/commands/validate.py +0 -0
  59. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/__init__.py +0 -0
  60. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/align.py +0 -0
  61. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/deduplicate.py +0 -0
  62. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/depict.py +0 -0
  63. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/diversity.py +0 -0
  64. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/enumerate.py +0 -0
  65. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/info.py +0 -0
  66. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/mcs.py +0 -0
  67. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/merge.py +0 -0
  68. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/mmp.py +0 -0
  69. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/protonate.py +0 -0
  70. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/rgroup.py +0 -0
  71. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/rings.py +0 -0
  72. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/rmsd.py +0 -0
  73. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/sample.py +0 -0
  74. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/sascorer.py +0 -0
  75. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/split.py +0 -0
  76. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/standardizer.py +0 -0
  77. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/stats.py +0 -0
  78. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/core/validate.py +0 -0
  79. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/io/__init__.py +0 -0
  80. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/io/formats.py +0 -0
  81. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/io/readers.py +0 -0
  82. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/io/writers.py +0 -0
  83. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/parallel/__init__.py +0 -0
  84. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/parallel/batch.py +0 -0
  85. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/parallel/executor.py +0 -0
  86. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/progress/__init__.py +0 -0
  87. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/progress/ninja.py +0 -0
  88. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/utils/__init__.py +0 -0
  89. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/src/rdkit_cli/utils/logging.py +0 -0
  90. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/__init__.py +0 -0
  91. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/conftest.py +0 -0
  92. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/fixtures/sample.csv +0 -0
  93. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/fixtures/sample.smi +0 -0
  94. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/integration/__init__.py +0 -0
  95. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/integration/test_cli.py +0 -0
  96. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/integration/test_interop.py +0 -0
  97. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/integration/test_new_commands.py +0 -0
  98. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/__init__.py +0 -0
  99. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_align.py +0 -0
  100. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_deduplicate.py +0 -0
  101. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_depict.py +0 -0
  102. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_descriptors.py +0 -0
  103. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_diversity.py +0 -0
  104. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_enumerate.py +0 -0
  105. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_filters.py +0 -0
  106. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_fingerprints.py +0 -0
  107. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_fragment.py +0 -0
  108. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_info.py +0 -0
  109. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_io.py +0 -0
  110. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_mcs.py +0 -0
  111. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_merge.py +0 -0
  112. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_mmp.py +0 -0
  113. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_protonate.py +0 -0
  114. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_reactions.py +0 -0
  115. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_rgroup.py +0 -0
  116. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_rings.py +0 -0
  117. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_rmsd.py +0 -0
  118. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_sample.py +0 -0
  119. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_sascorer.py +0 -0
  120. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_scaffold.py +0 -0
  121. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_similarity.py +0 -0
  122. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_split.py +0 -0
  123. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_standardizer.py +0 -0
  124. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_stats.py +0 -0
  125. {rdkit_cli-0.3.1 → rdkit_cli-0.3.2}/tests/unit/test_validate.py +0 -0
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.2] - 2026-04-03
9
+
10
+ ### Added
11
+
12
+ - **stereo**: New command for stereochemistry analysis — CIP label assignment (R/S, E/Z), stereocenter perception, enhanced stereo group inspection, and stereo cleanup/canonicalization
13
+ - **energy**: New command for force field energy calculations — single-point MMFF/UFF energy and structure minimization with convergence reporting
14
+ - **pharmacophore**: New command for pharmacophore feature perception (Donor, Acceptor, Aromatic, etc.) and 2D pharmacophore fingerprint similarity search
15
+ - **fingerprints**: Added Avalon, MHFP (MinHash), and 2D pharmacophore (Gobbi) fingerprint types
16
+ - **descriptors**: Added 42 Molecular Quantum Numbers (MQN) and 10 3D shape descriptors (PMI, NPR, Asphericity, Eccentricity, SpherocityIndex, PBF); new `--mqn`, `--3d`, and `--generate-conformers` flags
17
+ - **similarity**: Added 8 metrics (AllBit, Asymmetric, BraunBlanquet, Kulczynski, McConnaughey, OnBit, RogotGoldberg, Tversky with alpha/beta); new `shape` subcommand for 3D shape similarity (Tanimoto, Protrude, Tversky)
18
+ - **filter**: Expanded structural alert catalogs — `--catalog` option supports PAINS, PAINS_A/B/C, Brenk, NIH, ZINC, and all combined; added `alerts` subcommand as alias
19
+ - **conformers**: Added `constrained` subcommand for template-constrained 3D embedding and `torsion` subcommand for dihedral angle scanning with energy profiles
20
+ - **reactions**: Added `map` subcommand for atom-atom mapping inspection (text/JSON) and `fingerprint` subcommand for reaction difference/structural fingerprints
21
+ - **scaffold**: Added `network` subcommand for scaffold network construction (CSV/JSON output) using rdScaffoldNetwork
22
+ - **props**: Added `charges` subcommand for Gasteiger partial charges and `crippen` subcommand for per-atom LogP/MR contributions
23
+ - **fragment**: Added `brics-build` subcommand for recombining BRICS fragments into new molecules
24
+ - **depict**: Added `highlight` subcommand for SMARTS-based atom/bond highlighting with custom RGB colors
25
+
26
+ ### Changed
27
+
28
+ - Total command count increased from 29 to 32 (stereo, energy, pharmacophore)
29
+ - Total fingerprint types increased from 6 to 9
30
+ - Total descriptor count increased from ~133 to ~185
31
+ - Similarity metrics increased from 5 to 13
32
+
8
33
  ## [0.3.1] - 2026-03-14
9
34
 
10
35
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rdkit-cli
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: A comprehensive CLI tool for RDKit cheminformatics operations
5
5
  Project-URL: Homepage, https://github.com/vitruves/rdkit-cli
6
6
  Project-URL: Repository, https://github.com/vitruves/rdkit-cli
@@ -41,7 +41,7 @@ Description-Content-Type: text/markdown
41
41
 
42
42
  A high-performance CLI for cheminformatics workflows, powered by native RDKit (C++ under the hood).
43
43
 
44
- **29 commands** | **5 I/O formats** (CSV, TSV, SMI, SDF, Parquet) | **multi-core parallel processing** | **~80ms startup**
44
+ **32 commands** | **5 I/O formats** (CSV, TSV, SMI, SDF, Parquet) | **multi-core parallel processing** | **~80ms startup**
45
45
 
46
46
  ## Installation
47
47
 
@@ -81,6 +81,7 @@ Commands:
81
81
  depict Generate molecular depictions (SVG/PNG)
82
82
  descriptors Compute molecular descriptors
83
83
  diversity Analyze and select diverse molecules
84
+ energy Force field energy calculations
84
85
  enumerate Enumerate stereoisomers and tautomers
85
86
  filter Filter by substructure, properties, drug-likeness, PAINS
86
87
  fingerprints Compute fingerprints (Morgan, MACCS, RDKit, AtomPair, Torsion)
@@ -89,6 +90,7 @@ Commands:
89
90
  mcs Find Maximum Common Substructure
90
91
  merge Merge multiple molecule files
91
92
  mmp Matched Molecular Pairs analysis
93
+ pharmacophore Pharmacophore feature analysis
92
94
  props Property column operations (add, rename, drop, keep)
93
95
  protonate Enumerate protonation states
94
96
  reactions Apply SMIRKS transformations and enumerate products
@@ -102,6 +104,7 @@ Commands:
102
104
  split Split files into smaller chunks
103
105
  standardize Standardize and canonicalize molecules
104
106
  stats Calculate dataset statistics
107
+ stereo Analyze and manipulate stereochemistry
105
108
  validate Validate molecular structures
106
109
 
107
110
  Use 'rdkit-cli <command> --help' for command-specific options.
@@ -7,7 +7,7 @@
7
7
 
8
8
  A high-performance CLI for cheminformatics workflows, powered by native RDKit (C++ under the hood).
9
9
 
10
- **29 commands** | **5 I/O formats** (CSV, TSV, SMI, SDF, Parquet) | **multi-core parallel processing** | **~80ms startup**
10
+ **32 commands** | **5 I/O formats** (CSV, TSV, SMI, SDF, Parquet) | **multi-core parallel processing** | **~80ms startup**
11
11
 
12
12
  ## Installation
13
13
 
@@ -47,6 +47,7 @@ Commands:
47
47
  depict Generate molecular depictions (SVG/PNG)
48
48
  descriptors Compute molecular descriptors
49
49
  diversity Analyze and select diverse molecules
50
+ energy Force field energy calculations
50
51
  enumerate Enumerate stereoisomers and tautomers
51
52
  filter Filter by substructure, properties, drug-likeness, PAINS
52
53
  fingerprints Compute fingerprints (Morgan, MACCS, RDKit, AtomPair, Torsion)
@@ -55,6 +56,7 @@ Commands:
55
56
  mcs Find Maximum Common Substructure
56
57
  merge Merge multiple molecule files
57
58
  mmp Matched Molecular Pairs analysis
59
+ pharmacophore Pharmacophore feature analysis
58
60
  props Property column operations (add, rename, drop, keep)
59
61
  protonate Enumerate protonation states
60
62
  reactions Apply SMIRKS transformations and enumerate products
@@ -68,6 +70,7 @@ Commands:
68
70
  split Split files into smaller chunks
69
71
  standardize Standardize and canonicalize molecules
70
72
  stats Calculate dataset statistics
73
+ stereo Analyze and manipulate stereochemistry
71
74
  validate Validate molecular structures
72
75
 
73
76
  Use 'rdkit-cli <command> --help' for command-specific options.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rdkit-cli"
3
- version = "0.3.1"
3
+ version = "0.3.2"
4
4
  description = "A comprehensive CLI tool for RDKit cheminformatics operations"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -1,4 +1,4 @@
1
1
  """rdkit-cli: A comprehensive CLI tool for RDKit cheminformatics operations."""
2
2
 
3
- __version__ = "0.3.1"
3
+ __version__ = "0.3.2"
4
4
  __author__ = "Vitruves"
@@ -146,6 +146,7 @@ def _register_commands(subparsers):
146
146
  depict,
147
147
  descriptors,
148
148
  diversity,
149
+ energy,
149
150
  enumerate,
150
151
  filter,
151
152
  fingerprints,
@@ -154,6 +155,7 @@ def _register_commands(subparsers):
154
155
  mcs,
155
156
  merge,
156
157
  mmp,
158
+ pharmacophore,
157
159
  props,
158
160
  protonate,
159
161
  reactions,
@@ -167,6 +169,7 @@ def _register_commands(subparsers):
167
169
  split,
168
170
  standardize,
169
171
  stats,
172
+ stereo,
170
173
  validate,
171
174
  )
172
175
 
@@ -178,6 +181,7 @@ def _register_commands(subparsers):
178
181
  depict.register_parser(subparsers)
179
182
  descriptors.register_parser(subparsers)
180
183
  diversity.register_parser(subparsers)
184
+ energy.register_parser(subparsers)
181
185
  enumerate.register_parser(subparsers)
182
186
  filter.register_parser(subparsers)
183
187
  fingerprints.register_parser(subparsers)
@@ -186,6 +190,7 @@ def _register_commands(subparsers):
186
190
  mcs.register_parser(subparsers)
187
191
  merge.register_parser(subparsers)
188
192
  mmp.register_parser(subparsers)
193
+ pharmacophore.register_parser(subparsers)
189
194
  props.register_parser(subparsers)
190
195
  protonate.register_parser(subparsers)
191
196
  reactions.register_parser(subparsers)
@@ -199,6 +204,7 @@ def _register_commands(subparsers):
199
204
  split.register_parser(subparsers)
200
205
  standardize.register_parser(subparsers)
201
206
  stats.register_parser(subparsers)
207
+ stereo.register_parser(subparsers)
202
208
  validate.register_parser(subparsers)
203
209
 
204
210
 
@@ -120,6 +120,74 @@ def register_parser(subparsers):
120
120
  )
121
121
  opt_parser.set_defaults(func=run_optimize)
122
122
 
123
+ # conformers constrained
124
+ const_parser = conf_subparsers.add_parser(
125
+ "constrained",
126
+ help="Embed molecules constrained to a reference template",
127
+ formatter_class=RdkitHelpFormatter,
128
+ )
129
+ add_common_io_options(const_parser)
130
+ add_common_processing_options(const_parser)
131
+ const_parser.add_argument(
132
+ "-r", "--reference",
133
+ required=True,
134
+ metavar="FILE",
135
+ help="Reference molecule file with 3D coords (SDF, MOL, PDB)",
136
+ )
137
+ const_parser.add_argument(
138
+ "-f", "--force-field",
139
+ choices=["mmff", "uff"],
140
+ default="mmff",
141
+ help="Force field for optimization (default: mmff)",
142
+ )
143
+ const_parser.add_argument(
144
+ "--seed",
145
+ type=int,
146
+ default=42,
147
+ help="Random seed (default: 42)",
148
+ )
149
+ const_parser.set_defaults(func=run_constrained)
150
+
151
+ # conformers torsion
152
+ torsion_parser = conf_subparsers.add_parser(
153
+ "torsion",
154
+ help="Scan torsion angles and compute energy profile",
155
+ formatter_class=RdkitHelpFormatter,
156
+ )
157
+ add_common_io_options(torsion_parser)
158
+ add_common_processing_options(torsion_parser)
159
+ torsion_parser.add_argument(
160
+ "--atoms",
161
+ required=True,
162
+ metavar="I,J,K,L",
163
+ help="Comma-separated atom indices for the dihedral",
164
+ )
165
+ torsion_parser.add_argument(
166
+ "--start",
167
+ type=float,
168
+ default=-180.0,
169
+ help="Start angle in degrees (default: -180)",
170
+ )
171
+ torsion_parser.add_argument(
172
+ "--end",
173
+ type=float,
174
+ default=180.0,
175
+ help="End angle in degrees (default: 180)",
176
+ )
177
+ torsion_parser.add_argument(
178
+ "--step",
179
+ type=float,
180
+ default=10.0,
181
+ help="Step size in degrees (default: 10)",
182
+ )
183
+ torsion_parser.add_argument(
184
+ "-f", "--force-field",
185
+ choices=["mmff", "uff"],
186
+ default="mmff",
187
+ help="Force field (default: mmff)",
188
+ )
189
+ torsion_parser.set_defaults(func=run_torsion)
190
+
123
191
  # Set default for main parser
124
192
  parser.set_defaults(func=lambda args: parser.print_help() or 1)
125
193
 
@@ -218,3 +286,109 @@ def run_optimize(args) -> int:
218
286
  )
219
287
 
220
288
  return 0 if result.failed == 0 else 1
289
+
290
+
291
+ def run_constrained(args) -> int:
292
+ """Run constrained embedding."""
293
+ from rdkit_cli.core.conformers import ConstrainedEmbedder
294
+ from rdkit_cli.io import create_reader, create_writer, FileFormat
295
+
296
+ try:
297
+ embedder = ConstrainedEmbedder(
298
+ reference_file=args.reference,
299
+ force_field=args.force_field,
300
+ random_seed=args.seed,
301
+ )
302
+ except ValueError as e:
303
+ print(f"Error: {e}", file=sys.stderr)
304
+ return 1
305
+
306
+ input_path = Path(args.input)
307
+ if not input_path.exists():
308
+ print(f"Error: Input file not found: {input_path}", file=sys.stderr)
309
+ return 1
310
+
311
+ reader = create_reader(
312
+ input_path,
313
+ smiles_column=args.smiles_column,
314
+ name_column=args.name_column,
315
+ has_header=not args.no_header,
316
+ )
317
+
318
+ output_path = Path(args.output)
319
+ writer = create_writer(output_path, format_override=FileFormat.SDF)
320
+
321
+ # Single-threaded: ConstrainedEmbed not picklable
322
+ records = list(reader)
323
+ succeeded = 0
324
+ failed = 0
325
+ with writer:
326
+ for record in records:
327
+ result = embedder.embed(record)
328
+ if result is not None:
329
+ writer.write_row(result)
330
+ succeeded += 1
331
+ else:
332
+ failed += 1
333
+
334
+ if not args.quiet:
335
+ total = succeeded + failed
336
+ print(
337
+ f"Embedded {succeeded}/{total} molecules "
338
+ f"({failed} failed)",
339
+ file=sys.stderr,
340
+ )
341
+
342
+ return 0
343
+
344
+
345
+ def run_torsion(args) -> int:
346
+ """Run torsion angle scan."""
347
+ from rdkit_cli.core.conformers import TorsionScanner
348
+ from rdkit_cli.io import create_reader, create_writer
349
+
350
+ try:
351
+ indices = tuple(int(x) for x in args.atoms.split(","))
352
+ if len(indices) != 4:
353
+ raise ValueError("Need exactly 4 atom indices")
354
+ except ValueError as e:
355
+ print(f"Error: {e}", file=sys.stderr)
356
+ return 1
357
+
358
+ scanner = TorsionScanner(
359
+ atom_indices=indices,
360
+ start_angle=args.start,
361
+ end_angle=args.end,
362
+ step=args.step,
363
+ force_field=args.force_field,
364
+ )
365
+
366
+ input_path = Path(args.input)
367
+ if not input_path.exists():
368
+ print(f"Error: Input file not found: {input_path}", file=sys.stderr)
369
+ return 1
370
+
371
+ reader = create_reader(
372
+ input_path,
373
+ smiles_column=args.smiles_column,
374
+ name_column=getattr(args, "name_column", None),
375
+ has_header=not args.no_header,
376
+ )
377
+ output_path = Path(args.output)
378
+ writer = create_writer(output_path)
379
+
380
+ count = 0
381
+ with writer:
382
+ for record in reader:
383
+ result = scanner.scan(record)
384
+ if result is not None:
385
+ writer.write_row(result)
386
+ count += 1
387
+
388
+ if not args.quiet:
389
+ print(
390
+ f"Scanned torsion for {count} molecules",
391
+ file=sys.stderr,
392
+ )
393
+
394
+ return 0
@@ -274,6 +274,48 @@ def register_parser(subparsers):
274
274
  )
275
275
  grid_parser.set_defaults(func=run_grid)
276
276
 
277
+ # depict highlight
278
+ hl_parser = depict_subparsers.add_parser(
279
+ "highlight",
280
+ help="Depict with SMARTS-based highlighting",
281
+ formatter_class=RdkitHelpFormatter,
282
+ )
283
+ hl_parser.add_argument(
284
+ "smiles",
285
+ metavar="SMILES",
286
+ help="SMILES string of molecule to depict",
287
+ )
288
+ hl_parser.add_argument(
289
+ "-s", "--smarts",
290
+ required=True,
291
+ metavar="PATTERN",
292
+ help="SMARTS pattern to highlight",
293
+ )
294
+ hl_parser.add_argument(
295
+ "-o", "--output",
296
+ required=True,
297
+ metavar="FILE",
298
+ help="Output image file (SVG or PNG)",
299
+ )
300
+ hl_parser.add_argument(
301
+ "--width",
302
+ type=int,
303
+ default=400,
304
+ help="Image width (default: 400)",
305
+ )
306
+ hl_parser.add_argument(
307
+ "--height",
308
+ type=int,
309
+ default=300,
310
+ help="Image height (default: 300)",
311
+ )
312
+ hl_parser.add_argument(
313
+ "--color",
314
+ default="1.0,0.0,0.0",
315
+ help="Highlight color as R,G,B floats (default: 1.0,0.0,0.0)",
316
+ )
317
+ hl_parser.set_defaults(func=run_highlight)
318
+
277
319
  # Set default for main parser
278
320
  parser.set_defaults(func=lambda args: parser.print_help() or 1)
279
321
 
@@ -431,3 +473,72 @@ def run_grid(args) -> int:
431
473
  print(f"Wrote grid image to {output_path}", file=sys.stderr)
432
474
 
433
475
  return 0
476
+
477
+
478
+ def run_highlight(args) -> int:
479
+ """Depict molecule with SMARTS-based highlighting."""
480
+ from rdkit import Chem
481
+ from rdkit.Chem.Draw import rdMolDraw2D
482
+
483
+ mol = Chem.MolFromSmiles(args.smiles)
484
+ if mol is None:
485
+ print(f"Error: Invalid SMILES: {args.smiles}", file=sys.stderr)
486
+ return 1
487
+
488
+ pattern = Chem.MolFromSmarts(args.smarts)
489
+ if pattern is None:
490
+ print(f"Error: Invalid SMARTS: {args.smarts}", file=sys.stderr)
491
+ return 1
492
+
493
+ # Find matching atoms/bonds
494
+ matches = mol.GetSubstructMatches(pattern)
495
+ if not matches:
496
+ print("Warning: No substructure match found", file=sys.stderr)
497
+
498
+ highlight_atoms = []
499
+ highlight_bonds = []
500
+ for match in matches:
501
+ highlight_atoms.extend(match)
502
+ for i in range(len(match)):
503
+ for j in range(i + 1, len(match)):
504
+ bond = mol.GetBondBetweenAtoms(match[i], match[j])
505
+ if bond is not None:
506
+ highlight_bonds.append(bond.GetIdx())
507
+
508
+ # Parse color
509
+ try:
510
+ rgb = tuple(float(c) for c in args.color.split(","))
511
+ except (ValueError, TypeError):
512
+ rgb = (1.0, 0.0, 0.0)
513
+
514
+ atom_colors = {a: rgb for a in highlight_atoms}
515
+ bond_colors = {b: rgb for b in highlight_bonds}
516
+
517
+ output_path = Path(args.output)
518
+ fmt = output_path.suffix.lower().lstrip(".")
519
+
520
+ if fmt == "svg":
521
+ drawer = rdMolDraw2D.MolDraw2DSVG(args.width, args.height)
522
+ else:
523
+ drawer = rdMolDraw2D.MolDraw2DCairo(args.width, args.height)
524
+
525
+ drawer.DrawMolecule(
526
+ mol,
527
+ highlightAtoms=highlight_atoms,
528
+ highlightBonds=highlight_bonds,
529
+ highlightAtomColors=atom_colors,
530
+ highlightBondColors=bond_colors,
531
+ )
532
+ drawer.FinishDrawing()
533
+
534
+ data = drawer.GetDrawingText()
535
+ mode = "w" if fmt == "svg" else "wb"
536
+ with open(output_path, mode) as f:
537
+ f.write(data)
538
+
539
+ print(
540
+ f"Wrote highlighted image to {output_path} "
541
+ f"({len(highlight_atoms)} atoms highlighted)",
542
+ file=sys.stderr,
543
+ )
544
+ return 0
@@ -17,6 +17,8 @@ DESCRIPTOR_CATEGORIES = [
17
17
  "electronic",
18
18
  "geometric",
19
19
  "molecular",
20
+ "mqn",
21
+ "3d",
20
22
  ]
21
23
 
22
24
 
@@ -90,6 +92,17 @@ def register_parser(subparsers):
90
92
  action="store_true",
91
93
  help="Compute drug-likeness descriptors",
92
94
  )
95
+ desc_group.add_argument(
96
+ "--mqn",
97
+ action="store_true",
98
+ help="Compute 42 Molecular Quantum Numbers",
99
+ )
100
+ desc_group.add_argument(
101
+ "--3d",
102
+ action="store_true",
103
+ dest="compute_3d_set",
104
+ help="Compute 3D shape descriptors (PMI, NPR, Asphericity, etc.)",
105
+ )
93
106
  desc_group.add_argument(
94
107
  "--category",
95
108
  choices=DESCRIPTOR_CATEGORIES,
@@ -117,10 +130,9 @@ def register_parser(subparsers):
117
130
  help="Value to use for failed calculations (default: NaN)",
118
131
  )
119
132
  compute_parser.add_argument(
120
- "--3d",
133
+ "--generate-conformers",
121
134
  action="store_true",
122
- dest="compute_3d",
123
- help="Include 3D descriptors (requires 3D coordinates)",
135
+ help="Auto-generate 3D coordinates for 3D descriptors",
124
136
  )
125
137
  compute_parser.add_argument(
126
138
  "--no-smiles",
@@ -209,6 +221,8 @@ def run_compute(args) -> int:
209
221
  COMMON_DESCRIPTORS,
210
222
  LIPINSKI_DESCRIPTORS,
211
223
  DRUGLIKE_DESCRIPTORS,
224
+ MQN_DESCRIPTORS,
225
+ THREE_D_DESCRIPTORS,
212
226
  )
213
227
  from rdkit_cli.io import create_reader, create_writer
214
228
  from rdkit_cli.parallel.batch import process_molecules
@@ -224,6 +238,10 @@ def run_compute(args) -> int:
224
238
  descriptor_names = LIPINSKI_DESCRIPTORS
225
239
  elif args.druglike:
226
240
  descriptor_names = DRUGLIKE_DESCRIPTORS
241
+ elif args.mqn:
242
+ descriptor_names = MQN_DESCRIPTORS
243
+ elif args.compute_3d_set:
244
+ descriptor_names = THREE_D_DESCRIPTORS
227
245
  elif args.compute_category:
228
246
  descs = list_descriptors(category=args.compute_category)
229
247
  descriptor_names = [d.name for d in descs]
@@ -249,6 +267,7 @@ def run_compute(args) -> int:
249
267
  include_name=not args.no_name,
250
268
  precision=args.precision,
251
269
  error_value=args.error_value,
270
+ generate_conformers=getattr(args, "generate_conformers", False),
252
271
  )
253
272
  except ValueError as e:
254
273
  print(f"Error: {e}", file=sys.stderr)
@@ -0,0 +1,151 @@
1
+ """Energy command implementation."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from rdkit_cli.cli import (
7
+ RdkitHelpFormatter,
8
+ add_common_io_options,
9
+ add_common_processing_options,
10
+ )
11
+
12
+
13
+ def register_parser(subparsers):
14
+ """Register the energy command and subcommands."""
15
+ parser = subparsers.add_parser(
16
+ "energy",
17
+ help="Force field energy calculations",
18
+ description="Compute MMFF/UFF energies and minimize structures.",
19
+ formatter_class=RdkitHelpFormatter,
20
+ )
21
+
22
+ energy_sub = parser.add_subparsers(
23
+ title="Subcommands",
24
+ dest="subcommand",
25
+ metavar="<subcommand>",
26
+ )
27
+
28
+ # energy compute
29
+ compute_parser = energy_sub.add_parser(
30
+ "compute",
31
+ help="Compute single-point energy",
32
+ formatter_class=RdkitHelpFormatter,
33
+ )
34
+ add_common_io_options(compute_parser)
35
+ add_common_processing_options(compute_parser)
36
+ compute_parser.add_argument(
37
+ "-f", "--force-field",
38
+ choices=["mmff", "uff"],
39
+ default="mmff",
40
+ help="Force field (default: mmff)",
41
+ )
42
+ compute_parser.set_defaults(func=run_compute)
43
+
44
+ # energy minimize
45
+ minimize_parser = energy_sub.add_parser(
46
+ "minimize",
47
+ help="Minimize and report energy",
48
+ formatter_class=RdkitHelpFormatter,
49
+ )
50
+ add_common_io_options(minimize_parser)
51
+ add_common_processing_options(minimize_parser)
52
+ minimize_parser.add_argument(
53
+ "-f", "--force-field",
54
+ choices=["mmff", "uff"],
55
+ default="mmff",
56
+ help="Force field (default: mmff)",
57
+ )
58
+ minimize_parser.add_argument(
59
+ "--max-iter",
60
+ type=int,
61
+ default=500,
62
+ help="Maximum iterations (default: 500)",
63
+ )
64
+ minimize_parser.set_defaults(func=run_minimize)
65
+
66
+ parser.set_defaults(func=lambda args: parser.print_help() or 1)
67
+
68
+
69
+ def run_compute(args) -> int:
70
+ """Compute single-point energies."""
71
+ from rdkit_cli.core.energy import EnergyCalculator
72
+ from rdkit_cli.io import create_reader, create_writer
73
+ from rdkit_cli.parallel.batch import process_molecules
74
+
75
+ calculator = EnergyCalculator(force_field=args.force_field)
76
+
77
+ input_path = Path(args.input)
78
+ if not input_path.exists():
79
+ print(f"Error: Input file not found: {input_path}", file=sys.stderr)
80
+ return 1
81
+
82
+ reader = create_reader(
83
+ input_path,
84
+ smiles_column=args.smiles_column,
85
+ name_column=getattr(args, "name_column", None),
86
+ has_header=not args.no_header,
87
+ )
88
+ output_path = Path(args.output)
89
+ writer = create_writer(output_path)
90
+
91
+ with reader, writer:
92
+ result = process_molecules(
93
+ reader=reader,
94
+ writer=writer,
95
+ processor=calculator.compute,
96
+ n_workers=1, # 3D generation not picklable
97
+ quiet=args.quiet,
98
+ )
99
+
100
+ if not args.quiet:
101
+ print(
102
+ f"Computed energy for "
103
+ f"{result.successful}/{result.total_processed} molecules "
104
+ f"in {result.elapsed_time:.1f}s",
105
+ file=sys.stderr,
106
+ )
107
+ return 0
108
+
109
+
110
+ def run_minimize(args) -> int:
111
+ """Minimize structures."""
112
+ from rdkit_cli.core.energy import EnergyMinimizer
113
+ from rdkit_cli.io import create_reader, create_writer
114
+ from rdkit_cli.parallel.batch import process_molecules
115
+
116
+ minimizer = EnergyMinimizer(
117
+ force_field=args.force_field,
118
+ max_iterations=args.max_iter,
119
+ )
120
+
121
+ input_path = Path(args.input)
122
+ if not input_path.exists():
123
+ print(f"Error: Input file not found: {input_path}", file=sys.stderr)
124
+ return 1
125
+
126
+ reader = create_reader(
127
+ input_path,
128
+ smiles_column=args.smiles_column,
129
+ name_column=getattr(args, "name_column", None),
130
+ has_header=not args.no_header,
131
+ )
132
+ output_path = Path(args.output)
133
+ writer = create_writer(output_path)
134
+
135
+ with reader, writer:
136
+ result = process_molecules(
137
+ reader=reader,
138
+ writer=writer,
139
+ processor=minimizer.minimize,
140
+ n_workers=1,
141
+ quiet=args.quiet,
142
+ )
143
+
144
+ if not args.quiet:
145
+ print(
146
+ f"Minimized "
147
+ f"{result.successful}/{result.total_processed} molecules "
148
+ f"in {result.elapsed_time:.1f}s",
149
+ file=sys.stderr,
150
+ )
151
+ return 0